From 1065e0f26e0d47a67c479b0fad81efab5d9438d9 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 18 Aug 2017 20:22:01 +0200 Subject: [PATCH] Support JDK9-native ALPN Motivation: Netty is unable to use Java9s ALPN support atm. Modifications: When running on Java9+ we invoke the correct methods that are exposed on the Java9+ implementation of SSLEngine and so be able to support ALPN. This patch is based on the work of @rschmitt and so https://github.com/netty/netty/pull/6992. Result: Fixes #6933. --- .../io/netty/handler/ssl/Java9SslEngine.java | 175 ++++++++++++++++++ .../io/netty/handler/ssl/Java9SslUtils.java | 175 ++++++++++++++++++ .../JdkAlpnApplicationProtocolNegotiator.java | 14 +- .../JdkBaseApplicationProtocolNegotiator.java | 10 +- .../io/netty/handler/ssl/JdkSslEngine.java | 18 +- .../io/netty/handler/ssl/JdkSslSession.java | 152 --------------- .../netty/handler/ssl/JettyAlpnSslEngine.java | 1 - .../ssl/ReferenceCountedOpenSslEngine.java | 24 ++- .../java/io/netty/handler/ssl/SslHandler.java | 6 +- .../netty/handler/ssl/JdkSslEngineTest.java | 33 +++- pom.xml | 1 + 11 files changed, 421 insertions(+), 188 deletions(-) create mode 100644 handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java diff --git a/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java b/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java new file mode 100644 index 0000000000..6534f39b50 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java @@ -0,0 +1,175 @@ +/* + * Copyright 2017 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.util.internal.StringUtil; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; + +import java.nio.ByteBuffer; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.BiFunction; + +import static io.netty.handler.ssl.SslUtils.toSSLHandshakeException; +import static io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener; +import static io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector; + +final class Java9SslEngine extends JdkSslEngine { + private final ProtocolSelectionListener selectionListener; + private final AlpnSelector alpnSelector; + + private final class AlpnSelector implements BiFunction, String> { + private final ProtocolSelector selector; + private boolean called; + + AlpnSelector(ProtocolSelector selector) { + this.selector = selector; + } + + @Override + public String apply(SSLEngine sslEngine, List strings) { + assert !called; + called = true; + + try { + String selected = selector.select(strings); + return selected == null ? StringUtil.EMPTY_STRING : selected; + } catch (Exception cause) { + // Returning null means we want to fail the handshake. + // + // See http://download.java.net/java/jdk9/docs/api/javax/net/ssl/ + // SSLEngine.html#setHandshakeApplicationProtocolSelector-java.util.function.BiFunction- + return null; + } + } + + void checkUnsupported() { + if (called) { + // ALPN message was received by peer and so apply(...) was called. + // See: + // http://hg.openjdk.java.net/jdk9/dev/jdk/file/65464a307408/src/ + // java.base/share/classes/sun/security/ssl/ServerHandshaker.java#l933 + return; + } + String protocol = getApplicationProtocol(); + assert protocol != null; + + if (protocol.isEmpty()) { + // ALPN is not supported + selector.unsupported(); + } + } + } + + Java9SslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) { + super(engine); + if (isServer) { + selectionListener = null; + alpnSelector = new AlpnSelector(applicationNegotiator.protocolSelectorFactory(). + newSelector(this, new LinkedHashSet(applicationNegotiator.protocols()))); + Java9SslUtils.setHandshakeApplicationProtocolSelector(engine, alpnSelector); + } else { + selectionListener = applicationNegotiator.protocolListenerFactory() + .newListener(this, applicationNegotiator.protocols()); + alpnSelector = null; + Java9SslUtils.setApplicationProtocols(engine, applicationNegotiator.protocols()); + } + } + + private SSLEngineResult verifyProtocolSelection(SSLEngineResult result) throws SSLException { + if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) { + if (alpnSelector == null) { + // This means we are using client-side and + try { + String protocol = getApplicationProtocol(); + assert protocol != null; + if (protocol.isEmpty()) { + // If empty the server did not announce ALPN: + // See: + // http://hg.openjdk.java.net/jdk9/dev/jdk/file/65464a307408/src/java.base/ + // share/classes/sun/security/ssl/ClientHandshaker.java#l741 + selectionListener.unsupported(); + } else { + selectionListener.selected(protocol); + } + } catch (Throwable e) { + throw toSSLHandshakeException(e); + } + } else { + assert selectionListener == null; + alpnSelector.checkUnsupported(); + } + } + return result; + } + + @Override + public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { + return verifyProtocolSelection(super.wrap(src, dst)); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] srcs, ByteBuffer dst) throws SSLException { + return verifyProtocolSelection(super.wrap(srcs, dst)); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int len, ByteBuffer dst) throws SSLException { + return verifyProtocolSelection(super.wrap(srcs, offset, len, dst)); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { + return verifyProtocolSelection(super.unwrap(src, dst)); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { + return verifyProtocolSelection(super.unwrap(src, dsts)); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dst, int offset, int len) throws SSLException { + return verifyProtocolSelection(super.unwrap(src, dst, offset, len)); + } + + @Override + void setApplicationProtocol(String applicationProtocol) { + // Do nothing as this is handled internally by the Java9 implementation of SSLEngine. + } + + @Override + public String getApplicationProtocol() { + return Java9SslUtils.getApplicationProtocol(getWrappedEngine()); + } + + // These methods will override the methods defined by Java 9. As we compile with Java8 we can not add + // @Override annotations here. + public String getHandshakeApplicationProtocol() { + return Java9SslUtils.getHandshakeApplicationProtocol(getWrappedEngine()); + } + + public void setHandshakeApplicationProtocolSelector(BiFunction, String> selector) { + Java9SslUtils.setHandshakeApplicationProtocolSelector(getWrappedEngine(), selector); + } + + public BiFunction, String> getHandshakeApplicationProtocolSelector() { + return Java9SslUtils.getHandshakeApplicationProtocolSelector(getWrappedEngine()); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java b/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java new file mode 100644 index 0000000000..2fd3bbcd64 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java @@ -0,0 +1,175 @@ +/* + * Copyright 2017 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 javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.List; +import java.util.function.BiFunction; + +import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +final class Java9SslUtils { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(Java9SslUtils.class); + private static final Method SET_APPLICATION_PROTOCOLS; + private static final Method GET_APPLICATION_PROTOCOL; + private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL; + private static final Method SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR; + private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR; + + static { + Method getHandshakeApplicationProtocol = null; + Method getApplicationProtocol = null; + Method setApplicationProtocols = null; + Method setHandshakeApplicationProtocolSelector = null; + Method getHandshakeApplicationProtocolSelector = null; + + try { + SSLContext context = SSLContext.getInstance(JdkSslContext.PROTOCOL); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + getHandshakeApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + return SSLEngine.class.getMethod("getHandshakeApplicationProtocol"); + } + }); + getHandshakeApplicationProtocol.invoke(engine); + getApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + return SSLEngine.class.getMethod("getApplicationProtocol"); + } + }); + getApplicationProtocol.invoke(engine); + + setApplicationProtocols = AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + return SSLParameters.class.getMethod("setApplicationProtocols", String[].class); + } + }); + setApplicationProtocols.invoke(engine.getSSLParameters(), new Object[]{EmptyArrays.EMPTY_STRINGS}); + + setHandshakeApplicationProtocolSelector = + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + return SSLEngine.class.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class); + } + }); + setHandshakeApplicationProtocolSelector.invoke(engine, new BiFunction, String>() { + @Override + public String apply(SSLEngine sslEngine, List strings) { + return null; + } + }); + + getHandshakeApplicationProtocolSelector = + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + return SSLEngine.class.getMethod("getHandshakeApplicationProtocolSelector"); + } + }); + getHandshakeApplicationProtocolSelector.invoke(engine); + } catch (Throwable t) { + logger.error("Unable to initialize Java9SslUtils, but the detected javaVersion was: {}", + PlatformDependent.javaVersion(), t); + getHandshakeApplicationProtocol = null; + getApplicationProtocol = null; + setApplicationProtocols = null; + setHandshakeApplicationProtocolSelector = null; + getHandshakeApplicationProtocolSelector = null; + } + GET_HANDSHAKE_APPLICATION_PROTOCOL = getHandshakeApplicationProtocol; + GET_APPLICATION_PROTOCOL = getApplicationProtocol; + SET_APPLICATION_PROTOCOLS = setApplicationProtocols; + SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = setHandshakeApplicationProtocolSelector; + GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = getHandshakeApplicationProtocolSelector; + } + + private Java9SslUtils() { + } + + static boolean supportsAlpn() { + return GET_APPLICATION_PROTOCOL != null; + } + + static String getApplicationProtocol(SSLEngine sslEngine) { + try { + return (String) GET_APPLICATION_PROTOCOL.invoke(sslEngine); + } catch (UnsupportedOperationException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + static String getHandshakeApplicationProtocol(SSLEngine sslEngine) { + try { + return (String) GET_HANDSHAKE_APPLICATION_PROTOCOL.invoke(sslEngine); + } catch (UnsupportedOperationException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + static void setApplicationProtocols(SSLEngine engine, List supportedProtocols) { + SSLParameters parameters = engine.getSSLParameters(); + + String[] protocolArray = supportedProtocols.toArray(EmptyArrays.EMPTY_STRINGS); + try { + SET_APPLICATION_PROTOCOLS.invoke(parameters, new Object[]{protocolArray}); + } catch (UnsupportedOperationException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + engine.setSSLParameters(parameters); + } + + static void setHandshakeApplicationProtocolSelector( + SSLEngine engine, BiFunction, String> selector) { + try { + SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine, selector); + } catch (UnsupportedOperationException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + static BiFunction, String> getHandshakeApplicationProtocolSelector(SSLEngine engine) { + try { + return (BiFunction, String>) + GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine); + } catch (UnsupportedOperationException ex) { + throw ex; + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java index 27c213a4df..25f7ac8609 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java @@ -16,6 +16,8 @@ package io.netty.handler.ssl; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.PlatformDependent; + import javax.net.ssl.SSLEngine; /** @@ -25,7 +27,10 @@ import javax.net.ssl.SSLEngine; */ @Deprecated public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicationProtocolNegotiator { - private static final boolean AVAILABLE = Conscrypt.isAvailable() || JettyAlpnSslEngine.isAvailable(); + private static final boolean AVAILABLE = Conscrypt.isAvailable() || + jdkAlpnSupported() || + JettyAlpnSslEngine.isAvailable(); + private static final SslEngineWrapperFactory ALPN_WRAPPER = AVAILABLE ? new AlpnWrapper() : new FailureWrapper(); /** @@ -129,6 +134,9 @@ public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicati return isServer ? ConscryptAlpnSslEngine.newServerEngine(engine, alloc, applicationNegotiator) : ConscryptAlpnSslEngine.newClientEngine(engine, alloc, applicationNegotiator); } + if (jdkAlpnSupported()) { + return new Java9SslEngine(engine, applicationNegotiator, isServer); + } if (JettyAlpnSslEngine.isAvailable()) { return isServer ? JettyAlpnSslEngine.newServerEngine(engine, applicationNegotiator) : JettyAlpnSslEngine.newClientEngine(engine, applicationNegotiator); @@ -136,4 +144,8 @@ public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicati throw new RuntimeException("Unable to wrap SSLEngine of type " + engine.getClass().getName()); } } + + static boolean jdkAlpnSupported() { + return PlatformDependent.javaVersion() >= 9 && Java9SslUtils.supportsAlpn(); + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java index fc326f6488..8e20c9b1b1 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java @@ -137,14 +137,14 @@ class JdkBaseApplicationProtocolNegotiator implements JdkApplicationProtocolNego @Override public void unsupported() { - engineWrapper.getSession().setApplicationProtocol(null); + engineWrapper.setApplicationProtocol(null); } @Override public String select(List protocols) throws Exception { for (String p : supportedProtocols) { if (protocols.contains(p)) { - engineWrapper.getSession().setApplicationProtocol(p); + engineWrapper.setApplicationProtocol(p); return p; } } @@ -152,7 +152,7 @@ class JdkBaseApplicationProtocolNegotiator implements JdkApplicationProtocolNego } public String noSelectMatchFound() throws Exception { - engineWrapper.getSession().setApplicationProtocol(null); + engineWrapper.setApplicationProtocol(null); return null; } } @@ -179,13 +179,13 @@ class JdkBaseApplicationProtocolNegotiator implements JdkApplicationProtocolNego @Override public void unsupported() { - engineWrapper.getSession().setApplicationProtocol(null); + engineWrapper.setApplicationProtocol(null); } @Override public void selected(String protocol) throws Exception { if (supportedProtocols.contains(protocol)) { - engineWrapper.getSession().setApplicationProtocol(protocol); + engineWrapper.setApplicationProtocol(protocol); } else { noSelectedMatchFound(protocol); } diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java index 680eea6195..1cc3bdfa32 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java @@ -24,18 +24,26 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; -class JdkSslEngine extends SSLEngine { +class JdkSslEngine extends SSLEngine implements ApplicationProtocolAccessor { private final SSLEngine engine; - private final JdkSslSession session; + private volatile String applicationProtocol; JdkSslEngine(SSLEngine engine) { this.engine = engine; - session = new JdkSslSession(engine); } @Override - public JdkSslSession getSession() { - return session; + public String getApplicationProtocol() { + return applicationProtocol; + } + + void setApplicationProtocol(String applicationProtocol) { + this.applicationProtocol = applicationProtocol; + } + + @Override + public SSLSession getSession() { + return engine.getSession(); } public SSLEngine getWrappedEngine() { diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java deleted file mode 100644 index 183c296632..0000000000 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; -import javax.security.cert.X509Certificate; -import java.security.Principal; -import java.security.cert.Certificate; - -final class JdkSslSession implements SSLSession, ApplicationProtocolAccessor { - private final SSLEngine engine; - private volatile String applicationProtocol; - - JdkSslSession(SSLEngine engine) { - this.engine = engine; - } - - private SSLSession unwrap() { - return engine.getSession(); - } - - @Override - public String getProtocol() { - return unwrap().getProtocol(); - } - - @Override - public String getApplicationProtocol() { - return applicationProtocol; - } - - void setApplicationProtocol(String applicationProtocol) { - this.applicationProtocol = applicationProtocol; - } - - @Override - public byte[] getId() { - return unwrap().getId(); - } - - @Override - public SSLSessionContext getSessionContext() { - return unwrap().getSessionContext(); - } - - @Override - public long getCreationTime() { - return unwrap().getCreationTime(); - } - - @Override - public long getLastAccessedTime() { - return unwrap().getLastAccessedTime(); - } - - @Override - public void invalidate() { - unwrap().invalidate(); - } - - @Override - public boolean isValid() { - return unwrap().isValid(); - } - - @Override - public void putValue(String s, Object o) { - unwrap().putValue(s, o); - } - - @Override - public Object getValue(String s) { - return unwrap().getValue(s); - } - - @Override - public void removeValue(String s) { - unwrap().removeValue(s); - } - - @Override - public String[] getValueNames() { - return unwrap().getValueNames(); - } - - @Override - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return unwrap().getPeerCertificates(); - } - - @Override - public Certificate[] getLocalCertificates() { - return unwrap().getLocalCertificates(); - } - - @Override - public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { - return unwrap().getPeerCertificateChain(); - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - return unwrap().getPeerPrincipal(); - } - - @Override - public Principal getLocalPrincipal() { - return unwrap().getLocalPrincipal(); - } - - @Override - public String getCipherSuite() { - return unwrap().getCipherSuite(); - } - - @Override - public String getPeerHost() { - return unwrap().getPeerHost(); - } - - @Override - public int getPeerPort() { - return unwrap().getPeerPort(); - } - - @Override - public int getPacketBufferSize() { - return unwrap().getPacketBufferSize(); - } - - @Override - public int getApplicationBufferSize() { - return unwrap().getApplicationBufferSize(); - } -} diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java index 624719a5be..ce9f14a57e 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java @@ -38,7 +38,6 @@ abstract class JettyAlpnSslEngine extends JdkSslEngine { } private static boolean initAvailable() { - // TODO: Add support for ALPN when using Java9 and still be able to configure it the Netty way. if (PlatformDependent.javaVersion() <= 8) { try { // Always use bootstrap class loader. diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java index 9878a87d43..2670e53b23 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java @@ -87,7 +87,7 @@ import static javax.net.ssl.SSLEngineResult.Status.OK; * the instance depends upon are released. Otherwise if any method of this class is called which uses the * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash. */ -public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted { +public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted, ApplicationProtocolAccessor { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class); @@ -159,6 +159,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc private boolean renegotiationPending; private boolean receivedShutdown; private volatile int destroyed; + private volatile String applicationProtocol; // Reference Counting private final ResourceLeakTracker leak; @@ -1827,7 +1828,12 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); } - private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor { + @Override + public String getApplicationProtocol() { + return applicationProtocol; + } + + private final class OpenSslSession implements SSLSession { private final OpenSslSessionContext sessionContext; // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any @@ -1835,7 +1841,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc private X509Certificate[] x509PeerCerts; private Certificate[] peerCerts; private String protocol; - private String applicationProtocol; private String cipher; private byte[] id; private long creationTime; @@ -2043,14 +2048,14 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc case ALPN: applicationProtocol = SSL.getAlpnSelected(ssl); if (applicationProtocol != null) { - this.applicationProtocol = selectApplicationProtocol( + ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; case NPN: applicationProtocol = SSL.getNextProtoNegotiated(ssl); if (applicationProtocol != null) { - this.applicationProtocol = selectApplicationProtocol( + ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; @@ -2060,7 +2065,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc applicationProtocol = SSL.getNextProtoNegotiated(ssl); } if (applicationProtocol != null) { - this.applicationProtocol = selectApplicationProtocol( + ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; @@ -2159,13 +2164,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc return protocol; } - @Override - public String getApplicationProtocol() { - synchronized (ReferenceCountedOpenSslEngine.this) { - return applicationProtocol; - } - } - @Override public String getPeerHost() { return ReferenceCountedOpenSslEngine.this.getPeerHost(); diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index b03417d15e..3724b77271 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -573,12 +573,12 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH * @return the protocol name or {@code null} if application-level protocol has not been negotiated */ public String applicationProtocol() { - SSLSession sess = engine().getSession(); - if (!(sess instanceof ApplicationProtocolAccessor)) { + SSLEngine engine = engine(); + if (!(engine instanceof ApplicationProtocolAccessor)) { return null; } - return ((ApplicationProtocolAccessor) sess).getApplicationProtocol(); + return ((ApplicationProtocolAccessor) engine).getApplicationProtocol(); } /** diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java index c1205130fc..110871b360 100644 --- a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java +++ b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java @@ -25,6 +25,8 @@ import io.netty.handler.ssl.util.SelfSignedCertificate; import java.security.Provider; import java.util.ArrayList; import java.util.Collection; + +import io.netty.util.internal.PlatformDependent; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +45,7 @@ import static org.junit.Assume.assumeNoException; @RunWith(Parameterized.class) public class JdkSslEngineTest extends SSLEngineTest { public enum ProviderType { - NPN_DEFAULT { + NPN_JETTY { @Override boolean isAvailable() { return JettyNpnSslEngine.isAvailable(); @@ -59,7 +61,7 @@ public class JdkSslEngineTest extends SSLEngineTest { return null; } }, - ALPN_DEFAULT { + ALPN_JETTY { @Override boolean isAvailable() { return JettyAlpnSslEngine.isAvailable(); @@ -76,6 +78,23 @@ public class JdkSslEngineTest extends SSLEngineTest { return null; } }, + ALPN_JAVA9 { + @Override + boolean isAvailable() { + return PlatformDependent.javaVersion() >= 9 && Java9SslUtils.supportsAlpn(); + } + + @Override + Protocol protocol() { + return Protocol.ALPN; + } + + @Override + Provider provider() { + // Use the default provider. + return null; + } + }, ALPN_CONSCRYPT { private Provider provider; @@ -178,7 +197,7 @@ public class JdkSslEngineTest extends SSLEngineTest { public void testTlsExtensionNoCompatibleProtocolsClientHandshakeFailure() throws Exception { try { providerType.activate(this); - if (providerType == ProviderType.NPN_DEFAULT) { + if (providerType == ProviderType.NPN_JETTY) { ApplicationProtocolConfig clientApn = failingNegotiator(providerType.protocol(), PREFERRED_APPLICATION_LEVEL_PROTOCOL); ApplicationProtocolConfig serverApn = acceptingNegotiator(providerType.protocol(), @@ -249,7 +268,7 @@ public class JdkSslEngineTest extends SSLEngineTest { public void testAlpnCompatibleProtocolsDifferentClientOrder() throws Exception { try { providerType.activate(this); - if (providerType == ProviderType.NPN_DEFAULT) { + if (providerType == ProviderType.NPN_JETTY) { // This test only applies to ALPN. throw tlsExtensionNotFound(providerType.protocol()); } @@ -314,16 +333,14 @@ public class JdkSslEngineTest extends SSLEngineTest { return provider; } - private ApplicationProtocolConfig failingNegotiator(Protocol protocol, - String... supportedProtocols) { + private static ApplicationProtocolConfig failingNegotiator(Protocol protocol, String... supportedProtocols) { return new ApplicationProtocolConfig(protocol, SelectorFailureBehavior.FATAL_ALERT, SelectedListenerFailureBehavior.FATAL_ALERT, supportedProtocols); } - private ApplicationProtocolConfig acceptingNegotiator(Protocol protocol, - String... supportedProtocols) { + private static ApplicationProtocolConfig acceptingNegotiator(Protocol protocol, String... supportedProtocols) { return new ApplicationProtocolConfig(protocol, SelectorFailureBehavior.NO_ADVERTISE, SelectedListenerFailureBehavior.ACCEPT, diff --git a/pom.xml b/pom.xml index d90c6a779b..27f5298438 100644 --- a/pom.xml +++ b/pom.xml @@ -710,6 +710,7 @@ java.util.concurrent.atomic.LongAdder + java.util.function.BiFunction java.net.InetAddress