diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java index 036052bc98..887a583dbb 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java @@ -23,12 +23,7 @@ import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; -import javax.crypto.Cipher; -import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -38,7 +33,6 @@ import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.KeyException; import java.security.KeyFactory; import java.security.KeyStore; @@ -401,39 +395,4 @@ public abstract class JdkSslContext extends SslContext { return trustManagerFactory; } - - /** - * Generates a key specification for an (encrypted) private key. - * - * @param password characters, if {@code null} or empty an unencrypted key is assumed - * @param key bytes of the DER encoded private key - * - * @return a key specification - * - * @throws IOException if parsing {@code key} fails - * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown - * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown - * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated - * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt - * {@code key} - * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty - */ - private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key) - throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, - InvalidKeyException, InvalidAlgorithmParameterException { - - if (password == null || password.length == 0) { - return new PKCS8EncodedKeySpec(key); - } - - EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); - PBEKeySpec pbeKeySpec = new PBEKeySpec(password); - SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); - - Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); - cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters()); - - return encryptedPrivateKeyInfo.getKeySpec(cipher); - } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java index afd6677bab..d388485afa 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java @@ -17,6 +17,7 @@ package io.netty.handler.ssl; import javax.net.ssl.KeyManager; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java index e140f23bf1..1f1a8b3152 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java @@ -15,7 +15,12 @@ */ package io.netty.handler.ssl; +import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufInputStream; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.CertificateVerifier; import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSLContext; @@ -24,17 +29,21 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import java.io.File; +import java.io.IOException; import java.security.KeyStore; -import java.util.ArrayList; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.List; /** * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. */ public final class OpenSslClientContext extends OpenSslContext { - - private final X509TrustManager[] managers; + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class); /** * Creates a new instance. @@ -116,32 +125,31 @@ public final class OpenSslClientContext extends OpenSslContext { } SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH); - // check if verification should take place or not. - if (trustManagerFactory != null) { - try { - if (certChainFile == null) { - trustManagerFactory.init((KeyStore) null); - } else { - initTrustManagerFactory(certChainFile, trustManagerFactory); - } - } catch (Exception e) { - throw new SSLException("failed to initialize the client-side SSL context", e); + try { + // Set up trust manager factory to use our key store. + if (trustManagerFactory == null) { + trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); } - TrustManager[] tms = trustManagerFactory.getTrustManagers(); - if (tms == null || tms.length == 0) { - managers = null; - } else { - List managerList = new ArrayList(tms.length); - for (TrustManager tm: tms) { - if (tm instanceof X509TrustManager) { - managerList.add((X509TrustManager) tm); + initTrustManagerFactory(certChainFile, trustManagerFactory); + final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); + + SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() { + @Override + public boolean verify(long ssl, byte[][] chain, String auth) { + X509Certificate[] peerCerts = certificates(chain); + try { + manager.checkServerTrusted(peerCerts, auth); + return true; + } catch (Exception e) { + logger.debug("verification of certificate failed", e); } + return false; } - managers = managerList.toArray(new X509TrustManager[managerList.size()]); - } - } else { - managers = null; - } + }); + } catch (Exception e) { + throw new SSLException("unable to setup trustmanager", e); + } } success = true; } finally { @@ -151,13 +159,35 @@ public final class OpenSslClientContext extends OpenSslContext { } } + private static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + if (certChainFile != null) { + ByteBuf[] certs = PemReader.readCertificates(certChainFile); + try { + for (ByteBuf buf: certs) { + X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate( + new ByteBufInputStream(buf)); + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + } finally { + for (ByteBuf buf: certs) { + buf.release(); + } + } + } + trustManagerFactory.init(ks); + } + @Override public SSLEngine newEngine(ByteBufAllocator alloc) { List protos = applicationProtocolNegotiator().protocols(); if (protos.isEmpty()) { - return new OpenSslEngine(ctx, alloc, null, isClient(), managers); + return new OpenSslEngine(ctx, alloc, null, isClient()); } else { - return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), managers); + return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient()); } } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java index db6c369697..d7ee1e311e 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java @@ -25,6 +25,9 @@ import org.apache.tomcat.jni.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -221,6 +224,7 @@ public abstract class OpenSslContext extends SslContext { public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { throw new UnsupportedOperationException(); } + /** * Returns the {@code SSL_CTX} object of this context. */ @@ -265,6 +269,23 @@ public abstract class OpenSslContext extends SslContext { } } + protected static X509Certificate[] certificates(byte[][] chain) { + X509Certificate[] peerCerts = new X509Certificate[chain.length]; + for (int i = 0; i < peerCerts.length; i++) { + peerCerts[i] = new OpenSslX509Certificate(chain[i]); + } + return peerCerts; + } + + protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { + for (TrustManager m: managers) { + if (m instanceof X509TrustManager) { + return (X509TrustManager) m; + } + } + throw new IllegalStateException("no X509TrustManager found"); + } + /** * Translate a {@link ApplicationProtocolConfig} object to a * {@link OpenSslApplicationProtocolNegotiator} object. @@ -273,7 +294,7 @@ public abstract class OpenSslContext extends SslContext { * @return The results of the translation */ static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, - boolean isServer) { + boolean isServer) { if (config == null) { return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; } @@ -289,7 +310,8 @@ public abstract class OpenSslContext extends SslContext { default: throw new UnsupportedOperationException( new StringBuilder("OpenSSL provider does not support ") - .append(config.selectedListenerFailureBehavior()).append(" behavior").toString()); + .append(config.selectedListenerFailureBehavior()) + .append(" behavior").toString()); } } else { throw new UnsupportedOperationException("OpenSSL provider does not support client mode"); diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index b0075c46f6..d7e2877442 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -32,17 +32,13 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; -import javax.net.ssl.X509TrustManager; import javax.security.cert.X509Certificate; -import java.io.ByteArrayInputStream; +import javax.security.cert.CertificateException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.security.Principal; import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -130,7 +126,6 @@ public final class OpenSslEngine extends SSLEngine { private int lastPrimingReadResult; - private final X509TrustManager[] managers; private final boolean clientMode; private final ByteBufAllocator alloc; private final String fallbackApplicationProtocol; @@ -146,7 +141,7 @@ public final class OpenSslEngine extends SSLEngine { */ @Deprecated public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { - this(sslCtx, alloc, fallbackApplicationProtocol, false, null); + this(sslCtx, alloc, fallbackApplicationProtocol, false); } /** @@ -157,7 +152,7 @@ public final class OpenSslEngine extends SSLEngine { * @param clientMode {@code true} if this is used for clients, {@code false} otherwise */ OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, - boolean clientMode, X509TrustManager[] managers) { + boolean clientMode) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { throw new NullPointerException("sslContext"); @@ -171,11 +166,6 @@ public final class OpenSslEngine extends SSLEngine { networkBIO = SSL.makeNetworkBIO(ssl); this.fallbackApplicationProtocol = fallbackApplicationProtocol; this.clientMode = clientMode; - if (managers == null || managers.length == 0) { - this.managers = null; - } else { - this.managers = managers; - } } /** @@ -375,7 +365,7 @@ public final class OpenSslEngine extends SSLEngine { // In handshake or close_notify stages, check if call to wrap was made // without regard to the handshake status. - SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) { return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0); @@ -407,7 +397,7 @@ public final class OpenSslEngine extends SSLEngine { shutdown(); } - return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), 0, bytesProduced); + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced); } // There was no pending data in the network BIO -- encrypt any application data @@ -430,7 +420,7 @@ public final class OpenSslEngine extends SSLEngine { int capacity = dst.remaining(); if (capacity < pendingNet) { return new SSLEngineResult( - BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, bytesProduced); + BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); } // Write the pending data from the network BIO into the dst buffer @@ -440,12 +430,12 @@ public final class OpenSslEngine extends SSLEngine { throw new SSLException(e); } - return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); } } } - return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); } @Override @@ -490,7 +480,7 @@ public final class OpenSslEngine extends SSLEngine { // In handshake or close_notify stages, check if call to unwrap was made // without regard to the handshake status. - SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) { return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0); } @@ -532,7 +522,7 @@ public final class OpenSslEngine extends SSLEngine { // Do we have enough room in dsts to write decrypted data? if (capacity < pendingApp) { - return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, 0); + return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0); } // Write decrypted data to dsts buffers @@ -575,7 +565,7 @@ public final class OpenSslEngine extends SSLEngine { closeInbound(); } - return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); } @Override @@ -679,33 +669,27 @@ public final class OpenSslEngine extends SSLEngine { if (chain == null && clientCert == null) { throw new SSLPeerUnverifiedException("peer not verified"); } - try { - int len = 0; - if (chain != null) { - len += chain.length; - } - - int i = 0; - Certificate[] peerCerts; - if (clientCert != null) { - len++; - peerCerts = new Certificate[len]; - peerCerts[i++] = SslContext.X509_CERT_FACTORY.generateCertificate( - new ByteArrayInputStream(clientCert)); - } else { - peerCerts = new Certificate[len]; - } - if (chain != null) { - int a = 0; - for (; i < peerCerts.length; i++) { - peerCerts[i] = SslContext.X509_CERT_FACTORY.generateCertificate( - new ByteArrayInputStream(chain[a++])); - } - } - return peerCerts; - } catch (CertificateException e) { - throw new IllegalStateException(e); + int len = 0; + if (chain != null) { + len += chain.length; } + + int i = 0; + Certificate[] peerCerts; + if (clientCert != null) { + len++; + peerCerts = new Certificate[len]; + peerCerts[i++] = new OpenSslX509Certificate(clientCert); + } else { + peerCerts = new Certificate[len]; + } + if (chain != null) { + int a = 0; + for (; i < peerCerts.length; i++) { + peerCerts[i] = new OpenSslX509Certificate(chain[a++]); + } + } + return peerCerts; } @Override @@ -851,7 +835,7 @@ public final class OpenSslEngine extends SSLEngine { for (int i = 0; i < peerCerts.length; i++) { try { peerCerts[i] = X509Certificate.getInstance(chain[i]); - } catch (javax.security.cert.CertificateException e) { + } catch (CertificateException e) { throw new IllegalStateException(e); } } @@ -997,16 +981,6 @@ public final class OpenSslEngine extends SSLEngine { @Override public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { - try { - return handshakeStatus(false); - } catch (SSLException e) { - // Can not happen! - throw new Error(e); - } - } - - // No need to synchronize access as it is only called from within synchronized methods - private SSLEngineResult.HandshakeStatus handshakeStatus(boolean verify) throws SSLException { if (accepted == 0 || destroyed != 0) { return NOT_HANDSHAKING; } @@ -1037,10 +1011,6 @@ public final class OpenSslEngine extends SSLEngine { } else { this.applicationProtocol = null; } - if (verify && managers != null) { - peerCerts = initPeerCertChain(); - verifyCertificates(managers, peerCerts, cipher); - } return FINISHED; } @@ -1131,31 +1101,6 @@ public final class OpenSslEngine extends SSLEngine { return false; } - private void verifyCertificates(X509TrustManager[] managers, Certificate[] peerCerts, String cipher) - throws SSLException { - - List certs = - new ArrayList(peerCerts.length); - for (Certificate c: peerCerts) { - if (c instanceof java.security.cert.X509Certificate) { - certs.add((java.security.cert.X509Certificate) c); - } - } - java.security.cert.X509Certificate[] certArray = - certs.toArray(new java.security.cert.X509Certificate[certs.size()]); - for (X509TrustManager tm: managers) { - try { - if (clientMode) { - tm.checkServerTrusted(certArray, cipher); - } else { - tm.checkClientTrusted(certArray, cipher); - } - } catch (CertificateException e) { - throw new SSLException("peer certificate chain not trusted", e); - } - } - } - @Override protected void finalize() throws Throwable { super.finalize(); diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java index 446829f065..2a78851b88 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -15,13 +15,29 @@ */ package io.netty.handler.ssl; +import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufInputStream; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.CertificateVerifier; import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import java.io.File; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; import java.util.List; import static io.netty.util.internal.ObjectUtil.*; @@ -30,6 +46,7 @@ import static io.netty.util.internal.ObjectUtil.*; * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. */ public final class OpenSslServerContext extends OpenSslContext { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class); /** * Creates a new instance. @@ -166,6 +183,62 @@ public final class OpenSslServerContext extends OpenSslContext { } catch (Exception e) { throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e); } + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + KeyFactory rsaKF = KeyFactory.getInstance("RSA"); + KeyFactory dsaKF = KeyFactory.getInstance("DSA"); + + ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile); + byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()]; + encodedKeyBuf.readBytes(encodedKey).release(); + + char[] keyPasswordChars = keyPassword.toCharArray(); + PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey); + + PrivateKey key; + try { + key = rsaKF.generatePrivate(encodedKeySpec); + } catch (InvalidKeySpecException ignore) { + key = dsaKF.generatePrivate(encodedKeySpec); + } + + List certChain = new ArrayList(); + ByteBuf[] certs = PemReader.readCertificates(certChainFile); + try { + for (ByteBuf buf: certs) { + certChain.add(cf.generateCertificate(new ByteBufInputStream(buf))); + } + } finally { + for (ByteBuf buf: certs) { + buf.release(); + } + } + + ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()])); + + // This mimics the behavior of using SSLContext.init(...); + TrustManagerFactory factory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore) null); + final X509TrustManager manager = chooseTrustManager(factory.getTrustManagers()); + SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() { + @Override + public boolean verify(long ssl, byte[][] chain, String auth) { + X509Certificate[] peerCerts = certificates(chain); + try { + manager.checkClientTrusted(peerCerts, auth); + return true; + } catch (Exception e) { + logger.debug("verification of certificate failed", e); + } + return false; + } + }); + } catch (Exception e) { + throw new SSLException("unable to setup trustmanager", e); + } } success = true; } finally { @@ -175,16 +248,13 @@ public final class OpenSslServerContext extends OpenSslContext { } } - /** - * Returns a new server-side {@link javax.net.ssl.SSLEngine} with the current configuration. - */ @Override public SSLEngine newEngine(ByteBufAllocator alloc) { List protos = applicationProtocolNegotiator().protocols(); if (protos.isEmpty()) { - return new OpenSslEngine(ctx, alloc, null, isClient(), null); + return new OpenSslEngine(ctx, alloc, null, isClient()); } else { - return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null); + return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient()); } } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslX509Certificate.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslX509Certificate.java new file mode 100644 index 0000000000..77d0713613 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslX509Certificate.java @@ -0,0 +1,189 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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 io.netty.handler.ssl; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +final class OpenSslX509Certificate extends X509Certificate { + + private final byte[] bytes; + private X509Certificate wrapped; + + public OpenSslX509Certificate(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException { + unwrap().checkValidity(); + } + + @Override + public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException { + unwrap().checkValidity(date); + } + + @Override + public int getVersion() { + return unwrap().getVersion(); + } + + @Override + public BigInteger getSerialNumber() { + return unwrap().getSerialNumber(); + } + + @Override + public Principal getIssuerDN() { + return unwrap().getIssuerDN(); + } + + @Override + public Principal getSubjectDN() { + return unwrap().getSubjectDN(); + } + + @Override + public Date getNotBefore() { + return unwrap().getNotBefore(); + } + + @Override + public Date getNotAfter() { + return unwrap().getNotAfter(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return unwrap().getTBSCertificate(); + } + + @Override + public byte[] getSignature() { + return unwrap().getSignature(); + } + + @Override + public String getSigAlgName() { + return unwrap().getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return unwrap().getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return unwrap().getSigAlgParams(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return unwrap().getIssuerUniqueID(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return unwrap().getSubjectUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return unwrap().getKeyUsage(); + } + + @Override + public int getBasicConstraints() { + return unwrap().getBasicConstraints(); + } + + @Override + public byte[] getEncoded() { + return bytes.clone(); + } + + @Override + public void verify(PublicKey key) + throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + unwrap().verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) + throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + unwrap().verify(key, sigProvider); + } + + @Override + public String toString() { + return unwrap().toString(); + } + + @Override + public PublicKey getPublicKey() { + return unwrap().getPublicKey(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return unwrap().hasUnsupportedCriticalExtension(); + } + + @Override + public Set getCriticalExtensionOIDs() { + return unwrap().getCriticalExtensionOIDs(); + } + + @Override + public Set getNonCriticalExtensionOIDs() { + return unwrap().getNonCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return unwrap().getExtensionValue(oid); + } + + private X509Certificate unwrap() { + X509Certificate wrapped = this.wrapped; + if (wrapped == null) { + try { + wrapped = this.wrapped = (X509Certificate) SslContext.X509_CERT_FACTORY.generateCertificate( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + throw new IllegalStateException(e); + } + } + return wrapped; + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java index 5bb0f8541b..0087eea2e1 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -27,6 +27,12 @@ import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; @@ -35,12 +41,16 @@ import javax.net.ssl.TrustManagerFactory; import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.List; /** @@ -723,23 +733,38 @@ public abstract class SslContext { return new SslHandler(engine); } - static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory) - throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(null, null); - ByteBuf[] certs = PemReader.readCertificates(certChainFile); - try { - for (ByteBuf buf: certs) { - X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate( - new ByteBufInputStream(buf)); - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - } finally { - for (ByteBuf buf: certs) { - buf.release(); - } + /** + * Generates a key specification for an (encrypted) private key. + * + * @param password characters, if {@code null} or empty an unencrypted key is assumed + * @param key bytes of the DER encoded private key + * + * @return a key specification + * + * @throws IOException if parsing {@code key} fails + * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown + * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown + * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated + * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt + * {@code key} + * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty + */ + protected static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key) + throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, + InvalidKeyException, InvalidAlgorithmParameterException { + + if (password == null || password.length == 0) { + return new PKCS8EncodedKeySpec(key); } - trustManagerFactory.init(ks); + + EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); + PBEKeySpec pbeKeySpec = new PBEKeySpec(password); + SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); + + Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); + cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters()); + + return encryptedPrivateKeyInfo.getKeySpec(cipher); } }