diff --git a/common/src/main/java/io/netty/util/internal/EmptyArrays.java b/common/src/main/java/io/netty/util/internal/EmptyArrays.java index 52c9f24bee..f838f6476d 100644 --- a/common/src/main/java/io/netty/util/internal/EmptyArrays.java +++ b/common/src/main/java/io/netty/util/internal/EmptyArrays.java @@ -17,6 +17,7 @@ package io.netty.util.internal; import java.nio.ByteBuffer; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; public final class EmptyArrays { @@ -34,7 +35,10 @@ public final class EmptyArrays { public static final String[] EMPTY_STRINGS = new String[0]; public static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; public static final ByteBuffer[] EMPTY_BYTE_BUFFERS = new ByteBuffer[0]; + public static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0]; public static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; + public static final javax.security.cert.X509Certificate[] EMPTY_JAVAX_X509_CERTIFICATES = + new javax.security.cert.X509Certificate[0]; private EmptyArrays() { } } diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java index 2c94f2e3fa..ca69b8f707 100644 --- a/common/src/main/java/io/netty/util/internal/StringUtil.java +++ b/common/src/main/java/io/netty/util/internal/StringUtil.java @@ -33,9 +33,9 @@ public final class StringUtil { public static final char COMMA = ','; public static final char LINE_FEED = '\n'; public static final char CARRIAGE_RETURN = '\r'; + public static final String EMPTY_STRING = ""; private static final String[] BYTE2HEX_PAD = new String[256]; private static final String[] BYTE2HEX_NOPAD = new String[256]; - private static final String EMPTY_STRING = ""; /** * 2 - Quote character at beginning and end. * 5 - Extra allowance for anticipated escape characters that may be added. diff --git a/example/src/main/java/io/netty/example/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java index 65dfb31d8e..18f1402b38 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java @@ -31,6 +31,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; @@ -61,7 +62,8 @@ public final class Http2Client { // Configure SSL. final SslContext sslCtx; if (SSL) { - sslCtx = SslContext.newClientContext(SslProvider.JDK, + SslProvider provider = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK; + sslCtx = SslContext.newClientContext(provider, null, InsecureTrustManagerFactory.INSTANCE, Http2SecurityUtil.CIPHERS, /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification @@ -69,8 +71,8 @@ public final class Http2Client { SupportedCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( Protocol.ALPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, + SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, + SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()), 0, 0); diff --git a/example/src/main/java/io/netty/example/http2/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/server/Http2Server.java index 85cef929c6..c98aec7f22 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2Server.java @@ -30,6 +30,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; @@ -42,14 +43,16 @@ import io.netty.handler.ssl.util.SelfSignedCertificate; public final class Http2Server { static final boolean SSL = System.getProperty("ssl") != null; + static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { + SslProvider provider = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK; SelfSignedCertificate ssc = new SelfSignedCertificate(); - sslCtx = SslContext.newServerContext(SslProvider.JDK, + sslCtx = SslContext.newServerContext(provider, ssc.certificate(), ssc.privateKey(), null, Http2SecurityUtil.CIPHERS, /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification @@ -57,8 +60,8 @@ public final class Http2Server { SupportedCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( Protocol.ALPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, + SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, + SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()), 0, 0); diff --git a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java index 1ce0da16d8..4dc6ae4fdb 100644 --- a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java +++ b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java @@ -53,15 +53,15 @@ public final class MemcacheClient { // Configure SSL. final SslContext sslCtx; if (SSL) { - sslCtx = SslContext.newClientContext(SslProvider.JDK, + sslCtx = SslContext.newClientContext(null, null, InsecureTrustManagerFactory.INSTANCE, Http2SecurityUtil.CIPHERS, /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification * Please refer to the HTTP/2 specification for cipher requirements. */ SupportedCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( Protocol.ALPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, + SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, + SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()), 0, 0); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java index c33d4ebfce..74bbb2dabb 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java @@ -61,8 +61,8 @@ public final class SpdyClient { null, InsecureTrustManagerFactory.INSTANCE, null, IdentityCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( Protocol.NPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, + SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, + SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()), 0, 0); diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java index 40ce2a9cc3..ff1a6b7d1e 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java @@ -61,8 +61,8 @@ public final class SpdyServer { ssc.certificate(), ssc.privateKey(), null, null, IdentityCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( Protocol.NPN, - SelectorFailureBehavior.FATAL_ALERT, - SelectedListenerFailureBehavior.FATAL_ALERT, + SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, + SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL, SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()), 0, 0); diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolConfig.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolConfig.java index d5562bada2..e6d8e81857 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolConfig.java +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolConfig.java @@ -79,6 +79,9 @@ public final class ApplicationProtocolConfig { if (protocol == Protocol.NONE) { throw new IllegalArgumentException("protocol (" + Protocol.NONE + ") must not be " + Protocol.NONE + '.'); } + if (supportedProtocols.isEmpty()) { + throw new IllegalArgumentException("supportedProtocols must be not empty"); + } } /** 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 3b23509496..49e2da42ed 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java @@ -113,6 +113,14 @@ public final class OpenSsl { return UNAVAILABILITY_CAUSE == null; } + /** + * Returns {@code true} if the used version of openssl supports + * ALPN. + */ + public static boolean isAlpnSupported() { + return isAvailable() && SSL.version() >= 0x10002000L; + } + /** * Ensure that {@code netty-tcnative} and * its OpenSSL support are available. diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslApplicationProtocolNegotiator.java index 7cd2e1126e..aa5b419dd3 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslApplicationProtocolNegotiator.java @@ -19,10 +19,19 @@ package io.netty.handler.ssl; * OpenSSL version of {@link ApplicationProtocolNegotiator}. */ public interface OpenSslApplicationProtocolNegotiator extends ApplicationProtocolNegotiator { - // The current need for this interface is primarily for consistency with the JDK provider - // How the OpenSsl provider will provide extensibility to control the application selection - // and notification algorithms is not yet known (JNI, pure java, tcnative hooks, etc...). - // OpenSSL itself is currently not in compliance with the specification for the 2 supported - // protocols (ALPN, NPN) with respect to allowing the handshakes to fail during the application - // protocol negotiation process. Issue https://github.com/openssl/openssl/issues/188 has been created for this. + + /** + * Returns the {@link ApplicationProtocolConfig.Protocol} which should be used. + */ + ApplicationProtocolConfig.Protocol protocol(); + + /** + * Get the desired behavior for the peer who selects the application protocol. + */ + ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior(); + + /** + * Get the desired behavior for the peer who is notified of the selected protocol. + */ + ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior(); } 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 e3582222a4..d88e289b3e 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java @@ -45,7 +45,7 @@ public final class OpenSslClientContext extends OpenSslContext { * Creates a new instance. */ public OpenSslClientContext() throws SSLException { - this(null, null, null, null, 0, 0); + this(null, null, null, IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); } /** @@ -79,10 +79,13 @@ public final class OpenSslClientContext extends OpenSslContext { * {@code null} to use the default. */ public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { - this(certChainFile, trustManagerFactory, null, null, 0, 0); + this(certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); } /** + * @deprecated use {@link #OpenSslClientContext(File, TrustManagerFactory, Iterable, + * CipherSuiteFilter, ApplicationProtocolConfig, long, long)} + * * Creates a new instance. * * @param certChainFile an X.509 certificate chain file in PEM format @@ -97,10 +100,35 @@ public final class OpenSslClientContext extends OpenSslContext { * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. * {@code 0} to use the default value. */ + @Deprecated 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); + this(certChainFile, trustManagerFactory, ciphers, IdentityCipherSuiteFilter.INSTANCE, + apn, sessionCacheSize, sessionTimeout); + } + + /** + * 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 cipherFilter a filter to apply over the supplied list of ciphers + * @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, + CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, + long sessionCacheSize, long sessionTimeout) + throws SSLException { + super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT); boolean success = false; try { if (certChainFile != null && !certChainFile.isFile()) { 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 4080b01f8e..06f9d510cc 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java @@ -31,12 +31,15 @@ import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static io.netty.util.internal.ObjectUtil.checkNotNull; +import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; +import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; public abstract class OpenSslContext extends SslContext { @@ -48,10 +51,9 @@ public abstract class OpenSslContext extends SslContext { protected static final int VERIFY_DEPTH = 10; private final long aprPool; - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "FieldMayBeFinal" }) private volatile int aprPoolDestroyed; - private final List ciphers = new ArrayList(); - private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers); + private final List unmodifiableCiphers; private final long sessionCacheSize; private final long sessionTimeout; private final OpenSslApplicationProtocolNegotiator apn; @@ -59,6 +61,29 @@ public abstract class OpenSslContext extends SslContext { protected final long ctx; private final int mode; + static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR = + new OpenSslApplicationProtocolNegotiator() { + @Override + public ApplicationProtocolConfig.Protocol protocol() { + return ApplicationProtocolConfig.Protocol.NONE; + } + + @Override + public List protocols() { + return Collections.emptyList(); + } + + @Override + public SelectorFailureBehavior selectorFailureBehavior() { + return SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL; + } + + @Override + public SelectedListenerFailureBehavior selectedListenerFailureBehavior() { + return SelectedListenerFailureBehavior.ACCEPT; + } + }; + static { List ciphers = new ArrayList(); // XXX: Make sure to sync this list with JdkSslEngineFactory. @@ -86,12 +111,13 @@ public abstract class OpenSslContext extends SslContext { 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, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg, + long sessionCacheSize, long sessionTimeout, int mode) throws SSLException { + this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode); } - OpenSslContext(Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, + OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, + OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, long sessionTimeout, int mode) throws SSLException { OpenSsl.ensureAvailability(); @@ -100,22 +126,26 @@ public abstract class OpenSslContext extends SslContext { } this.mode = mode; + final List convertedCiphers; if (ciphers == null) { - ciphers = DEFAULT_CIPHERS; + convertedCiphers = null; + } else { + convertedCiphers = new ArrayList(); + for (String c: ciphers) { + if (c == null) { + break; + } + + String converted = CipherSuiteConverter.toOpenSsl(c); + if (converted != null) { + c = converted; + } + convertedCiphers.add(c); + } } - for (String c: ciphers) { - if (c == null) { - break; - } - - String converted = CipherSuiteConverter.toOpenSsl(c); - if (converted != null) { - c = converted; - } - - this.ciphers.add(c); - } + unmodifiableCiphers = Arrays.asList(checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites( + convertedCiphers, DEFAULT_CIPHERS, OpenSsl.availableCipherSuites())); this.apn = checkNotNull(apn, "apn"); @@ -142,25 +172,33 @@ public abstract class OpenSslContext extends SslContext { /* List the ciphers that are permitted to negotiate. */ try { - SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(this.ciphers)); + SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(unmodifiableCiphers)); } catch (SSLException e) { throw e; } catch (Exception e) { - throw new SSLException("failed to set cipher suite: " + this.ciphers, e); + throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, 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); + String[] protocols = nextProtoList.toArray(new String[nextProtoList.size()]); + int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); - SSLContext.setNextProtos(ctx, nextProtocolBuf.toString()); + switch (apn.protocol()) { + case NPN: + SSLContext.setNpnProtos(ctx, protocols, selectorBehavior); + break; + case ALPN: + SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior); + break; + case NPN_AND_ALPN: + SSLContext.setNpnProtos(ctx, protocols, selectorBehavior); + SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior); + break; + default: + throw new Error(); + } } /* Set session cache size, if specified */ @@ -193,6 +231,17 @@ public abstract class OpenSslContext extends SslContext { } } + private static int opensslSelectorFailureBehavior(SelectorFailureBehavior behavior) { + switch (behavior) { + case NO_ADVERTISE: + return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE; + case CHOOSE_MY_LAST_PROTOCOL: + return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL; + default: + throw new Error(); + } + } + @Override public final List cipherSuites() { return unmodifiableCiphers; @@ -228,15 +277,8 @@ public abstract class OpenSslContext extends SslContext { */ @Override public final SSLEngine newEngine(ByteBufAllocator alloc) { - List protos = applicationProtocolNegotiator().protocols(); OpenSslEngineMap engineMap = engineMap(); - final OpenSslEngine engine; - if (protos.isEmpty()) { - engine = new OpenSslEngine(ctx, alloc, null, isClient(), sessionContext(), engineMap); - } else { - engine = new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), - sessionContext(), engineMap); - } + final OpenSslEngine engine = new OpenSslEngine(ctx, alloc, isClient(), sessionContext(), apn, engineMap); engineMap.add(engine); return engine; } @@ -312,35 +354,41 @@ public abstract class OpenSslContext extends SslContext { * 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) { + static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) { if (config == null) { - return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; + return NONE_PROTOCOL_NEGOTIATOR; } 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"); + case NONE: + return NONE_PROTOCOL_NEGOTIATOR; + case ALPN: + case NPN: + case NPN_AND_ALPN: + switch (config.selectedListenerFailureBehavior()) { + case CHOOSE_MY_LAST_PROTOCOL: + case ACCEPT: + switch (config.selectorFailureBehavior()) { + case CHOOSE_MY_LAST_PROTOCOL: + case NO_ADVERTISE: + return new OpenSslDefaultApplicationProtocolNegotiator( + config); + default: + throw new UnsupportedOperationException( + new StringBuilder("OpenSSL provider does not support ") + .append(config.selectorFailureBehavior()) + .append(" behavior").toString()); } default: - throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ") - .append(config.protocol()).append(" protocol").toString()); + throw new UnsupportedOperationException( + new StringBuilder("OpenSSL provider does not support ") + .append(config.selectedListenerFailureBehavior()) + .append(" behavior").toString()); + } + default: + throw new Error(); } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslDefaultApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslDefaultApplicationProtocolNegotiator.java index 65191d91c0..fd7d44a9ef 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslDefaultApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslDefaultApplicationProtocolNegotiator.java @@ -15,21 +15,36 @@ */ package io.netty.handler.ssl; -import java.util.Collections; import java.util.List; -/** - * Provides no {@link ApplicationProtocolNegotiator} functionality for OpenSSL. - */ -final class OpenSslDefaultApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator { - static final OpenSslDefaultApplicationProtocolNegotiator INSTANCE = - new OpenSslDefaultApplicationProtocolNegotiator(); +import static io.netty.util.internal.ObjectUtil.checkNotNull; - private OpenSslDefaultApplicationProtocolNegotiator() { +/** + * OpenSSL {@link ApplicationProtocolNegotiator} for ALPN and NPN. + */ +public final class OpenSslDefaultApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator { + private final ApplicationProtocolConfig config; + public OpenSslDefaultApplicationProtocolNegotiator(ApplicationProtocolConfig config) { + this.config = checkNotNull(config, "config"); } @Override public List protocols() { - return Collections.emptyList(); + return config.supportedProtocols(); + } + + @Override + public ApplicationProtocolConfig.Protocol protocol() { + return config.protocol(); + } + + @Override + public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() { + return config.selectorFailureBehavior(); + } + + @Override + public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() { + return config.selectedListenerFailureBehavior(); } } 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 8e1066fa7f..6067dd7eb7 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -19,8 +19,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.internal.EmptyArrays; -import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.apache.tomcat.jni.Buffer; @@ -49,6 +49,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; +import static io.netty.util.internal.ObjectUtil.checkNotNull; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; import static javax.net.ssl.SSLEngineResult.Status.*; @@ -60,7 +62,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 Certificate[] EMPTY_CERTIFICATES = EmptyArrays.EMPTY_CERTIFICATES; + private static final X509Certificate[] EMPTY_X509_CERTIFICATES = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; + 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"); @@ -145,10 +149,9 @@ public final class OpenSslEngine extends SSLEngine { private final boolean clientMode; private final ByteBufAllocator alloc; - private final String fallbackApplicationProtocol; private final OpenSslSessionContext sessionContext; private final OpenSslEngineMap engineMap; - + private final OpenSslApplicationProtocolNegotiator apn; private final SSLSession session = new OpenSslSession(); /** @@ -158,8 +161,9 @@ public final class OpenSslEngine extends SSLEngine { * @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, OpenSslEngineMap.EMPTY); + public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, + @SuppressWarnings("unused") String fallbackApplicationProtocol) { + this(sslCtx, alloc, false, null, OpenSslContext.NONE_PROTOCOL_NEGOTIATOR, OpenSslEngineMap.EMPTY); } /** @@ -170,17 +174,18 @@ public final class OpenSslEngine extends SSLEngine { * @param clientMode {@code true} if this is used for clients, {@code false} otherwise * @param sessionContext the {@link OpenSslSessionContext} this {@link SSLEngine} belongs to. */ - OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, - boolean clientMode, OpenSslSessionContext sessionContext, OpenSslEngineMap engineMap) { + OpenSslEngine(long sslCtx, ByteBufAllocator alloc, + boolean clientMode, OpenSslSessionContext sessionContext, + OpenSslApplicationProtocolNegotiator apn, OpenSslEngineMap engineMap) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { throw new NullPointerException("sslCtx"); } - this.alloc = ObjectUtil.checkNotNull(alloc, "alloc"); + this.alloc = checkNotNull(alloc, "alloc"); + this.apn = checkNotNull(apn, "apn"); ssl = SSL.newSSL(sslCtx, !clientMode); networkBIO = SSL.makeNetworkBIO(ssl); - this.fallbackApplicationProtocol = fallbackApplicationProtocol; this.clientMode = clientMode; this.sessionContext = sessionContext; this.engineMap = engineMap; @@ -396,7 +401,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 = getHandshakeStatus(); + SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus0(); if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) { return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0); @@ -428,7 +433,7 @@ public final class OpenSslEngine extends SSLEngine { shutdown(); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), 0, bytesProduced); } // There was no pending data in the network BIO -- encrypt any application data @@ -455,7 +460,7 @@ public final class OpenSslEngine extends SSLEngine { int capacity = dst.remaining(); if (capacity < pendingNet) { return new SSLEngineResult( - BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); + BUFFER_OVERFLOW, handshakeStatus0(), bytesConsumed, bytesProduced); } // Write the pending data from the network BIO into the dst buffer @@ -465,12 +470,12 @@ public final class OpenSslEngine extends SSLEngine { throw new SSLException(e); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced); } } } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced); } public synchronized SSLEngineResult unwrap( @@ -520,7 +525,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 = handshakeStatus0(); if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) { return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0); } @@ -604,7 +609,7 @@ public final class OpenSslEngine extends SSLEngine { if (pendingApp > 0) { // 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, handshakeStatus0(), bytesConsumed, 0); } // Write decrypted data to dsts buffers @@ -647,7 +652,7 @@ public final class OpenSslEngine extends SSLEngine { closeInbound(); } - return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced); } public SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException { @@ -723,7 +728,14 @@ public final class OpenSslEngine extends SSLEngine { @Override public String[] getEnabledCipherSuites() { - String[] enabled = SSL.getCiphers(ssl); + final String[] enabled; + synchronized (this) { + if (destroyed == 0) { + enabled = SSL.getCiphers(ssl); + } else { + return EmptyArrays.EMPTY_STRINGS; + } + } if (enabled == null) { return EmptyArrays.EMPTY_STRINGS; } else { @@ -739,7 +751,7 @@ public final class OpenSslEngine extends SSLEngine { @Override public void setEnabledCipherSuites(String[] cipherSuites) { - ObjectUtil.checkNotNull(cipherSuites, "cipherSuites"); + checkNotNull(cipherSuites, "cipherSuites"); final StringBuilder buf = new StringBuilder(); for (String c: cipherSuites) { @@ -766,10 +778,17 @@ public final class OpenSslEngine extends SSLEngine { buf.setLength(buf.length() - 1); final String cipherSuiteSpec = buf.toString(); - try { - SSL.setCipherSuites(ssl, cipherSuiteSpec); - } catch (Exception e) { - throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e); + + synchronized (this) { + if (destroyed == 0) { + try { + SSL.setCipherSuites(ssl, cipherSuiteSpec); + } catch (Exception e) { + throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e); + } + } else { + throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec); + } } } @@ -783,7 +802,15 @@ public final class OpenSslEngine extends SSLEngine { List enabled = new ArrayList(); // Seems like there is no way to explict disable SSLv2Hello in openssl so it is always enabled enabled.add(PROTOCOL_SSL_V2_HELLO); - int opts = SSL.getOptions(ssl); + + int opts; + synchronized (this) { + if (destroyed == 0) { + opts = SSL.getOptions(ssl); + } else { + return enabled.toArray(new String[1]); + } + } if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) { enabled.add(PROTOCOL_TLS_V1); } @@ -799,12 +826,7 @@ public final class OpenSslEngine extends SSLEngine { if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) { enabled.add(PROTOCOL_SSL_V3); } - int size = enabled.size(); - if (size == 0) { - return EmptyArrays.EMPTY_STRINGS; - } else { - return enabled.toArray(new String[size]); - } + return enabled.toArray(new String[enabled.size()]); } @Override @@ -834,23 +856,29 @@ public final class OpenSslEngine extends SSLEngine { tlsv1_2 = true; } } - // Enable all and then disable what we not want - SSL.setOptions(ssl, SSL.SSL_OP_ALL); + synchronized (this) { + if (destroyed == 0) { + // Enable all and then disable what we not want + SSL.setOptions(ssl, SSL.SSL_OP_ALL); - if (!sslv2) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv2); - } - if (!sslv3) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv3); - } - if (!tlsv1) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1); - } - if (!tlsv1_1) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_1); - } - if (!tlsv1_2) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2); + if (!sslv2) { + SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv2); + } + if (!sslv3) { + SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv3); + } + if (!tlsv1) { + SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1); + } + if (!tlsv1_1) { + SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_1); + } + if (!tlsv1_2) { + SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2); + } + } else { + throw new IllegalStateException("failed to enable protocols: " + protocols); + } } } @@ -915,7 +943,7 @@ public final class OpenSslEngine extends SSLEngine { } else { // if SSL_do_handshake returns > 0 it means the handshake was finished. This means we can update // handshakeFinished directly and so eliminate uncessary calls to SSL.isInInit(...) - handshakeFinished = true; + handshakeFinished(); } } @@ -927,10 +955,76 @@ public final class OpenSslEngine extends SSLEngine { } } + private void handshakeFinished() throws SSLException { + SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior(); + List protocols = apn.protocols(); + String applicationProtocol; + switch (apn.protocol()) { + case NONE: + break; + // We always need to check for applicationProtocol == null as the remote peer may not support + // the TLS extension or may have returned an empty selection. + case ALPN: + applicationProtocol = SSL.getAlpnSelected(ssl); + if (applicationProtocol != null) { + this.applicationProtocol = selectApplicationProtocol(protocols, behavior, applicationProtocol); + } + break; + case NPN: + applicationProtocol = SSL.getNextProtoNegotiated(ssl); + if (applicationProtocol != null) { + this.applicationProtocol = selectApplicationProtocol(protocols, behavior, applicationProtocol); + } + break; + case NPN_AND_ALPN: + applicationProtocol = SSL.getAlpnSelected(ssl); + if (applicationProtocol == null) { + applicationProtocol = SSL.getNextProtoNegotiated(ssl); + } + if (applicationProtocol != null) { + this.applicationProtocol = selectApplicationProtocol(protocols, behavior, applicationProtocol); + } + break; + default: + throw new Error(); + } + handshakeFinished = true; + } + + private static String selectApplicationProtocol(List protocols, + SelectedListenerFailureBehavior behavior, + String applicationProtocol) throws SSLException { + applicationProtocol = applicationProtocol.replace(':', '_'); + if (behavior == SelectedListenerFailureBehavior.ACCEPT) { + return applicationProtocol; + } else { + int size = protocols.size(); + assert size > 0; + if (protocols.contains(applicationProtocol)) { + return applicationProtocol; + } else { + if (behavior == SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) { + return protocols.get(size - 1); + } else { + throw new SSLException("Unknown protocol " + applicationProtocol); + } + } + } + } + private SSLEngineResult.Status getEngineStatus() { return engineClosed? CLOSED : OK; } + private SSLEngineResult.HandshakeStatus handshakeStatus0() throws SSLException { + SSLEngineResult.HandshakeStatus status = getHandshakeStatus(); + if (status == FINISHED) { + handshakeFinished(); + } + + return status; + } + @Override public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { if (accepted == 0 || destroyed != 0) { @@ -947,7 +1041,6 @@ public final class OpenSslEngine extends SSLEngine { // No pending data to be sent to the peer // Check to see if we have finished handshaking if (SSL.isInInit(ssl) == 0) { - handshakeFinished = true; return FINISHED; } @@ -1088,8 +1181,15 @@ public final class OpenSslEngine extends SSLEngine { @Override public byte[] getId() { + final byte[] id; + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + id = SSL.getSessionId(ssl); + } else { + id = EmptyArrays.EMPTY_BYTES; + } + } // We don't cache that to keep memory usage to a minimum. - byte[] id = SSL.getSessionId(ssl); if (id == null) { // The id should never be null, if it was null then the SESSION itself was not valid. throw new IllegalStateException("SSL session ID not available"); @@ -1104,8 +1204,13 @@ public final class OpenSslEngine extends SSLEngine { @Override public long getCreationTime() { - // We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds. - return SSL.getTime(ssl) * 1000L; + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + // We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds. + return SSL.getTime(ssl) * 1000L; + } + return 0; + } } @Override @@ -1126,9 +1231,12 @@ public final class OpenSslEngine extends SSLEngine { @Override public void putValue(String name, Object value) { - ObjectUtil.checkNotNull(name, "name"); - ObjectUtil.checkNotNull(value, "value"); - + if (name == null) { + throw new NullPointerException("name"); + } + if (value == null) { + throw new NullPointerException("value"); + } Map values = this.values; if (values == null) { // Use size of 2 to keep the memory overhead small @@ -1143,8 +1251,9 @@ public final class OpenSslEngine extends SSLEngine { @Override public Object getValue(String name) { - ObjectUtil.checkNotNull(name, "name"); - + if (name == null) { + throw new NullPointerException("name"); + } if (values == null) { return null; } @@ -1153,8 +1262,9 @@ public final class OpenSslEngine extends SSLEngine { @Override public void removeValue(String name) { - ObjectUtil.checkNotNull(name, "name"); - + if (name == null) { + throw new NullPointerException("name"); + } Map values = this.values; if (values == null) { return; @@ -1183,53 +1293,20 @@ public final class OpenSslEngine extends SSLEngine { // 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"); + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + if (SSL.isInInit(ssl) != 0) { + throw new SSLPeerUnverifiedException("peer not verified"); + } + c = peerCerts = initPeerCertChain(); + } else { + c = peerCerts = EMPTY_CERTIFICATES; + } } - c = peerCerts = initPeerCertChain(); } return c; } - 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"); - } - 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 public Certificate[] getLocalCertificates() { // TODO: Find out how to get these @@ -1241,10 +1318,18 @@ public final class OpenSslEngine extends SSLEngine { // 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"); + final byte[][] chain; + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + if (SSL.isInInit(ssl) != 0) { + throw new SSLPeerUnverifiedException("peer not verified"); + } + chain = SSL.getPeerCertChain(ssl); + } else { + c = x509PeerCerts = EMPTY_X509_CERTIFICATES; + return c; + } } - byte[][] chain = SSL.getPeerCertChain(ssl); if (chain == null) { throw new SSLPeerUnverifiedException("peer not verified"); } @@ -1289,7 +1374,14 @@ public final class OpenSslEngine extends SSLEngine { return INVALID_CIPHER; } if (cipher == null) { - String c = toJavaCipherSuite(SSL.getCipherForSSL(ssl)); + final String c; + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + c = toJavaCipherSuite(SSL.getCipherForSSL(ssl)); + } else { + c = INVALID_CIPHER; + } + } if (c != null) { cipher = c; } @@ -1300,19 +1392,15 @@ public final class OpenSslEngine extends SSLEngine { @Override public String getProtocol() { String applicationProtocol = OpenSslEngine.this.applicationProtocol; - if (applicationProtocol == null) { - applicationProtocol = SSL.getNextProtoNegotiated(ssl); - if (applicationProtocol == null) { - applicationProtocol = fallbackApplicationProtocol; - } - if (applicationProtocol != null) { - OpenSslEngine.this.applicationProtocol = applicationProtocol.replace(':', '_'); + final String version; + synchronized (OpenSslEngine.this) { + if (destroyed == 0) { + version = SSL.getVersion(ssl); } else { - OpenSslEngine.this.applicationProtocol = applicationProtocol = ""; + return StringUtil.EMPTY_STRING; } } - String version = SSL.getVersion(ssl); - if (applicationProtocol.isEmpty()) { + if (applicationProtocol == null || applicationProtocol.isEmpty()) { return version; } else { return version + ':' + applicationProtocol; @@ -1338,5 +1426,44 @@ public final class OpenSslEngine extends SSLEngine { public int getApplicationBufferSize() { return MAX_PLAINTEXT_LENGTH; } + + private Certificate[] initPeerCertChain() throws SSLPeerUnverifiedException { + byte[][] chain = SSL.getPeerCertChain(ssl); + final 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"); + } + 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; + } } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslNpnApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslNpnApplicationProtocolNegotiator.java index e4b4e8f043..a4bf6853fa 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslNpnApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslNpnApplicationProtocolNegotiator.java @@ -22,7 +22,10 @@ import java.util.List; /** * OpenSSL {@link ApplicationProtocolNegotiator} for NPN. + * + * @deprecated use {@link OpenSslDefaultApplicationProtocolNegotiator} */ +@Deprecated public final class OpenSslNpnApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator { private final List protocols; @@ -34,8 +37,23 @@ public final class OpenSslNpnApplicationProtocolNegotiator implements OpenSslApp this.protocols = checkNotNull(toList(protocols), "protocols"); } + @Override + public ApplicationProtocolConfig.Protocol protocol() { + return ApplicationProtocolConfig.Protocol.NPN; + } + @Override public List protocols() { return protocols; } + + @Override + public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() { + return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL; + } + + @Override + public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() { + return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT; + } } 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 8d73cf1f2a..603d57d6ee 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -64,11 +64,14 @@ public final class OpenSslServerContext extends OpenSslContext { * {@code null} if it's not password-protected. */ public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { - this(certChainFile, keyFile, keyPassword, null, null, - OpenSslDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0); + this(certChainFile, keyFile, keyPassword, null, null, IdentityCipherSuiteFilter.INSTANCE, + NONE_PROTOCOL_NEGOTIATOR, 0, 0); } /** + * @deprecated use {@link #OpenSslServerContext( + * File, File, String, Iterable, CipherSuiteFilter, ApplicationProtocolConfig, long, long)} + * * Creates a new instance. * * @param certChainFile an X.509 certificate chain file in PEM format @@ -83,12 +86,13 @@ public final class OpenSslServerContext extends OpenSslContext { * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. * {@code 0} to use the default value. */ + @Deprecated public OpenSslServerContext( File certChainFile, File keyFile, String keyPassword, Iterable ciphers, ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout) throws SSLException { this(certChainFile, keyFile, keyPassword, null, ciphers, - toNegotiator(apn, false), sessionCacheSize, sessionTimeout); + toNegotiator(apn), sessionCacheSize, sessionTimeout); } /** @@ -105,13 +109,17 @@ public final class OpenSslServerContext extends OpenSslContext { * {@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. + * @deprecated use {@link #OpenSslServerContext( + * File, File, String, TrustManagerFactory, Iterable, + * CipherSuiteFilter, ApplicationProtocolConfig, long, long)} */ + @Deprecated public OpenSslServerContext( File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, Iterable ciphers, ApplicationProtocolConfig config, long sessionCacheSize, long sessionTimeout) throws SSLException { this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers, - toNegotiator(config, true), sessionCacheSize, sessionTimeout); + toNegotiator(config), sessionCacheSize, sessionTimeout); } /** @@ -128,14 +136,89 @@ public final class OpenSslServerContext extends OpenSslContext { * {@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. + * @deprecated use {@link #OpenSslServerContext( + * File, File, String, TrustManagerFactory, Iterable, + * CipherSuiteFilter, OpenSslApplicationProtocolNegotiator, long, long)} */ + @Deprecated public OpenSslServerContext( File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, long sessionTimeout) throws SSLException { + this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers, + IdentityCipherSuiteFilter.INSTANCE, apn, sessionCacheSize, sessionTimeout); + } - super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER); - OpenSsl.ensureAvailability(); + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param cipherFilter a filter to apply over the supplied list of ciphers + * @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 OpenSslServerContext( + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, + long sessionCacheSize, long sessionTimeout) throws SSLException { + this(certChainFile, keyFile, keyPassword, null, ciphers, cipherFilter, + toNegotiator(apn), sessionCacheSize, sessionTimeout); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param cipherFilter a filter to apply over the supplied list of ciphers + * @param config Application protocol config. + * @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 OpenSslServerContext( + File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, + Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config, + long sessionCacheSize, long sessionTimeout) throws SSLException { + this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers, cipherFilter, + toNegotiator(config), sessionCacheSize, sessionTimeout); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param cipherFilter a filter to apply over the supplied list of ciphers + * @param apn Application protocol negotiator. + * @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 OpenSslServerContext( + File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, + Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, + long sessionCacheSize, long sessionTimeout) throws SSLException { + super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER); + OpenSsl.ensureAvailability(); checkNotNull(certChainFile, "certChainFile"); if (!certChainFile.isFile()) { 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 45ecf94e46..09cfde3451 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -262,7 +262,7 @@ public abstract class SslContext { case OPENSSL: return new OpenSslServerContext( keyCertChainFile, keyFile, keyPassword, trustManagerFactory, - ciphers, apn, sessionCacheSize, sessionTimeout); + ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); default: throw new Error(provider.toString()); } @@ -485,7 +485,8 @@ public abstract class SslContext { keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); case OPENSSL: return new OpenSslClientContext( - trustCertChainFile, trustManagerFactory, ciphers, apn, sessionCacheSize, sessionTimeout); + trustCertChainFile, trustManagerFactory, ciphers, cipherFilter, apn, + sessionCacheSize, sessionTimeout); } // Should never happen!! throw new Error(); diff --git a/pom.xml b/pom.xml index 2fff911a0b..85875c6a4b 100644 --- a/pom.xml +++ b/pom.xml @@ -615,7 +615,7 @@ ${project.groupId} netty-tcnative - 1.1.32.Fork1 + 1.1.33.Fork1 ${os.detected.classifier} compile true