diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java index c3422cae25..0ec0dad63f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java @@ -16,7 +16,6 @@ package io.netty.handler.ssl; - import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java index 206c27a38f..0036949928 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java @@ -30,8 +30,7 @@ public final class OpenSsl { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); private static final Throwable UNAVAILABILITY_CAUSE; - - static final String IGNORABLE_ERROR_PREFIX = "error:00000000:"; + private static final String IGNORABLE_ERROR_PREFIX = "error:00000000:"; static { Throwable cause = null; @@ -80,5 +79,9 @@ public final class OpenSsl { return UNAVAILABILITY_CAUSE; } + static boolean isError(String error) { + return error != null && !error.startsWith(IGNORABLE_ERROR_PREFIX); + } + private OpenSsl() { } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java new file mode 100644 index 0000000000..e140f23bf1 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java @@ -0,0 +1,163 @@ +/* + * 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 io.netty.buffer.ByteBufAllocator; +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.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.security.KeyStore; +import java.util.ArrayList; +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; + + /** + * Creates a new instance. + */ + public OpenSslClientContext() throws SSLException { + this(null, null, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + */ + public OpenSslClientContext(File certChainFile) throws SSLException { + this(certChainFile, null); + } + + /** + * Creates a new instance. + * + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + */ + public OpenSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { + this(null, trustManagerFactory); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + */ + public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + this(certChainFile, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default.. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param apn Provides a means to configure parameters related to application protocol negotiation. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + */ + public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, + ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout) + throws SSLException { + super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT); + boolean success = false; + try { + if (certChainFile != null && !certChainFile.isFile()) { + throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile); + } + + synchronized (OpenSslContext.class) { + if (certChainFile != null) { + /* Load the certificate chain. We must skip the first cert when server mode */ + if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) { + String error = SSL.getLastError(); + if (OpenSsl.isError(error)) { + throw new SSLException( + "failed to set certificate chain: " + + certChainFile + " (" + SSL.getLastError() + ')'); + } + } + } + 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); + } + 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); + } + } + managers = managerList.toArray(new X509TrustManager[managerList.size()]); + } + } else { + managers = null; + } + } + success = true; + } finally { + if (!success) { + destroyPools(); + } + } + } + + @Override + public SSLEngine newEngine(ByteBufAllocator alloc) { + List protos = applicationProtocolNegotiator().protocols(); + if (protos.isEmpty()) { + return new OpenSslEngine(ctx, alloc, null, isClient(), managers); + } else { + return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), managers); + } + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java new file mode 100644 index 0000000000..db6c369697 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java @@ -0,0 +1,302 @@ +/* + * 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 io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.Pool; +import org.apache.tomcat.jni.SSL; +import org.apache.tomcat.jni.SSLContext; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +public abstract class OpenSslContext extends SslContext { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslContext.class); + private static final List DEFAULT_CIPHERS; + private static final AtomicIntegerFieldUpdater DESTROY_UPDATER; + + // TODO: Maybe make configurable ? + protected static final int VERIFY_DEPTH = 10; + + private final long aprPool; + @SuppressWarnings("unused") + private volatile int aprPoolDestroyed; + private final List ciphers = new ArrayList(); + private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers); + private final long sessionCacheSize; + private final long sessionTimeout; + private final OpenSslApplicationProtocolNegotiator apn; + /** The OpenSSL SSL_CTX object */ + protected final long ctx; + private final int mode; + private final OpenSslSessionStats stats; + + static { + List ciphers = new ArrayList(); + // XXX: Make sure to sync this list with JdkSslEngineFactory. + Collections.addAll( + ciphers, + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-SHA", + "ECDHE-RSA-AES256-SHA", + "AES128-GCM-SHA256", + "AES128-SHA", + "AES256-SHA", + "DES-CBC3-SHA", + "RC4-SHA"); + DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers); + + if (logger.isDebugEnabled()) { + logger.debug("Default cipher suite (OpenSSL): " + ciphers); + } + + AtomicIntegerFieldUpdater updater = + PlatformDependent.newAtomicIntegerFieldUpdater(OpenSslContext.class, "aprPoolDestroyed"); + if (updater == null) { + updater = AtomicIntegerFieldUpdater.newUpdater(OpenSslContext.class, "aprPoolDestroyed"); + } + DESTROY_UPDATER = updater; + } + + OpenSslContext(Iterable ciphers, ApplicationProtocolConfig apnCfg, long sessionCacheSize, + long sessionTimeout, int mode) throws SSLException { + this(ciphers, toNegotiator(apnCfg, mode == SSL.SSL_MODE_SERVER), sessionCacheSize, sessionTimeout, mode); + } + + OpenSslContext(Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, + long sessionTimeout, int mode) throws SSLException { + OpenSsl.ensureAvailability(); + + if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) { + throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT"); + } + this.mode = mode; + + if (ciphers == null) { + ciphers = DEFAULT_CIPHERS; + } + + for (String c: ciphers) { + if (c == null) { + break; + } + this.ciphers.add(c); + } + + this.apn = checkNotNull(apn, "apn"); + + // Allocate a new APR pool. + aprPool = Pool.create(0); + + // Create a new SSL_CTX and configure it. + boolean success = false; + try { + synchronized (OpenSslContext.class) { + try { + ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, mode); + } catch (Exception e) { + throw new SSLException("failed to create an SSL_CTX", e); + } + + SSLContext.setOptions(ctx, SSL.SSL_OP_ALL); + SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2); + SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3); + SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); + SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE); + SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE); + SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + /* List the ciphers that are permitted to negotiate. */ + try { + // Convert the cipher list into a colon-separated string. + StringBuilder cipherBuf = new StringBuilder(); + for (String c: this.ciphers) { + cipherBuf.append(c); + cipherBuf.append(':'); + } + cipherBuf.setLength(cipherBuf.length() - 1); + + SSLContext.setCipherSuite(ctx, cipherBuf.toString()); + } catch (SSLException e) { + throw e; + } catch (Exception e) { + throw new SSLException("failed to set cipher suite: " + this.ciphers, e); + } + + List nextProtoList = apn.protocols(); + /* Set next protocols for next protocol negotiation extension, if specified */ + if (!nextProtoList.isEmpty()) { + // Convert the protocol list into a comma-separated string. + StringBuilder nextProtocolBuf = new StringBuilder(); + for (String p: nextProtoList) { + nextProtocolBuf.append(p); + nextProtocolBuf.append(','); + } + nextProtocolBuf.setLength(nextProtocolBuf.length() - 1); + + SSLContext.setNextProtos(ctx, nextProtocolBuf.toString()); + } + + /* Set session cache size, if specified */ + if (sessionCacheSize > 0) { + this.sessionCacheSize = sessionCacheSize; + SSLContext.setSessionCacheSize(ctx, sessionCacheSize); + } else { + // Get the default session cache size using SSLContext.setSessionCacheSize() + this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480); + // Revert the session cache size to the default value. + SSLContext.setSessionCacheSize(ctx, sessionCacheSize); + } + + /* Set session timeout, if specified */ + if (sessionTimeout > 0) { + this.sessionTimeout = sessionTimeout; + SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); + } else { + // Get the default session timeout using SSLContext.setSessionCacheTimeout() + this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300); + // Revert the session timeout to the default value. + SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); + } + } + success = true; + } finally { + if (!success) { + destroyPools(); + } + } + + stats = new OpenSslSessionStats(ctx); + } + + @Override + public final List cipherSuites() { + return unmodifiableCiphers; + } + + @Override + public final long sessionCacheSize() { + return sessionCacheSize; + } + + @Override + public final long sessionTimeout() { + return sessionTimeout; + } + + @Override + public ApplicationProtocolNegotiator applicationProtocolNegotiator() { + return apn; + } + + @Override + public final boolean isClient() { + return mode == SSL.SSL_MODE_CLIENT; + } + + @Override + public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { + throw new UnsupportedOperationException(); + } + /** + * Returns the {@code SSL_CTX} object of this context. + */ + public final long context() { + return ctx; + } + + /** + * Returns the stats of this context. + */ + public final OpenSslSessionStats stats() { + return stats; + } + + @Override + @SuppressWarnings("FinalizeDeclaration") + protected final void finalize() throws Throwable { + super.finalize(); + synchronized (OpenSslContext.class) { + if (ctx != 0) { + SSLContext.free(ctx); + } + } + + destroyPools(); + } + + /** + * Sets the SSL session ticket keys of this context. + */ + public final void setTicketKeys(byte[] keys) { + if (keys == null) { + throw new NullPointerException("keys"); + } + SSLContext.setSessionTicketKeys(ctx, keys); + } + + protected final void destroyPools() { + // Guard against multiple destroyPools() calls triggered by construction exception and finalize() later + if (aprPool != 0 && DESTROY_UPDATER.compareAndSet(this, 0, 1)) { + Pool.destroy(aprPool); + } + } + + /** + * Translate a {@link ApplicationProtocolConfig} object to a + * {@link OpenSslApplicationProtocolNegotiator} object. + * @param config The configuration which defines the translation + * @param isServer {@code true} if a server {@code false} otherwise. + * @return The results of the translation + */ + static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, + boolean isServer) { + if (config == null) { + return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; + } + + switch(config.protocol()) { + case NONE: + return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; + case NPN: + if (isServer) { + switch(config.selectedListenerFailureBehavior()) { + case CHOOSE_MY_LAST_PROTOCOL: + return new OpenSslNpnApplicationProtocolNegotiator(config.supportedProtocols()); + default: + throw new UnsupportedOperationException( + new StringBuilder("OpenSSL provider does not support ") + .append(config.selectedListenerFailureBehavior()).append(" behavior").toString()); + } + } else { + throw new UnsupportedOperationException("OpenSSL provider does not support client mode"); + } + default: + throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ") + .append(config.protocol()).append(" protocol").toString()); + } + } +} 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 4e15508630..9047ff157c 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -26,14 +26,21 @@ import org.apache.tomcat.jni.SSL; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.X509TrustManager; import javax.security.cert.X509Certificate; +import java.io.ByteArrayInputStream; 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.List; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.regex.Pattern; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; import static javax.net.ssl.SSLEngineResult.Status.*; @@ -47,12 +54,9 @@ public final class OpenSslEngine extends SSLEngine { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class); private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0]; - private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; - private static final SSLException ENGINE_CLOSED = new SSLException("engine closed"); private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported"); private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized"); - static { ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); @@ -71,6 +75,7 @@ public final class OpenSslEngine extends SSLEngine { private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); + private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-"); // OpenSSL state private long ssl; private long networkBIO; @@ -84,8 +89,12 @@ public final class OpenSslEngine extends SSLEngine { @SuppressWarnings("UnusedDeclaration") private volatile int destroyed; - private String cipher; + // Use an invalid cipherSuite until the handshake is completed + // See http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#getSession() + private volatile String cipher = "SSL_NULL_WITH_NULL_NULL"; private volatile String applicationProtocol; + private volatile Certificate[] peerCerts; + private volatile X509Certificate[] x509PeerCerts; // SSL Engine status variables private boolean isInboundDone; @@ -94,6 +103,8 @@ 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; private SSLSession session; @@ -104,7 +115,20 @@ public final class OpenSslEngine extends SSLEngine { * @param sslCtx an OpenSSL {@code SSL_CTX} object * @param alloc the {@link ByteBufAllocator} that will be used by this engine */ + @Deprecated public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { + this(sslCtx, alloc, fallbackApplicationProtocol, false, null); + } + + /** + * Creates a new instance + * + * @param sslCtx an OpenSSL {@code SSL_CTX} object + * @param alloc the {@link ByteBufAllocator} that will be used by this engine + * @param clientMode {@code true} if this is used for clients, {@code false} otherwise + */ + OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, + boolean clientMode, X509TrustManager[] managers) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { throw new NullPointerException("sslContext"); @@ -114,9 +138,15 @@ public final class OpenSslEngine extends SSLEngine { } this.alloc = alloc; - ssl = SSL.newSSL(sslCtx, true); + ssl = SSL.newSSL(sslCtx, !clientMode); networkBIO = SSL.makeNetworkBIO(ssl); this.fallbackApplicationProtocol = fallbackApplicationProtocol; + this.clientMode = clientMode; + if (managers == null || managers.length == 0) { + this.managers = null; + } else { + this.managers = managers; + } } /** @@ -154,12 +184,7 @@ public final class OpenSslEngine extends SSLEngine { } else { ByteBuf buf = alloc.directBuffer(len); try { - final long addr; - if (buf.hasMemoryAddress()) { - addr = buf.memoryAddress(); - } else { - addr = Buffer.address(buf.nioBuffer()); - } + final long addr = memoryAddress(buf); src.limit(pos + len); @@ -198,12 +223,7 @@ public final class OpenSslEngine extends SSLEngine { } else { final ByteBuf buf = alloc.directBuffer(len); try { - final long addr; - if (buf.hasMemoryAddress()) { - addr = buf.memoryAddress(); - } else { - addr = Buffer.address(buf.nioBuffer()); - } + final long addr = memoryAddress(buf); buf.setBytes(0, src); @@ -242,12 +262,7 @@ public final class OpenSslEngine extends SSLEngine { final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos); final ByteBuf buf = alloc.directBuffer(len); try { - final long addr; - if (buf.hasMemoryAddress()) { - addr = buf.memoryAddress(); - } else { - addr = Buffer.address(buf.nioBuffer()); - } + final long addr = memoryAddress(buf); final int sslRead = SSL.readFromSSL(ssl, addr, len); if (sslRead > 0) { @@ -279,12 +294,7 @@ public final class OpenSslEngine extends SSLEngine { } else { final ByteBuf buf = alloc.directBuffer(pending); try { - final long addr; - if (buf.hasMemoryAddress()) { - addr = buf.memoryAddress(); - } else { - addr = Buffer.address(buf.nioBuffer()); - } + final long addr = memoryAddress(buf); final int bioRead = SSL.readFromBIO(networkBIO, addr, pending); if (bioRead > 0) { @@ -336,7 +346,8 @@ 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 = getHandshakeStatus(); + SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); + if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) { return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0); } @@ -367,7 +378,7 @@ public final class OpenSslEngine extends SSLEngine { shutdown(); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), 0, bytesProduced); } // There was no pending data in the network BIO -- encrypt any application data @@ -389,7 +400,8 @@ public final class OpenSslEngine extends SSLEngine { // Do we have enough room in dst to write encrypted data? int capacity = dst.remaining(); if (capacity < pendingNet) { - return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult( + BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, bytesProduced); } // Write the pending data from the network BIO into the dst buffer @@ -399,12 +411,12 @@ public final class OpenSslEngine extends SSLEngine { throw new SSLException(e); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); } } } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); } @Override @@ -449,7 +461,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 = getHandshakeStatus(); + SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) { return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0); } @@ -474,7 +486,7 @@ public final class OpenSslEngine extends SSLEngine { // Check for OpenSSL errors caused by the priming read String error = SSL.getLastError(); - if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { + if (OpenSsl.isError(error)) { if (logger.isInfoEnabled()) { logger.info( "SSL_read failed: primingReadResult: " + lastPrimingReadResult + @@ -491,7 +503,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, getHandshakeStatus(), bytesConsumed, 0); + return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, 0); } // Write decrypted data to dsts buffers @@ -534,7 +546,7 @@ public final class OpenSslEngine extends SSLEngine { closeInbound(); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); } @Override @@ -622,14 +634,65 @@ public final class OpenSslEngine extends SSLEngine { throw new UnsupportedOperationException(); } + private Certificate[] initPeerCertChain() throws SSLPeerUnverifiedException { + byte[][] chain = SSL.getPeerCertChain(ssl); + byte[] clientCert; + if (!clientMode) { + // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer certificate. + // We use SSL_get_peer_certificate to get it in this case and add it to our array later. + // + // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html + clientCert = SSL.getPeerCertificate(ssl); + } else { + clientCert = null; + } + + 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); + } + } + @Override public SSLSession getSession() { SSLSession session = this.session; if (session == null) { this.session = session = new SSLSession() { + private volatile byte[] id; + @Override public byte[] getId() { - return String.valueOf(ssl).getBytes(); + // these are lazy created to reduce memory overhead but cached for performance reasons. + if (id == null) { + id = String.valueOf(ssl).getBytes(); + } + return id; } @Override @@ -675,8 +738,16 @@ public final class OpenSslEngine extends SSLEngine { } @Override - public Certificate[] getPeerCertificates() { - return EMPTY_CERTIFICATES; + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + // these are lazy created to reduce memory overhead + Certificate[] c = peerCerts; + if (c == null) { + if (SSL.isInInit(ssl) != 0) { + throw new SSLPeerUnverifiedException("peer not verified"); + } + c = peerCerts = initPeerCertChain(); + } + return c; } @Override @@ -685,8 +756,28 @@ public final class OpenSslEngine extends SSLEngine { } @Override - public X509Certificate[] getPeerCertificateChain() { - return EMPTY_X509_CERTIFICATES; + public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + // these are lazy created to reduce memory overhead + X509Certificate[] c = x509PeerCerts; + if (c == null) { + if (SSL.isInInit(ssl) != 0) { + throw new SSLPeerUnverifiedException("peer not verified"); + } + byte[][] chain = SSL.getPeerCertChain(ssl); + if (chain == null) { + throw new SSLPeerUnverifiedException("peer not verified"); + } + X509Certificate[] peerCerts = new X509Certificate[chain.length]; + for (int i = 0; i < peerCerts.length; i++) { + try { + peerCerts[i] = X509Certificate.getInstance(chain[i]); + } catch (javax.security.cert.CertificateException e) { + throw new IllegalStateException(e); + } + } + c = x509PeerCerts = peerCerts; + } + return c; } @Override @@ -747,7 +838,7 @@ public final class OpenSslEngine extends SSLEngine { } switch (accepted) { case 0: - SSL.doHandshake(ssl); + handshake(); accepted = 2; break; case 1: @@ -772,17 +863,53 @@ public final class OpenSslEngine extends SSLEngine { } if (accepted == 0) { - SSL.doHandshake(ssl); + handshake(); accepted = 1; } } + private void handshake() throws SSLException { + int code = SSL.doHandshake(ssl); + if (code <= 0) { + // Check for OpenSSL errors caused by the handshake + String error = SSL.getLastError(); + if (OpenSsl.isError(error)) { + if (logger.isInfoEnabled()) { + logger.info( + "SSL_do_handshake failed: OpenSSL error: '" + error + '\''); + } + + // There was an internal error -- shutdown + shutdown(); + throw new SSLException(error); + } + } + } + + private static long memoryAddress(ByteBuf buf) { + if (buf.hasMemoryAddress()) { + return buf.memoryAddress(); + } else { + return Buffer.address(buf.nioBuffer()); + } + } + private SSLEngineResult.Status getEngineStatus() { return engineClosed? CLOSED : OK; } @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; } @@ -798,7 +925,12 @@ public final class OpenSslEngine extends SSLEngine { // Check to see if we have finished handshaking if (SSL.isInInit(ssl) == 0) { handshakeFinished = true; - cipher = SSL.getCipherForSSL(ssl); + String c = SSL.getCipherForSSL(ssl); + if (c != null) { + // OpenSSL returns the ciphers seperated by '-' but the JDK SSLEngine does by '_', so replace '-' + // with '_' to match the behaviour. + cipher = CIPHER_REPLACE_PATTERN.matcher(c).replaceAll("_"); + } String applicationProtocol = SSL.getNextProtoNegotiated(ssl); if (applicationProtocol == null) { applicationProtocol = fallbackApplicationProtocol; @@ -808,6 +940,10 @@ public final class OpenSslEngine extends SSLEngine { } else { this.applicationProtocol = null; } + if (verify && managers != null) { + peerCerts = initPeerCertChain(); + verifyCertificates(managers, peerCerts, cipher); + } return FINISHED; } @@ -832,14 +968,14 @@ public final class OpenSslEngine extends SSLEngine { @Override public void setUseClientMode(boolean clientMode) { - if (clientMode) { + if (clientMode != this.clientMode) { throw new UnsupportedOperationException(); } } @Override public boolean getUseClientMode() { - return false; + return clientMode; } @Override @@ -878,6 +1014,31 @@ 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 7631fc8bb6..446829f065 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -16,17 +16,12 @@ package io.netty.handler.ssl; import io.netty.buffer.ByteBufAllocator; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; -import org.apache.tomcat.jni.Pool; import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import java.io.File; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static io.netty.util.internal.ObjectUtil.*; @@ -34,42 +29,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 SslContext { - - private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class); - private static final List DEFAULT_CIPHERS; - - static { - List ciphers = new ArrayList(); - // XXX: Make sure to sync this list with JdkSslEngineFactory. - Collections.addAll( - ciphers, - "ECDHE-RSA-AES128-GCM-SHA256", - "ECDHE-RSA-AES128-SHA", - "ECDHE-RSA-AES256-SHA", - "AES128-GCM-SHA256", - "AES128-SHA", - "AES256-SHA", - "DES-CBC3-SHA", - "RC4-SHA"); - DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers); - - if (logger.isDebugEnabled()) { - logger.debug("Default cipher suite (OpenSSL): " + ciphers); - } - } - - private final long aprPool; - - private final List ciphers = new ArrayList(); - private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers); - private final long sessionCacheSize; - private final long sessionTimeout; - private final OpenSslApplicationProtocolNegotiator apn; - - /** The OpenSSL SSL_CTX object */ - private final long ctx; - private final OpenSslSessionStats stats; +public final class OpenSslServerContext extends OpenSslContext { /** * Creates a new instance. @@ -163,6 +123,7 @@ public final class OpenSslServerContext extends SslContext { Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, long sessionTimeout) throws SSLException { + super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER); OpenSsl.ensureAvailability(); checkNotNull(certChainFile, "certChainFile"); @@ -170,65 +131,28 @@ public final class OpenSslServerContext extends SslContext { throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile); } checkNotNull(keyFile, "keyFile"); - this.apn = checkNotNull(apn, "apn"); if (!keyFile.isFile()) { throw new IllegalArgumentException("keyPath is not a file: " + keyFile); } - if (ciphers == null) { - ciphers = DEFAULT_CIPHERS; - } - if (keyPassword == null) { keyPassword = ""; } - for (String c: ciphers) { - if (c == null) { - break; - } - this.ciphers.add(c); - } - - // Allocate a new APR pool. - aprPool = Pool.create(0); - // Create a new SSL_CTX and configure it. boolean success = false; try { - synchronized (OpenSslServerContext.class) { - try { - ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); - } catch (Exception e) { - throw new SSLException("failed to create an SSL_CTX", e); - } - - SSLContext.setOptions(ctx, SSL.SSL_OP_ALL); - SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2); - SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3); - SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); - SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE); - SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE); - SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - - /* List the ciphers that the client is permitted to negotiate. */ - try { - // Convert the cipher list into a colon-separated string. - StringBuilder cipherBuf = new StringBuilder(); - for (String c: this.ciphers) { - cipherBuf.append(c); - cipherBuf.append(':'); - } - cipherBuf.setLength(cipherBuf.length() - 1); - - SSLContext.setCipherSuite(ctx, cipherBuf.toString()); - } catch (SSLException e) { - throw e; - } catch (Exception e) { - throw new SSLException("failed to set cipher suite: " + this.ciphers, e); - } - + synchronized (OpenSslContext.class) { /* Set certificate verification policy. */ - SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10); + SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); + + /* Load the certificate chain. We must skip the first cert when server mode */ + if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) { + String error = SSL.getLastError(); + if (OpenSsl.isError(error)) { + throw new SSLException( + "failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')'); + } + } /* Load the certificate file and private key. */ try { @@ -242,51 +166,6 @@ public final class OpenSslServerContext extends SslContext { } catch (Exception e) { throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e); } - - /* Load the certificate chain. We must skip the first cert since it was loaded above. */ - if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) { - String error = SSL.getLastError(); - if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { - throw new SSLException( - "failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')'); - } - } - - /* Set next protocols for next protocol negotiation extension, if specified */ - List protocols = apn.protocols(); - if (!protocols.isEmpty()) { - // Convert the protocol list into a comma-separated string. - StringBuilder nextProtocolBuf = new StringBuilder(); - for (int i = 0; i < protocols.size(); ++i) { - nextProtocolBuf.append(protocols.get(i)); - nextProtocolBuf.append(','); - } - nextProtocolBuf.setLength(nextProtocolBuf.length() - 1); - - SSLContext.setNextProtos(ctx, nextProtocolBuf.toString()); - } - - /* Set session cache size, if specified */ - if (sessionCacheSize > 0) { - this.sessionCacheSize = sessionCacheSize; - SSLContext.setSessionCacheSize(ctx, sessionCacheSize); - } else { - // Get the default session cache size using SSLContext.setSessionCacheSize() - this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480); - // Revert the session cache size to the default value. - SSLContext.setSessionCacheSize(ctx, sessionCacheSize); - } - - /* Set session timeout, if specified */ - if (sessionTimeout > 0) { - this.sessionTimeout = sessionTimeout; - SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); - } else { - // Get the default session timeout using SSLContext.setSessionCacheTimeout() - this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300); - // Revert the session timeout to the default value. - SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); - } } success = true; } finally { @@ -294,47 +173,6 @@ public final class OpenSslServerContext extends SslContext { destroyPools(); } } - - stats = new OpenSslSessionStats(ctx); - } - - @Override - public boolean isClient() { - return false; - } - - @Override - public List cipherSuites() { - return unmodifiableCiphers; - } - - @Override - public long sessionCacheSize() { - return sessionCacheSize; - } - - @Override - public long sessionTimeout() { - return sessionTimeout; - } - - @Override - public ApplicationProtocolNegotiator applicationProtocolNegotiator() { - return apn; - } - - /** - * Returns the {@code SSL_CTX} object of this context. - */ - public long context() { - return ctx; - } - - /** - * Returns the stats of this context. - */ - public OpenSslSessionStats stats() { - return stats; } /** @@ -342,79 +180,11 @@ public final class OpenSslServerContext extends SslContext { */ @Override public SSLEngine newEngine(ByteBufAllocator alloc) { - List protocols = apn.protocols(); - if (protocols.isEmpty()) { - return new OpenSslEngine(ctx, alloc, null); + List protos = applicationProtocolNegotiator().protocols(); + if (protos.isEmpty()) { + return new OpenSslEngine(ctx, alloc, null, isClient(), null); } else { - return new OpenSslEngine(ctx, alloc, protocols.get(protocols.size() - 1)); - } - } - - @Override - public SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { - throw new UnsupportedOperationException(); - } - - /** - * Sets the SSL session ticket keys of this context. - */ - public void setTicketKeys(byte[] keys) { - if (keys == null) { - throw new NullPointerException("keys"); - } - SSLContext.setSessionTicketKeys(ctx, keys); - } - - @Override - @SuppressWarnings("FinalizeDeclaration") - protected void finalize() throws Throwable { - super.finalize(); - synchronized (OpenSslServerContext.class) { - if (ctx != 0) { - SSLContext.free(ctx); - } - } - - destroyPools(); - } - - private void destroyPools() { - if (aprPool != 0) { - Pool.destroy(aprPool); - } - } - - /** - * Translate a {@link ApplicationProtocolConfig} object to a - * {@link OpenSslApplicationProtocolNegotiator} object. - * @param config The configuration which defines the translation - * @param isServer {@code true} if a server {@code false} otherwise. - * @return The results of the translation - */ - private static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, - boolean isServer) { - if (config == null) { - return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; - } - - switch(config.protocol()) { - case NONE: - return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; - case NPN: - if (isServer) { - switch(config.selectedListenerFailureBehavior()) { - case CHOOSE_MY_LAST_PROTOCOL: - return new OpenSslNpnApplicationProtocolNegotiator(config.supportedProtocols()); - default: - throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ") - .append(config.selectedListenerFailureBehavior()).append(" behavior").toString()); - } - } else { - throw new UnsupportedOperationException("OpenSSL provider does not support client mode"); - } - default: - throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ") - .append(config.protocol()).append(" protocol").toString()); + return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null); } } } 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 843e2c0fc4..5bb0f8541b 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -16,7 +16,9 @@ package io.netty.handler.ssl; +import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufInputStream; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; @@ -30,7 +32,15 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.x500.X500Principal; import java.io.File; +import java.io.IOException; +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.util.List; /** @@ -56,6 +66,15 @@ import java.util.List; * */ public abstract class SslContext { + static final CertificateFactory X509_CERT_FACTORY; + static { + try { + X509_CERT_FACTORY = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new IllegalStateException("unable to instance X.509 CertificateFactory", e); + } + } + /** * Returns the default server-side implementation provider currently in use. * @@ -509,7 +528,6 @@ public abstract class SslContext { File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, Iterable nextProtocols, long sessionCacheSize, long sessionTimeout) throws SSLException { - return newClientContext( provider, certChainFile, trustManagerFactory, null, null, null, null, ciphers, IdentityCipherSuiteFilter.INSTANCE, @@ -593,9 +611,20 @@ public abstract class SslContext { if (provider != null && provider != SslProvider.JDK) { throw new SSLException("client context unsupported for: " + provider); } - - return new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, - keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); + if (provider == null) { + provider = SslProvider.JDK; + } + switch (provider) { + case JDK: + new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, + keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); + case OPENSSL: + return new OpenSslClientContext( + trustCertChainFile, trustManagerFactory, + ciphers, apn, sessionCacheSize, sessionTimeout); + } + // Should never happen!! + throw new Error(); } static ApplicationProtocolConfig toApplicationProtocolConfig(Iterable nextProtocols) { @@ -693,4 +722,24 @@ public abstract class SslContext { private static SslHandler newHandler(SSLEngine engine) { 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(); + } + } + trustManagerFactory.init(ks); + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java index 04799758fd..d78b468677 100644 --- a/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java +++ b/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java @@ -28,10 +28,10 @@ import java.security.KeyStore; import java.security.cert.X509Certificate; /** - * An insecure {@link javax.net.ssl.TrustManagerFactory} that trusts all X.509 certificates without any verification. + * An insecure {@link TrustManagerFactory} that trusts all X.509 certificates without any verification. *

* NOTE: - * Never use this {@link javax.net.ssl.TrustManagerFactory} in production. + * Never use this {@link TrustManagerFactory} in production. * It is purely for testing purposes, and thus it is very insecure. *

*/ diff --git a/pom.xml b/pom.xml index d8022cd0b6..42b2c82866 100644 --- a/pom.xml +++ b/pom.xml @@ -567,7 +567,7 @@ ${project.groupId} netty-tcnative - 1.1.30.Fork2 + 1.1.30.Fork3-SNAPSHOT ${os.detected.classifier} compile true diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java index b4d529d6e3..97423e80b9 100644 --- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java +++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java @@ -30,6 +30,7 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.JdkSslClientContext; import io.netty.handler.ssl.JdkSslServerContext; import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslClientContext; import io.netty.handler.ssl.OpenSslServerContext; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.util.SelfSignedCertificate; @@ -84,9 +85,7 @@ public class SocketSslGreetingTest extends AbstractSocketTest { boolean hasOpenSsl = OpenSsl.isAvailable(); if (hasOpenSsl) { serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE)); - - // TODO: Client mode is not supported yet. - // clientContexts.add(new OpenSslContext(CERT_FILE)); + clientContexts.add(new OpenSslClientContext(CERT_FILE)); } else { logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); } diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java index c4eae5d0a3..fdbeb6a57a 100644 --- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java +++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java @@ -32,6 +32,7 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.JdkSslClientContext; import io.netty.handler.ssl.JdkSslServerContext; import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslClientContext; import io.netty.handler.ssl.OpenSslServerContext; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; @@ -91,9 +92,7 @@ public class SocketStartTlsTest extends AbstractSocketTest { boolean hasOpenSsl = OpenSsl.isAvailable(); if (hasOpenSsl) { serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE)); - - // TODO: Client mode is not supported yet. - // clientContexts.add(new OpenSslContext(CERT_FILE)); + clientContexts.add(new OpenSslClientContext(CERT_FILE)); } else { logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); }