From 8953f5a6cb3649c35bb58b70ea8e222fc3161be7 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 17:21:18 +0900 Subject: [PATCH] Add unified NextProtoNego extension support to SslContext Motivation: - OpenSslEngine and JDK SSLEngine (+ Jetty NPN) have different APIs to support NextProtoNego extension. - It is impossible to configure NPN with SslContext when the provider type is JDK. Modification: - Implement NextProtoNego extension by overriding the behavior of SSLSession.getProtocol() for both OpenSSLEngine and JDK SSLEngine. - SSLEngine.getProtocol() returns a string delimited by a colon (':') where the first component is the transport protosol (e.g. TLSv1.2) and the second component is the name of the application protocol - Remove the direct reference of Jetty NPN classes from the examples - Add SslContext.newApplicationProtocolSelector Result: - A user can now use both JDK SSLEngine and OpenSslEngine for NPN-based protocols such as HTTP2 and SPDY --- .../netty/example/spdy/client/SpdyClient.java | 10 +- .../spdy/client/SpdyClientInitializer.java | 12 +- .../spdy/client/SpdyClientProvider.java | 58 ---- .../spdy/server/SpdyOrHttpHandler.java | 5 +- .../netty/example/spdy/server/SpdyServer.java | 12 +- .../spdy/server/SpdyServerInitializer.java | 14 +- .../spdy/server/SpdyServerProvider.java | 63 ---- handler/pom.xml | 11 + .../handler/ssl/JdkSslClientContext.java | 17 +- .../io/netty/handler/ssl/JdkSslContext.java | 6 +- .../handler/ssl/JdkSslServerContext.java | 29 +- .../netty/handler/ssl/JettyNpnSslEngine.java | 286 ++++++++++++++++++ .../netty/handler/ssl/JettyNpnSslSession.java | 167 ++++++++++ .../io/netty/handler/ssl/OpenSslEngine.java | 20 +- .../handler/ssl/OpenSslServerContext.java | 6 +- .../java/io/netty/handler/ssl/SslContext.java | 78 +++++ pom.xml | 3 + 17 files changed, 634 insertions(+), 163 deletions(-) delete mode 100644 example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java delete mode 100644 example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java 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 f5fa16f513..fbae5d12f1 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 @@ -27,8 +27,8 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import javax.net.ssl.SSLException; @@ -60,7 +60,13 @@ public class SpdyClient { private EventLoopGroup workerGroup; public SpdyClient(String host, int port) throws SSLException { - sslCtx = SslContext.newClientContext(SslProvider.JDK, InsecureTrustManagerFactory.INSTANCE); + sslCtx = SslContext.newClientContext( + null, InsecureTrustManagerFactory.INSTANCE, null, + SslContext.newApplicationProtocolSelector( + SelectedProtocol.SPDY_3_1.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); + this.host = host; this.port = port; httpResponseHandler = new HttpResponseClientHandler(); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java index 63400c472d..f8fc08bce4 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java @@ -23,10 +23,6 @@ import io.netty.handler.codec.spdy.SpdyHttpDecoder; import io.netty.handler.codec.spdy.SpdyHttpEncoder; import io.netty.handler.codec.spdy.SpdySessionHandler; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; import static io.netty.handler.codec.spdy.SpdyVersion.*; import static io.netty.util.internal.logging.InternalLogLevel.*; @@ -45,14 +41,8 @@ public class SpdyClientInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { - SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = sslHandler.engine(); - NextProtoNego.put(engine, new SpdyClientProvider()); - NextProtoNego.debug = true; - ChannelPipeline pipeline = ch.pipeline(); - - pipeline.addLast("ssl", sslHandler); + pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc())); pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(SPDY_3_1)); pipeline.addLast("spdyFrameLogger", new SpdyFrameLogger(INFO)); pipeline.addLast("spdySessionHandler", new SpdySessionHandler(SPDY_3_1, false)); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java deleted file mode 100644 index c4adedf39b..0000000000 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java +++ /dev/null @@ -1,58 +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.example.spdy.client; - -import org.eclipse.jetty.npn.NextProtoNego.ClientProvider; - -import java.util.List; - -import static io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol.*; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next Protocol - * Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which protocol to use - * over the secure connection. - *

- * This NPN service provider negotiates using SPDY. - *

- * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The - * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from Maven - * at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. - * - * @see Jetty documentation - */ -public class SpdyClientProvider implements ClientProvider { - - private String selectedProtocol; - - @Override - public String selectProtocol(List protocols) { - if (protocols.contains(SPDY_3_1.protocolName())) { - return SPDY_3_1.protocolName(); - } - return selectedProtocol; - } - - @Override - public boolean supports() { - return true; - } - - @Override - public void unsupported() { - selectedProtocol = HTTP_1_1.protocolName(); - } -} diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java b/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java index b22c070d13..d2837177bc 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyOrHttpHandler.java @@ -17,7 +17,6 @@ package io.netty.example.spdy.server; import io.netty.channel.ChannelInboundHandler; import io.netty.handler.codec.spdy.SpdyOrHttpChooser; -import org.eclipse.jetty.npn.NextProtoNego; import javax.net.ssl.SSLEngine; import java.util.logging.Logger; @@ -41,8 +40,8 @@ public class SpdyOrHttpHandler extends SpdyOrHttpChooser { @Override protected SelectedProtocol getProtocol(SSLEngine engine) { - SpdyServerProvider provider = (SpdyServerProvider) NextProtoNego.get(engine); - SelectedProtocol selectedProtocol = provider.getSelectedProtocol(); + String[] protocol = engine.getSession().getProtocol().split(":"); + SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]); logger.info("Selected Protocol is " + selectedProtocol); return selectedProtocol; 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 04ab1080ea..a281dd5ddb 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 @@ -21,10 +21,12 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.util.Arrays; + /** * A SPDY Server that responds to a GET request with a Hello World. *

@@ -85,7 +87,13 @@ public class SpdyServer { // Configure SSL. SelfSignedCertificate ssc = new SelfSignedCertificate(); - SslContext sslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey()); + SslContext sslCtx = SslContext.newServerContext( + ssc.certificate(), ssc.privateKey(), null, null, + Arrays.asList( + SelectedProtocol.SPDY_3_1.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); + new SpdyServer(sslCtx, port).run(); } } diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java index 9f3779a3cd..e59954b002 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java @@ -19,10 +19,6 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; /** * Sets up the Netty pipeline @@ -38,15 +34,7 @@ public class SpdyServerInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); - - SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = sslHandler.engine(); - p.addLast("ssl", sslHandler); - - // Setup NextProtoNego with our server provider - NextProtoNego.put(engine, new SpdyServerProvider()); - NextProtoNego.debug = true; - + p.addLast("ssl", sslCtx.newHandler(ch.alloc())); // Negotiates with the browser if SPDY or HTTP is going to be used p.addLast("handler", new SpdyOrHttpHandler()); } diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java deleted file mode 100644 index e8f5ff2eba..0000000000 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java +++ /dev/null @@ -1,63 +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.example.spdy.server; - -import io.netty.handler.codec.spdy.SpdyOrHttpChooser; -import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; - -import java.util.Arrays; -import java.util.List; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next - * Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which - * protocol to use over the secure connection. - *

- * This NPN service provider negotiates using SPDY. - *

- * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The - * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from - * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. - * - * @see Jetty documentation - */ -public class SpdyServerProvider implements ServerProvider { - - private String selectedProtocol; - - @Override - public void unsupported() { - // if unsupported, default to http/1.1 - selectedProtocol = "http/1.1"; - } - - @Override - public List protocols() { - return Arrays.asList("spdy/3.1", "http/1.1"); - } - - @Override - public void protocolSelected(String protocol) { - selectedProtocol = protocol; - } - - public SpdyOrHttpChooser.SelectedProtocol getSelectedProtocol() { - if (selectedProtocol == null) { - return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN; - } - return SpdyOrHttpChooser.SelectedProtocol.protocol(selectedProtocol); - } -} diff --git a/handler/pom.xml b/handler/pom.xml index 6ecf7bdefb..a9101f4729 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -55,6 +55,17 @@ bcpkix-jdk15on true + + org.eclipse.jetty.npn + npn-api + true + + + org.mortbay.jetty.npn + npn-boot + provided + true + 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 05296723b1..05e13fdf8f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java @@ -20,6 +20,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManager; @@ -38,6 +39,7 @@ import java.util.List; public final class JdkSslClientContext extends JdkSslContext { private final SSLContext ctx; + private final ApplicationProtocolSelector nextProtocolSelector; /** * Creates a new instance. @@ -105,10 +107,12 @@ public final class JdkSslClientContext extends JdkSslContext { super(ciphers); - if (nextProtocolSelector != null) { + if (nextProtocolSelector != null && !JettyNpnSslEngine.isAvailable()) { throw new SSLException("NPN/ALPN unsupported: " + nextProtocolSelector); } + this.nextProtocolSelector = nextProtocolSelector; + try { if (certChainFile == null) { ctx = SSLContext.getInstance(PROTOCOL); @@ -166,7 +170,7 @@ public final class JdkSslClientContext extends JdkSslContext { @Override public ApplicationProtocolSelector nextProtocolSelector() { - return null; + return nextProtocolSelector; } @Override @@ -178,4 +182,13 @@ public final class JdkSslClientContext extends JdkSslContext { public SSLContext context() { return ctx; } + + @Override + SSLEngine wrapEngine(SSLEngine engine) { + if (nextProtocolSelector == null) { + return engine; + } else { + return new JettyNpnSslEngine(engine, nextProtocolSelector); + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java index d543f2dc80..c2d408ae5b 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java @@ -153,7 +153,7 @@ public abstract class JdkSslContext extends SslContext { engine.setEnabledCipherSuites(cipherSuites); engine.setEnabledProtocols(PROTOCOLS); engine.setUseClientMode(isClient()); - return engine; + return wrapEngine(engine); } @Override @@ -162,9 +162,11 @@ public abstract class JdkSslContext extends SslContext { engine.setEnabledCipherSuites(cipherSuites); engine.setEnabledProtocols(PROTOCOLS); engine.setUseClientMode(isClient()); - return engine; + return wrapEngine(engine); } + abstract SSLEngine wrapEngine(SSLEngine engine); + private static String[] toCipherSuiteArray(Iterable ciphers) { if (ciphers == null) { return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]); diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java index 30f836385f..4363f72f19 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java @@ -21,6 +21,7 @@ import io.netty.buffer.ByteBufInputStream; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; import java.io.File; @@ -42,6 +43,7 @@ import java.util.List; public final class JdkSslServerContext extends JdkSslContext { private final SSLContext ctx; + private final List nextProtocols; /** * Creates a new instance. @@ -100,7 +102,21 @@ public final class JdkSslServerContext extends JdkSslContext { } if (nextProtocols != null && nextProtocols.iterator().hasNext()) { - throw new SSLException("NPN/ALPN unsupported: " + nextProtocols); + if (!JettyNpnSslEngine.isAvailable()) { + throw new SSLException("NPN/ALPN unsupported: " + nextProtocols); + } + + List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + this.nextProtocols = Collections.unmodifiableList(list); + } else { + this.nextProtocols = Collections.emptyList(); } String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); @@ -173,11 +189,20 @@ public final class JdkSslServerContext extends JdkSslContext { @Override public List nextProtocols() { - return Collections.emptyList(); + return nextProtocols; } @Override public SSLContext context() { return ctx; } + + @Override + SSLEngine wrapEngine(SSLEngine engine) { + if (nextProtocols.isEmpty()) { + return engine; + } else { + return new JettyNpnSslEngine(engine, nextProtocols); + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java new file mode 100644 index 0000000000..04a0d2535a --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java @@ -0,0 +1,286 @@ +/* + * 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.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.eclipse.jetty.npn.NextProtoNego; +import org.eclipse.jetty.npn.NextProtoNego.ClientProvider; +import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import java.nio.ByteBuffer; +import java.util.List; + +final class JettyNpnSslEngine extends SSLEngine { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(JettyNpnSslEngine.class); + + private static boolean available; + + static boolean isAvailable() { + updateAvailability(); + return available; + } + + private static void updateAvailability() { + if (available) { + return; + } + try { + // Try to get the bootstrap class loader. + ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent(); + if (bootloader == null) { + // If failed, use the system class loader, + // although it's not perfect to tell if NPN extension has been loaded. + bootloader = ClassLoader.getSystemClassLoader(); + } + Class.forName("sun.security.ssl.NextProtoNegoExtension", true, bootloader); + available = true; + } catch (Exception ignore) { + // npn-boot was not loaded. + } + } + + private final SSLEngine engine; + private final JettyNpnSslSession session; + + JettyNpnSslEngine(SSLEngine engine, final List nextProtocols) { + assert !nextProtocols.isEmpty(); + + this.engine = engine; + session = new JettyNpnSslSession(engine); + + NextProtoNego.put(engine, new ServerProvider() { + @Override + public void unsupported() { + getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1)); + } + + @Override + public List protocols() { + return nextProtocols; + } + + @Override + public void protocolSelected(String protocol) { + getSession().setApplicationProtocol(protocol); + } + }); + } + + JettyNpnSslEngine(SSLEngine engine, final ApplicationProtocolSelector nextProtocolSelector) { + this.engine = engine; + session = new JettyNpnSslSession(engine); + + NextProtoNego.put(engine, new ClientProvider() { + @Override + public boolean supports() { + return true; + } + + @Override + public void unsupported() { + session.setApplicationProtocol(null); + } + + @Override + public String selectProtocol(List protocols) { + String p = null; + try { + p = nextProtocolSelector.selectProtocol(protocols); + } catch (Exception e) { + logger.warn("Failed to select the next protocol:", e); + } + session.setApplicationProtocol(p); + return p; + } + }); + } + + @Override + public JettyNpnSslSession getSession() { + return session; + } + + @Override + public void closeInbound() throws SSLException { + NextProtoNego.remove(engine); + engine.closeInbound(); + } + + @Override + public void closeOutbound() { + NextProtoNego.remove(engine); + engine.closeOutbound(); + } + + @Override + public String getPeerHost() { + return engine.getPeerHost(); + } + + @Override + public int getPeerPort() { + return engine.getPeerPort(); + } + + @Override + public SSLEngineResult wrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException { + return engine.wrap(byteBuffer, byteBuffer2); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] byteBuffers, ByteBuffer byteBuffer) throws SSLException { + return engine.wrap(byteBuffers, byteBuffer); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i2, ByteBuffer byteBuffer) throws SSLException { + return engine.wrap(byteBuffers, i, i2, byteBuffer); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffer2); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffers); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i2) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffers, i, i2); + } + + @Override + public Runnable getDelegatedTask() { + return engine.getDelegatedTask(); + } + + @Override + public boolean isInboundDone() { + return engine.isInboundDone(); + } + + @Override + public boolean isOutboundDone() { + return engine.isOutboundDone(); + } + + @Override + public String[] getSupportedCipherSuites() { + return engine.getSupportedCipherSuites(); + } + + @Override + public String[] getEnabledCipherSuites() { + return engine.getEnabledCipherSuites(); + } + + @Override + public void setEnabledCipherSuites(String[] strings) { + engine.setEnabledCipherSuites(strings); + } + + @Override + public String[] getSupportedProtocols() { + return engine.getSupportedProtocols(); + } + + @Override + public String[] getEnabledProtocols() { + return engine.getEnabledProtocols(); + } + + @Override + public void setEnabledProtocols(String[] strings) { + engine.setEnabledProtocols(strings); + } + + @Override + public SSLSession getHandshakeSession() { + return engine.getHandshakeSession(); + } + + @Override + public void beginHandshake() throws SSLException { + engine.beginHandshake(); + } + + @Override + public HandshakeStatus getHandshakeStatus() { + return engine.getHandshakeStatus(); + } + + @Override + public void setUseClientMode(boolean b) { + engine.setUseClientMode(b); + } + + @Override + public boolean getUseClientMode() { + return engine.getUseClientMode(); + } + + @Override + public void setNeedClientAuth(boolean b) { + engine.setNeedClientAuth(b); + } + + @Override + public boolean getNeedClientAuth() { + return engine.getNeedClientAuth(); + } + + @Override + public void setWantClientAuth(boolean b) { + engine.setWantClientAuth(b); + } + + @Override + public boolean getWantClientAuth() { + return engine.getWantClientAuth(); + } + + @Override + public void setEnableSessionCreation(boolean b) { + engine.setEnableSessionCreation(b); + } + + @Override + public boolean getEnableSessionCreation() { + return engine.getEnableSessionCreation(); + } + + @Override + public SSLParameters getSSLParameters() { + return engine.getSSLParameters(); + } + + @Override + public void setSSLParameters(SSLParameters sslParameters) { + engine.setSSLParameters(sslParameters); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java new file mode 100644 index 0000000000..b05866f174 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java @@ -0,0 +1,167 @@ +/* + * 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 JettyNpnSslSession implements SSLSession { + + private final SSLEngine engine; + private volatile String applicationProtocol; + + JettyNpnSslSession(SSLEngine engine) { + this.engine = engine; + } + + void setApplicationProtocol(String applicationProtocol) { + this.applicationProtocol = applicationProtocol; + } + + @Override + public String getProtocol() { + final String protocol = unwrap().getProtocol(); + final String applicationProtocol = this.applicationProtocol; + + if (applicationProtocol == null) { + if (protocol != null) { + return protocol.replace(':', '_'); + } else { + return null; + } + } + + final StringBuilder buf = new StringBuilder(32); + if (protocol != null) { + buf.append(protocol.replace(':', '_')); + buf.append(':'); + } else { + buf.append("null:"); + } + buf.append(applicationProtocol); + return buf.toString(); + } + + private SSLSession unwrap() { + return engine.getSession(); + } + + @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/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 0fc8c4a904..8d27bfa042 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -85,7 +85,7 @@ public final class OpenSslEngine extends SSLEngine { private volatile int destroyed; private String cipher; - private String protocol; + private volatile String applicationProtocol; // SSL Engine status variables private boolean isInboundDone; @@ -95,6 +95,7 @@ public final class OpenSslEngine extends SSLEngine { private int lastPrimingReadResult; private final ByteBufAllocator alloc; + private final String fallbackApplicationProtocol; private SSLSession session; /** @@ -103,7 +104,7 @@ 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 */ - public OpenSslEngine(long sslCtx, ByteBufAllocator alloc) { + public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { throw new NullPointerException("sslContext"); @@ -115,6 +116,7 @@ public final class OpenSslEngine extends SSLEngine { this.alloc = alloc; ssl = SSL.newSSL(sslCtx, true); networkBIO = SSL.makeNetworkBIO(ssl); + this.fallbackApplicationProtocol = fallbackApplicationProtocol; } /** @@ -708,7 +710,13 @@ public final class OpenSslEngine extends SSLEngine { @Override public String getProtocol() { - return protocol; + // TODO: Figure out how to get the current protocol. + String applicationProtocol = OpenSslEngine.this.applicationProtocol; + if (applicationProtocol == null) { + return "unknown"; + } else { + return "unknown:" + applicationProtocol; + } } @Override @@ -796,7 +804,11 @@ public final class OpenSslEngine extends SSLEngine { if (SSL.isInInit(ssl) == 0) { handshakeFinished = true; cipher = SSL.getCipherForSSL(ssl); - protocol = SSL.getNextProtoNegotiated(ssl); + String applicationProtocol = SSL.getNextProtoNegotiated(ssl); + if (applicationProtocol == null) { + applicationProtocol = fallbackApplicationProtocol; + } + this.applicationProtocol = applicationProtocol; return FINISHED; } 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 04142440b6..cbea241297 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -310,7 +310,11 @@ public final class OpenSslServerContext extends SslContext { */ @Override public SSLEngine newEngine(ByteBufAllocator alloc) { - return new OpenSslEngine(ctx, alloc); + if (unmodifiableNextProtocols.isEmpty()) { + return new OpenSslEngine(ctx, alloc, null); + } else { + return new OpenSslEngine(ctx, alloc, unmodifiableNextProtocols.get(unmodifiableNextProtocols.size() - 1)); + } } @Override 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 8da2a14757..f3a3f33b71 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -26,6 +26,7 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.File; +import java.util.ArrayList; import java.util.List; /** @@ -373,6 +374,83 @@ public abstract class SslContext { ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); } + /** + * Creates a simple client-side {@link ApplicationProtocolSelector} that selects the most preferred protocol + * among the application protocols sent by the server. If there is no match, it chooses the least preferred one. + * + * @param nextProtocols the list of the supported client-side application protocols, in the order of preference + * @return the new {@link ApplicationProtocolSelector}. + * {@code null} if the specified {@code nextProtocols} does not contain any elements. + * + */ + public static ApplicationProtocolSelector newApplicationProtocolSelector(String... nextProtocols) { + if (nextProtocols == null) { + throw new NullPointerException("nextProtocols"); + } + + final List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + if (list.isEmpty()) { + return null; + } + + return newApplicationProtocolSelector(list); + } + + private static ApplicationProtocolSelector newApplicationProtocolSelector(final List list) { + return new ApplicationProtocolSelector() { + @Override + public String selectProtocol(List protocols) throws Exception { + for (String p: list) { + if (protocols.contains(p)) { + return p; + } + } + return list.get(list.size() - 1); + } + + @Override + public String toString() { + return "ApplicationProtocolSelector(" + list + ')'; + } + }; + } + + /** + * Creates a simple client-side {@link ApplicationProtocolSelector} that selects the most preferred protocol + * among the application protocols sent by the server. If there is no match, it chooses the least preferred one. + * + * @param nextProtocols the list of the supported client-side application protocols, in the order of preference + * @return the new {@link ApplicationProtocolSelector}. + * {@code null} if the specified {@code nextProtocols} does not contain any elements. + * + */ + public static ApplicationProtocolSelector newApplicationProtocolSelector(Iterable nextProtocols) { + if (nextProtocols == null) { + throw new NullPointerException("nextProtocols"); + } + + final List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + if (list.isEmpty()) { + return null; + } + + return newApplicationProtocolSelector(list); + } + SslContext() { } /** diff --git a/pom.xml b/pom.xml index ca247e7548..86d66cec12 100644 --- a/pom.xml +++ b/pom.xml @@ -667,6 +667,9 @@ sun.security.x509.X500Name sun.security.x509.X509CertInfo sun.security.x509.X509CertImpl + + + javax.net.ssl.SSLEngine