/*
* Copyright 2014 Christopher Mann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.uni_bonn.bit;
import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.reflect.ReflectResponder;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.ssl.SslHandler;
import org.spongycastle.asn1.x500.X500Name;
import org.spongycastle.asn1.x509.AlgorithmIdentifier;
import org.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cert.bc.BcX509v3CertificateBuilder;
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.generators.RSAKeyPairGenerator;
import org.spongycastle.crypto.params.RSAKeyGenerationParameters;
import org.spongycastle.crypto.params.RSAKeyParameters;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.spongycastle.operator.OperatorCreationException;
import org.spongycastle.operator.bc.BcRSAContentSignerBuilder;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;
import java.util.concurrent.Executors;
/**
* This class contains the boiler plate code to create a server for the {@link de.uni_bonn.bit.wallet_protocol.IPairingProtocol}
* or the {@link de.uni_bonn.bit.wallet_protocol.IWalletProtocol}. The server supports incoming TLS connections. A random
* RSA key pair generated when creating a new instance of this class. The TLS connection uses a certificate which is generated ad-hoc.
* The certificate contains the RSA public key. The key can be retrieved with {@link ProtocolServer#getPublicKey()}.
*/
public class ProtocolServer {
AsymmetricCipherKeyPair keyPair;
NettyServer nettyServer;
public ProtocolServer(Class protocolInterfaceType, Object protocolImpl){
RSAKeyPairGenerator rsaGen = new RSAKeyPairGenerator();
rsaGen.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x1001), new SecureRandom(), 2048, 25));
keyPair = rsaGen.generateKeyPair();
ChannelFactory channelFactory = new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()
);
nettyServer = new NettyServer(new ReflectResponder(
protocolInterfaceType,
protocolImpl), new InetSocketAddress(7001),
channelFactory, new SSLChannelPipelineFactory(keyPair),
null);
}
public RSAPublicKey getPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
RSAKeyParameters publicKey = (RSAKeyParameters) keyPair.getPublic();
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getExponent());
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
}
public void close() {
nettyServer.close();
}
private static class SSLChannelPipelineFactory
implements ChannelPipelineFactory {
private AsymmetricCipherKeyPair keyPair;
public SSLChannelPipelineFactory(AsymmetricCipherKeyPair keyPair){
this.keyPair = keyPair;
}
/**
* Generates a short-living certificate for the keyPair.
*/
private X509Certificate generateCertificate() throws NoSuchProviderException, NoSuchAlgorithmException, CertificateException, SignatureException, InvalidKeyException, IOException, OperatorCreationException {
/* The certificate starts to be valid one minute in the past to be safe
* if the clocks are a bit out of sync. */
Calendar startDate = Calendar.getInstance();
startDate.add(Calendar.MINUTE, -1);
/* The certificate is not valid anymore after two minutes. This should
* be enough to complete the protocol. */
Calendar expiryDate = Calendar.getInstance();
expiryDate.add(Calendar.MINUTE, +2);
AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
ContentSigner signer = new BcRSAContentSignerBuilder(
new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"),
new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA))
.build(keyPair.getPrivate());
X500Name subjectName = new X500Name("CN=Wallet Protocol Server Ephemeral Certificate");
BcX509v3CertificateBuilder certBuilder = new BcX509v3CertificateBuilder(
subjectName,
BigInteger.ONE,
startDate.getTime(), expiryDate.getTime(),
subjectName,
keyPair.getPublic()
);
X509CertificateHolder certHolder = certBuilder.build(signer);
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
return cert;
}
private SSLContext createServerSSLContext() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
java.security.cert.Certificate cert = generateCertificate();
RSAKeyParameters privateKey = (RSAKeyParameters) keyPair.getPrivate();
RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(privateKey.getModulus(), privateKey.getExponent());
PrivateKey jPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
keyStore.setKeyEntry("myCert", jPrivateKey, "aaa".toCharArray(), new java.security.cert.Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "aaa".toCharArray());
SSLContext serverContext = SSLContext.getInstance("TLS");
serverContext.init(kmf.getKeyManagers(), null, null);
return serverContext;
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
SSLEngine sslEngine = createServerSSLContext().createSSLEngine();
/**
* This fixes the cipher suite and disables DH key agreement. The RSA key pair itself only lives as long
* as the instance of this class. Therefore, there is no need to do a DH key agreement for PFS. The RSA key
* itself will only be used for a very short time.
*/
sslEngine.setEnabledCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA"});
sslEngine.setUseClientMode(false);
pipeline.addLast("ssl", new SslHandler(sslEngine));
return pipeline;
}
}
}