From cb4020d4be08e4c2539278a7eeb7495cf498bbbe Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 14 May 2014 21:01:36 +0900 Subject: [PATCH] Provide convenient universal API to enable SSL/TLS Motivation: Although 4cff4b99fd9bcaf256fa62699309e7beff8a136b introduced OpenSslEngine and its helper classes, a user has to write two different copies of SSL initialization code that does pretty much same job, because the initialization procedure between JDK SSLEngine and OpenSslEngine are different. Modifications: - Replace OpenSslContextBuilder with SslContext which provides the unified API for creating an SSL context - SslContext allows you to create a new SSLEngine or a new SslHandler with your PKCS#8 key and X.509 certificate chain. - Merge OpenSslBufferPool into SslBufferPool - Add an option to preallocate the pool - Add an option to allocate direct buffers - When OpenSSL is in use, preallocate direct buffers, which is close to what OpenSslBufferPool does. - Add JdkSslContext which is a simple wrapper of JDK's SSLContext - The specified PKCS#8 key and X.509 certificate chain are converted to JDK KeyStore in instantiation time. - Like OpenSslServerContext, it uses sensible default cipher suites now. - A user does not specify certPath and caPath separately anymore. He or she has to merge them into a single file. I find this more logical because previously ca file's first entry and cert file were always same. - Clean up SSL tests to demonstrate the advantage of this change - AbstractSocketSsl*Test now uses SslContext.new*Context() to configure both the client and the server side. We did this only for the server side previously and had to use different certificates for JDK SSLEngine and OpenSslEngine, but not anymore. - Add ApplicationProtocolSelector to ensure the future support for NPN (NextProtoNego) and ALPN (Application Layer Protocol Negotiation) on the client-side. - Add SimpleTrustManagerFactory to help a user write a TrustManagerFactory easily, which should be useful for those who need to write an alternative verification mechanism. For example, we can use it to implement an unsafe TrustManagerFactory that accepts self-signed certificates for testing purposes. - Add InsecureTrustManagerFactory and FingerprintTrustManager for quick and dirty testing - Add SelfSignedCertificate class which generates a self-signed X.509 certificate very easily. - Update all our examples to use SslContext.newClient/ServerContext() - Found that OpenSslEngine performs unnecessary memory copy - optimized it. - SslHandler now logs the chosen cipher suite when handshake is finished. Result: - Cleaner unified API for configuring an SSL client and an SSL server regardless of its internal implementation. - When native libraries are available, OpenSSL-based SSLEngine implementation is selected automatically to take advantage of its performance benefit. - Examples take advantage of this modification and thus are cleaner. --- NOTICE.txt | 25 +- license/LICENSE.bouncycastle.txt | 23 ++ pom.xml | 25 ++ .../http/file/HttpStaticFileServer.java | 29 +- .../HttpStaticFileServerPipelineFactory.java | 23 +- .../example/http/snoop/HttpSnoopClient.java | 15 +- .../snoop/HttpSnoopClientPipelineFactory.java | 23 +- .../tunnel/HttpTunnelingClientExample.java | 8 +- .../example/http/upload/HttpUploadClient.java | 92 ++--- .../HttpUploadClientPipelineFactory.java | 24 +- .../example/http/upload/HttpUploadServer.java | 33 +- .../HttpUploadServerPipelineFactory.java | 25 +- .../PortUnificationServer.java | 22 +- .../PortUnificationServerHandler.java | 23 +- .../example/securechat/SecureChatClient.java | 13 +- .../SecureChatClientPipelineFactory.java | 24 +- .../securechat/SecureChatKeyStore.java | 313 ------------------ .../example/securechat/SecureChatServer.java | 19 +- .../SecureChatServerPipelineFactory.java | 29 +- .../SecureChatSslContextFactory.java | 106 ------ .../SecureChatTrustManagerFactory.java | 74 ----- .../ssl/ApplicationProtocolSelector.java | 32 ++ .../handler/ssl/JdkSslClientContext.java | 134 ++++++++ .../netty/handler/ssl/JdkSslContext.java | 169 ++++++++++ .../handler/ssl/JdkSslServerContext.java | 152 +++++++++ .../org/jboss/netty/handler/ssl/OpenSsl.java | 2 + .../netty/handler/ssl/OpenSslBufferPool.java | 95 ------ .../handler/ssl/OpenSslContextBuilder.java | 164 --------- .../netty/handler/ssl/OpenSslEngine.java | 173 ++++++---- .../handler/ssl/OpenSslServerContext.java | 231 ++++++++----- .../jboss/netty/handler/ssl/PemReader.java | 137 ++++++++ .../netty/handler/ssl/SslBufferPool.java | 117 +++++-- .../jboss/netty/handler/ssl/SslContext.java | 206 ++++++++++++ .../jboss/netty/handler/ssl/SslHandler.java | 24 +- .../jboss/netty/handler/ssl/SslProvider.java | 25 ++ .../BouncyCastleSelfSignedCertGenerator.java | 58 ++++ .../util/FingerprintTrustManagerFactory.java | 162 +++++++++ .../ssl/util/InsecureTrustManagerFactory.java | 61 ++++ .../util/OpenJdkSelfSignedCertGenerator.java | 69 ++++ .../ssl/util/SelfSignedCertificate.java | 177 ++++++++++ .../ssl/util/SimpleTrustManagerFactory.java | 109 ++++++ .../ssl/util/ThreadLocalInsecureRandom.java | 100 ++++++ .../netty/handler/ssl/util/package-info.java | 20 ++ .../netty/util/internal/EmptyArrays.java | 2 + .../util/internal/ThreadLocalRandom.java | 4 +- .../handler/ssl/NioNioSocketSslEchoTest.java | 39 --- .../ssl/NioNioSocketSslGreetingTest.java | 41 --- .../handler/ssl/NioOioSocketSslEchoTest.java | 40 --- .../handler/ssl/OioNioSocketSslEchoTest.java | 40 --- .../ssl/OioNioSocketSslGreetingTest.java | 41 --- .../handler/ssl/OioOioSocketSslEchoTest.java | 44 --- ...slEchoTest.java => SocketSslEchoTest.java} | 168 +--------- ...ngTest.java => SocketSslGreetingTest.java} | 49 +-- .../jboss/netty/handler/ssl/SslCloseTest.java | 25 +- .../netty/handler/ssl/SslHandlerTest.java | 45 --- ...eTester.java => SslHandshakeRaceTest.java} | 74 ++--- .../org/jboss/netty/handler/ssl/SslTest.java | 120 +++++++ src/test/resources/openssl.crt | 30 -- src/test/resources/openssl.pem | 52 --- 59 files changed, 2408 insertions(+), 1791 deletions(-) create mode 100644 license/LICENSE.bouncycastle.txt delete mode 100644 src/main/java/org/jboss/netty/example/securechat/SecureChatKeyStore.java delete mode 100644 src/main/java/org/jboss/netty/example/securechat/SecureChatSslContextFactory.java delete mode 100644 src/main/java/org/jboss/netty/example/securechat/SecureChatTrustManagerFactory.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/ApplicationProtocolSelector.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/JdkSslClientContext.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/JdkSslContext.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/JdkSslServerContext.java delete mode 100644 src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java delete mode 100644 src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/PemReader.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/SslContext.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/SslProvider.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/FingerprintTrustManagerFactory.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/InsecureTrustManagerFactory.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/SelfSignedCertificate.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/SimpleTrustManagerFactory.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/ThreadLocalInsecureRandom.java create mode 100644 src/main/java/org/jboss/netty/handler/ssl/util/package-info.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java rename src/test/java/org/jboss/netty/handler/ssl/{AbstractSocketSslEchoTest.java => SocketSslEchoTest.java} (53%) rename src/test/java/org/jboss/netty/handler/ssl/{AbstractSocketSslGreetingTest.java => SocketSslGreetingTest.java} (73%) delete mode 100644 src/test/java/org/jboss/netty/handler/ssl/SslHandlerTest.java rename src/test/java/org/jboss/netty/handler/ssl/{SslHandshakeRaceTester.java => SslHandshakeRaceTest.java} (69%) create mode 100644 src/test/java/org/jboss/netty/handler/ssl/SslTest.java delete mode 100644 src/test/resources/openssl.crt delete mode 100644 src/test/resources/openssl.pem diff --git a/NOTICE.txt b/NOTICE.txt index d29f4a4d3e..5b2e21acea 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -50,6 +50,14 @@ zlib in pure Java, which can be obtained at: * HOMEPAGE: * http://www.jcraft.com/jzlib/ +This product contains a modified version of 'Webbit', a Java event based +WebSocket and HTTP server: + + * LICENSE: + * license/LICENSE.webbit.txt (BSD License) + * HOMEPAGE: + * https://github.com/joewalnes/webbit + This product optionally depends on 'Protocol Buffers', Google's data interchange format, which can be obtained at: @@ -58,6 +66,15 @@ interchange format, which can be obtained at: * HOMEPAGE: * http://code.google.com/p/protobuf/ +This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +a temporary self-signed X.509 certificate when the JVM does not provide the +equivalent functionality. It can be obtained at: + + * LICENSE: + * license/LICENSE.bouncycastle.txt (MIT License) + * HOMEPAGE: + * http://www.bouncycastle.org/ + This product optionally depends on 'SLF4J', a simple logging facade for Java, which can be obtained at: @@ -97,12 +114,4 @@ framework implementation, which can be obtained at: * license/LICENSE.felix.txt (Apache License 2.0) * HOMEPAGE: * http://felix.apache.org/ - -This product optionally depends on 'Webbit', a Java event based -WebSocket and HTTP server: - * LICENSE: - * license/LICENSE.webbit.txt (BSD License) - * HOMEPAGE: - * https://github.com/joewalnes/webbit - diff --git a/license/LICENSE.bouncycastle.txt b/license/LICENSE.bouncycastle.txt new file mode 100644 index 0000000000..dbba1dd782 --- /dev/null +++ b/license/LICENSE.bouncycastle.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. + (http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/pom.xml b/pom.xml index dece63c2f5..657f8fddc4 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,19 @@ true + + + org.bouncycastle + bcpkix-jdk15on + 1.50 + compile + true + + @@ -362,6 +375,18 @@ java.io.ObjectStreamClass java.net.IDN + + sun.security.x509.AlgorithmId + sun.security.x509.CertificateAlgorithmId + sun.security.x509.CertificateIssuerName + sun.security.x509.CertificateSerialNumber + sun.security.x509.CertificateSubjectName + sun.security.x509.CertificateValidity + sun.security.x509.CertificateVersion + sun.security.x509.CertificateX509Key + sun.security.x509.X500Name + sun.security.x509.X509CertInfo + sun.security.x509.X509CertImpl diff --git a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServer.java b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServer.java index f6c990a8d9..247950d9d1 100644 --- a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServer.java +++ b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServer.java @@ -15,17 +15,23 @@ */ package org.jboss.netty.example.http.file; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; + import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; - public class HttpStaticFileServer { + private static final boolean useSsl = false; // Set to true to enable SSL. + + private final SslContext sslCtx; private final int port; - public HttpStaticFileServer(int port) { + public HttpStaticFileServer(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -37,19 +43,28 @@ public class HttpStaticFileServer { Executors.newCachedThreadPool())); // Set up the event pipeline factory. - bootstrap.setPipelineFactory(new HttpStaticFileServerPipelineFactory()); + bootstrap.setPipelineFactory(new HttpStaticFileServerPipelineFactory(sslCtx)); // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } - new HttpStaticFileServer(port).run(); + + final SslContext sslCtx; + if (useSsl) { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + } else { + sslCtx = null; + } + + new HttpStaticFileServer(sslCtx, port).run(); } } diff --git a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerPipelineFactory.java index 01a9d88949..6f08a3ff02 100644 --- a/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/http/file/HttpStaticFileServerPipelineFactory.java @@ -15,30 +15,31 @@ */ package org.jboss.netty.example.http.file; -import static org.jboss.netty.channel.Channels.*; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.handler.codec.http.HttpChunkAggregator; import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.ssl.SslContext; import org.jboss.netty.handler.stream.ChunkedWriteHandler; -// Uncomment the following lines if you want HTTPS -//import javax.net.ssl.SSLEngine; -//import org.jboss.netty.example.securechat.SecureChatSslContextFactory; -//import org.jboss.netty.handler.ssl.SslHandler; +import static org.jboss.netty.channel.Channels.*; public class HttpStaticFileServerPipelineFactory implements ChannelPipelineFactory { + + private final SslContext sslCtx; + + public HttpStaticFileServerPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + public ChannelPipeline getPipeline() throws Exception { // Create a default pipeline implementation. ChannelPipeline pipeline = pipeline(); - // Uncomment the following lines if you want HTTPS - //SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - //engine.setUseClientMode(false); - //pipeline.addLast("ssl", new SslHandler(engine)); - + if (sslCtx != null) { + pipeline.addLast("ssl", sslCtx.newHandler()); + } pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); pipeline.addLast("encoder", new HttpResponseEncoder()); diff --git a/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClient.java b/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClient.java index c8f72d795f..ce51f87a02 100644 --- a/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClient.java +++ b/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClient.java @@ -25,6 +25,8 @@ import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpVersion; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.InsecureTrustManagerFactory; import java.net.InetSocketAddress; import java.net.URI; @@ -42,7 +44,7 @@ public class HttpSnoopClient { this.uri = uri; } - public void run() { + public void run() throws Exception { String scheme = uri.getScheme() == null? "http" : uri.getScheme(); String host = uri.getHost() == null? "localhost" : uri.getHost(); int port = uri.getPort(); @@ -59,7 +61,14 @@ public class HttpSnoopClient { return; } - boolean ssl = "https".equalsIgnoreCase(scheme); + // Configure SSL context if necessary. + final boolean ssl = "https".equalsIgnoreCase(scheme); + final SslContext sslCtx; + if (ssl) { + sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); + } else { + sslCtx = null; + } // Configure the client. ClientBootstrap bootstrap = new ClientBootstrap( @@ -68,7 +77,7 @@ public class HttpSnoopClient { Executors.newCachedThreadPool())); // Set up the event pipeline factory. - bootstrap.setPipelineFactory(new HttpSnoopClientPipelineFactory(ssl)); + bootstrap.setPipelineFactory(new HttpSnoopClientPipelineFactory(sslCtx)); // Start the connection attempt. ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); diff --git a/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClientPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClientPipelineFactory.java index 354dd46029..ab5c3d1b31 100644 --- a/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClientPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/http/snoop/HttpSnoopClientPipelineFactory.java @@ -15,23 +15,20 @@ */ package org.jboss.netty.example.http.snoop; -import static org.jboss.netty.channel.Channels.*; - -import javax.net.ssl.SSLEngine; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.handler.codec.http.HttpClientCodec; import org.jboss.netty.handler.codec.http.HttpContentDecompressor; -import org.jboss.netty.handler.ssl.SslHandler; +import org.jboss.netty.handler.ssl.SslContext; + +import static org.jboss.netty.channel.Channels.*; public class HttpSnoopClientPipelineFactory implements ChannelPipelineFactory { - private final boolean ssl; + private final SslContext sslCtx; - public HttpSnoopClientPipelineFactory(boolean ssl) { - this.ssl = ssl; + public HttpSnoopClientPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; } public ChannelPipeline getPipeline() throws Exception { @@ -39,12 +36,8 @@ public class HttpSnoopClientPipelineFactory implements ChannelPipelineFactory { ChannelPipeline pipeline = pipeline(); // Enable HTTPS if necessary. - if (ssl) { - SSLEngine engine = - SecureChatSslContextFactory.getClientContext().createSSLEngine(); - engine.setUseClientMode(true); - - pipeline.addLast("ssl", new SslHandler(engine)); + if (sslCtx != null) { + pipeline.addLast("ssl", sslCtx.newHandler()); } pipeline.addLast("codec", new HttpClientCodec()); diff --git a/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java b/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java index 216cbf4cae..78bad2a804 100644 --- a/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java +++ b/src/main/java/org/jboss/netty/example/http/tunnel/HttpTunnelingClientExample.java @@ -22,14 +22,14 @@ import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.socket.http.HttpTunnelingClientSocketChannelFactory; import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.codec.string.StringEncoder; import org.jboss.netty.handler.logging.LoggingHandler; +import org.jboss.netty.handler.ssl.JdkSslClientContext; +import org.jboss.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.jboss.netty.logging.InternalLogLevel; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.URI; @@ -49,7 +49,7 @@ public class HttpTunnelingClientExample { this.uri = uri; } - public void run() throws IOException { + public void run() throws Exception { String scheme = uri.getScheme() == null? "http" : uri.getScheme(); // Configure the client. @@ -72,7 +72,7 @@ public class HttpTunnelingClientExample { // Configure SSL if necessary if ("https".equals(scheme)) { - b.setOption("sslContext", SecureChatSslContextFactory.getClientContext()); + b.setOption("sslContext", new JdkSslClientContext(InsecureTrustManagerFactory.INSTANCE).context()); } else if (!"http".equals(scheme)) { // Only HTTP and HTTPS are supported. System.err.println("Only HTTP(S) is supported."); diff --git a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClient.java b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClient.java index 64f4da6a72..222a8f3a43 100644 --- a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClient.java +++ b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClient.java @@ -33,6 +33,8 @@ import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestEncoder; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestEncoder.ErrorDataEncoderException; import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; @@ -57,7 +59,7 @@ public class HttpUploadClient { this.filePath = filePath; } - public void run() { + public void run() throws Exception { String postSimple, postFile, get; if (baseUri.endsWith("/")) { postSimple = baseUri + "formpost"; @@ -91,7 +93,14 @@ public class HttpUploadClient { return; } - boolean ssl = "https".equalsIgnoreCase(scheme); + final boolean ssl = "https".equalsIgnoreCase(scheme); + final SslContext sslCtx; + if (ssl) { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + } else { + sslCtx = null; + } URI uriFile; try { @@ -113,7 +122,7 @@ public class HttpUploadClient { Executors.newCachedThreadPool())); // Set up the event pipeline factory. - bootstrap.setPipelineFactory(new HttpUploadClientPipelineFactory(ssl)); + bootstrap.setPipelineFactory(new HttpUploadClientPipelineFactory(sslCtx)); // setup the factory: here using a mixed memory/disk based on size threshold HttpDataFactory factory = new DefaultHttpDataFactory( @@ -225,7 +234,7 @@ public class HttpUploadClient { private static List formpost(ClientBootstrap bootstrap, String host, int port, URI uriSimple, File file, HttpDataFactory factory, - List> headers) { + List> headers) throws ErrorDataEncoderException { // XXX /formpost // Start the connection attempt. ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); @@ -242,17 +251,8 @@ public class HttpUploadClient { HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); // Use the PostBody encoder - HttpPostRequestEncoder bodyRequestEncoder = null; - try { - bodyRequestEncoder = new HttpPostRequestEncoder(factory, - request, false); // false => not multipart - } catch (NullPointerException e) { - // should not be since args are not null - e.printStackTrace(); - } catch (ErrorDataEncoderException e) { - // test if method is a POST method - e.printStackTrace(); - } + HttpPostRequestEncoder bodyRequestEncoder = + new HttpPostRequestEncoder(factory, request, false); // false => not multipart // it is legal to add directly header or cookie into the request until finalize for (Entry entry : headers) { @@ -260,28 +260,15 @@ public class HttpUploadClient { } // add Form attribute - try { - bodyRequestEncoder.addBodyAttribute("getform", "POST"); - bodyRequestEncoder.addBodyAttribute("info", "first value"); - bodyRequestEncoder.addBodyAttribute("secondinfo", "secondvalue ���&"); - bodyRequestEncoder.addBodyAttribute("thirdinfo", textArea); - bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false); - bodyRequestEncoder.addBodyAttribute("Send", "Send"); - } catch (NullPointerException e) { - // should not be since not null args - e.printStackTrace(); - } catch (ErrorDataEncoderException e) { - // if an encoding error occurs - e.printStackTrace(); - } + bodyRequestEncoder.addBodyAttribute("getform", "POST"); + bodyRequestEncoder.addBodyAttribute("info", "first value"); + bodyRequestEncoder.addBodyAttribute("secondinfo", "secondvalue ���&"); + bodyRequestEncoder.addBodyAttribute("thirdinfo", textArea); + bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false); // finalize request - try { - request = bodyRequestEncoder.finalizeRequest(); - } catch (ErrorDataEncoderException e) { - // if an encoding error occurs - e.printStackTrace(); - } + request = bodyRequestEncoder.finalizeRequest(); + // Create the bodylist to be reused on the last version with Multipart support List bodylist = bodyRequestEncoder.getBodyListAttributes(); @@ -311,7 +298,7 @@ public class HttpUploadClient { */ private static void formpostmultipart(ClientBootstrap bootstrap, String host, int port, URI uriFile, HttpDataFactory factory, - List> headers, List bodylist) { + List> headers, List bodylist) throws ErrorDataEncoderException { // XXX /formpostmultipart // Start the connection attempt. ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); @@ -328,17 +315,8 @@ public class HttpUploadClient { HttpVersion.HTTP_1_1, HttpMethod.POST, uriFile.toASCIIString()); // Use the PostBody encoder - HttpPostRequestEncoder bodyRequestEncoder = null; - try { - bodyRequestEncoder = new HttpPostRequestEncoder(factory, - request, true); // true => multipart - } catch (NullPointerException e) { - // should not be since no null args - e.printStackTrace(); - } catch (ErrorDataEncoderException e) { - // test if method is a POST method - e.printStackTrace(); - } + HttpPostRequestEncoder bodyRequestEncoder = + new HttpPostRequestEncoder(factory, request, true); // true => multipart // it is legal to add directly header or cookie into the request until finalize for (Entry entry : headers) { @@ -346,23 +324,10 @@ public class HttpUploadClient { } // add Form attribute from previous request in formpost() - try { - bodyRequestEncoder.setBodyHttpDatas(bodylist); - } catch (NullPointerException e1) { - // should not be since previously created - e1.printStackTrace(); - } catch (ErrorDataEncoderException e1) { - // again should not be since previously encoded (except if an error occurs previously) - e1.printStackTrace(); - } + bodyRequestEncoder.setBodyHttpDatas(bodylist); // finalize request - try { - bodyRequestEncoder.finalizeRequest(); - } catch (ErrorDataEncoderException e) { - // if an encoding error occurs - e.printStackTrace(); - } + bodyRequestEncoder.finalizeRequest(); // send request channel.write(request); @@ -379,7 +344,7 @@ public class HttpUploadClient { channel.getCloseFuture().awaitUninterruptibly(); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { if (args.length != 2) { logger.error( "Usage: " + HttpUploadClient.class.getSimpleName() + @@ -983,5 +948,4 @@ public class HttpUploadClient { "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n"; - } diff --git a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClientPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClientPipelineFactory.java index e7bf7ec2b2..e18f8763a2 100644 --- a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClientPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadClientPipelineFactory.java @@ -15,23 +15,21 @@ */ package org.jboss.netty.example.http.upload; -import static org.jboss.netty.channel.Channels.*; - -import javax.net.ssl.SSLEngine; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.handler.codec.http.HttpClientCodec; import org.jboss.netty.handler.codec.http.HttpContentDecompressor; +import org.jboss.netty.handler.ssl.SslContext; import org.jboss.netty.handler.ssl.SslHandler; import org.jboss.netty.handler.stream.ChunkedWriteHandler; -public class HttpUploadClientPipelineFactory implements ChannelPipelineFactory { - private final boolean ssl; +import static org.jboss.netty.channel.Channels.*; - public HttpUploadClientPipelineFactory(boolean ssl) { - this.ssl = ssl; +public class HttpUploadClientPipelineFactory implements ChannelPipelineFactory { + private final SslContext sslCtx; + + public HttpUploadClientPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; } public ChannelPipeline getPipeline() throws Exception { @@ -39,12 +37,8 @@ public class HttpUploadClientPipelineFactory implements ChannelPipelineFactory { ChannelPipeline pipeline = pipeline(); // Enable HTTPS if necessary. - if (ssl) { - SSLEngine engine = - SecureChatSslContextFactory.getClientContext().createSSLEngine(); - engine.setUseClientMode(true); - - SslHandler handler = new SslHandler(engine); + if (sslCtx != null) { + SslHandler handler = sslCtx.newHandler(); handler.setIssueHandshake(true); pipeline.addLast("ssl", handler); } diff --git a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServer.java b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServer.java index 1a7fba3e29..577b614d02 100644 --- a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServer.java +++ b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServer.java @@ -15,18 +15,23 @@ */ package org.jboss.netty.example.http.upload; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; + import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; - public class HttpUploadServer { - private final int port; - public static boolean isSSL; + private static final boolean useSsl = false; // Set to true to enable SSL. - public HttpUploadServer(int port) { + private final SslContext sslCtx; + private final int port; + + public HttpUploadServer(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -38,22 +43,28 @@ public class HttpUploadServer { Executors.newCachedThreadPool())); // Set up the event pipeline factory. - bootstrap.setPipelineFactory(new HttpUploadServerPipelineFactory()); + bootstrap.setPipelineFactory(new HttpUploadServerPipelineFactory(sslCtx)); // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } - if (args.length > 1) { - isSSL = true; + + SslContext sslCtx; + if (useSsl) { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + } else { + sslCtx = null; } - new HttpUploadServer(port).run(); + + new HttpUploadServer(sslCtx, port).run(); } } diff --git a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServerPipelineFactory.java b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServerPipelineFactory.java index fab5775609..8dbdcdb6a7 100644 --- a/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServerPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/http/upload/HttpUploadServerPipelineFactory.java @@ -15,29 +15,32 @@ */ package org.jboss.netty.example.http.upload; -import static org.jboss.netty.channel.Channels.*; - -import javax.net.ssl.SSLEngine; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.handler.codec.http.HttpContentCompressor; import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.ssl.SslContext; import org.jboss.netty.handler.ssl.SslHandler; +import static org.jboss.netty.channel.Channels.*; + public class HttpUploadServerPipelineFactory implements ChannelPipelineFactory { + + private final SslContext sslCtx; + + public HttpUploadServerPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + public ChannelPipeline getPipeline() throws Exception { // Create a default pipeline implementation. ChannelPipeline pipeline = pipeline(); - if (HttpUploadServer.isSSL) { - SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - engine.setUseClientMode(false); - SslHandler handler = new SslHandler(engine); - handler.setIssueHandshake(true); - pipeline.addLast("ssl", handler); + if (sslCtx != null) { + SslHandler handler = sslCtx.newHandler(); + handler.setIssueHandshake(true); + pipeline.addLast("ssl", handler); } pipeline.addLast("decoder", new HttpRequestDecoder()); diff --git a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java index 20747c1299..4d1ceef733 100644 --- a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java +++ b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServer.java @@ -15,14 +15,16 @@ */ package org.jboss.netty.example.portunification; -import java.net.InetSocketAddress; -import java.util.concurrent.Executors; - import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; /** * Serves two protocols (HTTP and Factorial) using only one port, enabling @@ -33,9 +35,11 @@ import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; */ public class PortUnificationServer { + private final SslContext sslCtx; private final int port; - public PortUnificationServer(int port) { + public PortUnificationServer(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -49,7 +53,7 @@ public class PortUnificationServer { // Set up the event pipeline factory. bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { - return Channels.pipeline(new PortUnificationServerHandler()); + return Channels.pipeline(new PortUnificationServerHandler(sslCtx)); } }); @@ -64,6 +68,12 @@ public class PortUnificationServer { } else { port = 8080; } - new PortUnificationServer(port).run(); + + // Configure SSL context + SelfSignedCertificate ssc = new SelfSignedCertificate(); + final SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + + // Start the server. + new PortUnificationServer(sslCtx, port).run(); } } diff --git a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java index 52e1d098bf..2286c2487e 100644 --- a/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java +++ b/src/main/java/org/jboss/netty/example/portunification/PortUnificationServerHandler.java @@ -15,8 +15,6 @@ */ package org.jboss.netty.example.portunification; -import javax.net.ssl.SSLEngine; - import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; @@ -25,7 +23,6 @@ import org.jboss.netty.example.factorial.BigIntegerDecoder; import org.jboss.netty.example.factorial.FactorialServerHandler; import org.jboss.netty.example.factorial.NumberEncoder; import org.jboss.netty.example.http.snoop.HttpSnoopServerHandler; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.handler.codec.compression.ZlibDecoder; import org.jboss.netty.handler.codec.compression.ZlibEncoder; import org.jboss.netty.handler.codec.compression.ZlibWrapper; @@ -33,6 +30,7 @@ import org.jboss.netty.handler.codec.frame.FrameDecoder; import org.jboss.netty.handler.codec.http.HttpContentCompressor; import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.ssl.SslContext; import org.jboss.netty.handler.ssl.SslHandler; /** @@ -41,14 +39,16 @@ import org.jboss.netty.handler.ssl.SslHandler; */ public class PortUnificationServerHandler extends FrameDecoder { + private final SslContext sslCtx; private final boolean detectSsl; private final boolean detectGzip; - public PortUnificationServerHandler() { - this(true, true); + public PortUnificationServerHandler(SslContext sslCtx) { + this(sslCtx, true, true); } - private PortUnificationServerHandler(boolean detectSsl, boolean detectGzip) { + private PortUnificationServerHandler(SslContext sslCtx, boolean detectSsl, boolean detectGzip) { + this.sslCtx = sslCtx; this.detectSsl = detectSsl; this.detectGzip = detectGzip; } @@ -116,13 +116,8 @@ public class PortUnificationServerHandler extends FrameDecoder { private void enableSsl(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.getPipeline(); - - SSLEngine engine = - SecureChatSslContextFactory.getServerContext().createSSLEngine(); - engine.setUseClientMode(false); - - p.addLast("ssl", new SslHandler(engine)); - p.addLast("unificationA", new PortUnificationServerHandler(false, detectGzip)); + p.addLast("ssl", sslCtx.newHandler()); + p.addLast("unificationA", new PortUnificationServerHandler(sslCtx, false, detectGzip)); p.remove(this); } @@ -130,7 +125,7 @@ public class PortUnificationServerHandler extends FrameDecoder { ChannelPipeline p = ctx.getPipeline(); p.addLast("gzipdeflater", new ZlibEncoder(ZlibWrapper.GZIP)); p.addLast("gzipinflater", new ZlibDecoder(ZlibWrapper.GZIP)); - p.addLast("unificationB", new PortUnificationServerHandler(detectSsl, false)); + p.addLast("unificationB", new PortUnificationServerHandler(sslCtx, detectSsl, false)); p.remove(this); } diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatClient.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatClient.java index 40b8a40423..ff7ffcc734 100644 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatClient.java +++ b/src/main/java/org/jboss/netty/example/securechat/SecureChatClient.java @@ -20,6 +20,8 @@ import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.example.telnet.TelnetClient; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.InsecureTrustManagerFactory; import java.io.BufferedReader; import java.io.IOException; @@ -32,10 +34,12 @@ import java.util.concurrent.Executors; */ public class SecureChatClient { + private final SslContext sslCtx; private final String host; private final int port; - public SecureChatClient(String host, int port) { + public SecureChatClient(SslContext sslCtx, String host, int port) { + this.sslCtx = sslCtx; this.host = host; this.port = port; } @@ -48,7 +52,7 @@ public class SecureChatClient { Executors.newCachedThreadPool())); // Configure the pipeline factory. - bootstrap.setPipelineFactory(new SecureChatClientPipelineFactory()); + bootstrap.setPipelineFactory(new SecureChatClientPipelineFactory(sslCtx)); // Start the connection attempt. ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); @@ -107,6 +111,9 @@ public class SecureChatClient { String host = args[0]; int port = Integer.parseInt(args[1]); - new SecureChatClient(host, port).run(); + // Configure the SSL context. + SslContext sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); + + new SecureChatClient(sslCtx, host, port).run(); } } diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatClientPipelineFactory.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatClientPipelineFactory.java index eaef075fb1..83a6db05a6 100644 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatClientPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/securechat/SecureChatClientPipelineFactory.java @@ -15,23 +15,26 @@ */ package org.jboss.netty.example.securechat; -import static org.jboss.netty.channel.Channels.*; - -import javax.net.ssl.SSLEngine; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder; import org.jboss.netty.handler.codec.frame.Delimiters; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.codec.string.StringEncoder; -import org.jboss.netty.handler.ssl.SslHandler; +import org.jboss.netty.handler.ssl.SslContext; + +import static org.jboss.netty.channel.Channels.*; /** * Creates a newly configured {@link ChannelPipeline} for a new channel. */ -public class SecureChatClientPipelineFactory implements - ChannelPipelineFactory { +public class SecureChatClientPipelineFactory implements ChannelPipelineFactory { + + private final SslContext sslCtx; + + public SecureChatClientPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; + } public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = pipeline(); @@ -41,12 +44,7 @@ public class SecureChatClientPipelineFactory implements // and accept any invalid certificates in the client side. // You will need something more complicated to identify both // and server in the real world. - - SSLEngine engine = - SecureChatSslContextFactory.getClientContext().createSSLEngine(); - engine.setUseClientMode(true); - - pipeline.addLast("ssl", new SslHandler(engine)); + pipeline.addLast("ssl", sslCtx.newHandler()); // On top of the SSL handler, add the text line codec. pipeline.addLast("framer", new DelimiterBasedFrameDecoder( diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatKeyStore.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatKeyStore.java deleted file mode 100644 index e871d0e09d..0000000000 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatKeyStore.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.example.securechat; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * A bogus key store which provides all the required information to - * create an example SSL connection. - * - * To generate a bogus key store: - *
- * keytool  -genkey -alias securechat -keysize 2048 -validity 36500
- *          -keyalg RSA -dname "CN=securechat"
- *          -keypass secret -storepass secret
- *          -keystore cert.jks
- * 
- */ -public final class SecureChatKeyStore { - private static final short[] DATA = { - 0xfe, 0xed, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x00, 0x00, 0x01, 0x1a, 0x9f, 0x57, 0xa5, - 0x27, 0x00, 0x00, 0x01, 0x9a, 0x30, 0x82, 0x01, - 0x96, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, - 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05, - 0x00, 0x04, 0x82, 0x01, 0x82, 0x48, 0x6d, 0xcf, - 0x16, 0xb5, 0x50, 0x95, 0x36, 0xbf, 0x47, 0x27, - 0x50, 0x58, 0x0d, 0xa2, 0x52, 0x7e, 0x25, 0xab, - 0x14, 0x1a, 0x26, 0x5e, 0x2d, 0x8a, 0x23, 0x90, - 0x60, 0x7f, 0x12, 0x20, 0x56, 0xd1, 0x43, 0xa2, - 0x6b, 0x47, 0x5d, 0xed, 0x9d, 0xd4, 0xe5, 0x83, - 0x28, 0x89, 0xc2, 0x16, 0x4c, 0x76, 0x06, 0xad, - 0x8e, 0x8c, 0x29, 0x1a, 0x9b, 0x0f, 0xdd, 0x60, - 0x4b, 0xb4, 0x62, 0x82, 0x9e, 0x4a, 0x63, 0x83, - 0x2e, 0xd2, 0x43, 0x78, 0xc2, 0x32, 0x1f, 0x60, - 0xa9, 0x8a, 0x7f, 0x0f, 0x7c, 0xa6, 0x1d, 0xe6, - 0x92, 0x9e, 0x52, 0xc7, 0x7d, 0xbb, 0x35, 0x3b, - 0xaa, 0x89, 0x73, 0x4c, 0xfb, 0x99, 0x54, 0x97, - 0x99, 0x28, 0x6e, 0x66, 0x5b, 0xf7, 0x9b, 0x7e, - 0x6d, 0x8a, 0x2f, 0xfa, 0xc3, 0x1e, 0x71, 0xb9, - 0xbd, 0x8f, 0xc5, 0x63, 0x25, 0x31, 0x20, 0x02, - 0xff, 0x02, 0xf0, 0xc9, 0x2c, 0xdd, 0x3a, 0x10, - 0x30, 0xab, 0xe5, 0xad, 0x3d, 0x1a, 0x82, 0x77, - 0x46, 0xed, 0x03, 0x38, 0xa4, 0x73, 0x6d, 0x36, - 0x36, 0x33, 0x70, 0xb2, 0x63, 0x20, 0xca, 0x03, - 0xbf, 0x5a, 0xf4, 0x7c, 0x35, 0xf0, 0x63, 0x1a, - 0x12, 0x33, 0x12, 0x58, 0xd9, 0xa2, 0x63, 0x6b, - 0x63, 0x82, 0x41, 0x65, 0x70, 0x37, 0x4b, 0x99, - 0x04, 0x9f, 0xdd, 0x5e, 0x07, 0x01, 0x95, 0x9f, - 0x36, 0xe8, 0xc3, 0x66, 0x2a, 0x21, 0x69, 0x68, - 0x40, 0xe6, 0xbc, 0xbb, 0x85, 0x81, 0x21, 0x13, - 0xe6, 0xa4, 0xcf, 0xd3, 0x67, 0xe3, 0xfd, 0x75, - 0xf0, 0xdf, 0x83, 0xe0, 0xc5, 0x36, 0x09, 0xac, - 0x1b, 0xd4, 0xf7, 0x2a, 0x23, 0x57, 0x1c, 0x5c, - 0x0f, 0xf4, 0xcf, 0xa2, 0xcf, 0xf5, 0xbd, 0x9c, - 0x69, 0x98, 0x78, 0x3a, 0x25, 0xe4, 0xfd, 0x85, - 0x11, 0xcc, 0x7d, 0xef, 0xeb, 0x74, 0x60, 0xb1, - 0xb7, 0xfb, 0x1f, 0x0e, 0x62, 0xff, 0xfe, 0x09, - 0x0a, 0xc3, 0x80, 0x2f, 0x10, 0x49, 0x89, 0x78, - 0xd2, 0x08, 0xfa, 0x89, 0x22, 0x45, 0x91, 0x21, - 0xbc, 0x90, 0x3e, 0xad, 0xb3, 0x0a, 0xb4, 0x0e, - 0x1c, 0xa1, 0x93, 0x92, 0xd8, 0x72, 0x07, 0x54, - 0x60, 0xe7, 0x91, 0xfc, 0xd9, 0x3c, 0xe1, 0x6f, - 0x08, 0xe4, 0x56, 0xf6, 0x0b, 0xb0, 0x3c, 0x39, - 0x8a, 0x2d, 0x48, 0x44, 0x28, 0x13, 0xca, 0xe9, - 0xf7, 0xa3, 0xb6, 0x8a, 0x5f, 0x31, 0xa9, 0x72, - 0xf2, 0xde, 0x96, 0xf2, 0xb1, 0x53, 0xb1, 0x3e, - 0x24, 0x57, 0xfd, 0x18, 0x45, 0x1f, 0xc5, 0x33, - 0x1b, 0xa4, 0xe8, 0x21, 0xfa, 0x0e, 0xb2, 0xb9, - 0xcb, 0xc7, 0x07, 0x41, 0xdd, 0x2f, 0xb6, 0x6a, - 0x23, 0x18, 0xed, 0xc1, 0xef, 0xe2, 0x4b, 0xec, - 0xc9, 0xba, 0xfb, 0x46, 0x43, 0x90, 0xd7, 0xb5, - 0x68, 0x28, 0x31, 0x2b, 0x8d, 0xa8, 0x51, 0x63, - 0xf7, 0x53, 0x99, 0x19, 0x68, 0x85, 0x66, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35, - 0x30, 0x39, 0x00, 0x00, 0x02, 0x3a, 0x30, 0x82, - 0x02, 0x36, 0x30, 0x82, 0x01, 0xe0, 0xa0, 0x03, - 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, 0xf1, - 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x30, 0x81, 0xa0, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 0x52, - 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67, - 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b, - 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d, - 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, 0x68, - 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20, - 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31, - 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, - 0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, - 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, - 0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, - 0x31, 0x33, 0x38, 0x5a, 0x18, 0x0f, 0x32, 0x31, - 0x38, 0x37, 0x31, 0x31, 0x32, 0x34, 0x30, 0x35, - 0x34, 0x31, 0x33, 0x38, 0x5a, 0x30, 0x81, 0xa0, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, - 0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, - 0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x04, 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, - 0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, - 0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x31, 0x30, - 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, - 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74, - 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, - 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74, - 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, - 0x00, 0xc3, 0xe3, 0x5e, 0x41, 0xa7, 0x87, 0x11, - 0x00, 0x42, 0x2a, 0xb0, 0x4b, 0xed, 0xb2, 0xe0, - 0x23, 0xdb, 0xb1, 0x3d, 0x58, 0x97, 0x35, 0x60, - 0x0b, 0x82, 0x59, 0xd3, 0x00, 0xea, 0xd4, 0x61, - 0xb8, 0x79, 0x3f, 0xb6, 0x3c, 0x12, 0x05, 0x93, - 0x2e, 0x9a, 0x59, 0x68, 0x14, 0x77, 0x3a, 0xc8, - 0x50, 0x25, 0x57, 0xa4, 0x49, 0x18, 0x63, 0x41, - 0xf0, 0x2d, 0x28, 0xec, 0x06, 0xfb, 0xb4, 0x9f, - 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00, - 0x65, 0x6c, 0x30, 0x01, 0xc2, 0x8e, 0x3e, 0xcb, - 0xb3, 0x77, 0x48, 0xe9, 0x66, 0x61, 0x9a, 0x40, - 0x86, 0xaf, 0xf6, 0x03, 0xeb, 0xba, 0x6a, 0xf2, - 0xfd, 0xe2, 0xaf, 0x36, 0x5e, 0x7b, 0xaa, 0x22, - 0x04, 0xdd, 0x2c, 0x20, 0xc4, 0xfc, 0xdd, 0xd0, - 0x82, 0x20, 0x1c, 0x3d, 0xd7, 0x9e, 0x5e, 0x5c, - 0x92, 0x5a, 0x76, 0x71, 0x28, 0xf5, 0x07, 0x7d, - 0xa2, 0x81, 0xba, 0x77, 0x9f, 0x2a, 0xd9, 0x44, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x6d, 0x79, - 0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x1a, 0x9f, - 0x5b, 0x56, 0xa0, 0x00, 0x00, 0x01, 0x99, 0x30, - 0x82, 0x01, 0x95, 0x30, 0x0e, 0x06, 0x0a, 0x2b, - 0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, - 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x81, 0x29, - 0xa8, 0xb6, 0x08, 0x0c, 0x85, 0x75, 0x3e, 0xdd, - 0xb5, 0xe5, 0x1a, 0x87, 0x68, 0xd1, 0x90, 0x4b, - 0x29, 0x31, 0xee, 0x90, 0xbc, 0x9d, 0x73, 0xa0, - 0x3f, 0xe9, 0x0b, 0xa4, 0xef, 0x30, 0x9b, 0x36, - 0x9a, 0xb2, 0x54, 0x77, 0x81, 0x07, 0x4b, 0xaa, - 0xa5, 0x77, 0x98, 0xe1, 0xeb, 0xb5, 0x7c, 0x4e, - 0x48, 0xd5, 0x08, 0xfc, 0x2c, 0x36, 0xe2, 0x65, - 0x03, 0xac, 0xe5, 0xf3, 0x96, 0xb7, 0xd0, 0xb5, - 0x3b, 0x92, 0xe4, 0x14, 0x05, 0x7a, 0x6a, 0x92, - 0x56, 0xfe, 0x4e, 0xab, 0xd3, 0x0e, 0x32, 0x04, - 0x22, 0x22, 0x74, 0x47, 0x7d, 0xec, 0x21, 0x99, - 0x30, 0x31, 0x64, 0x46, 0x64, 0x9b, 0xc7, 0x13, - 0xbf, 0xbe, 0xd0, 0x31, 0x49, 0xe7, 0x3c, 0xbf, - 0xba, 0xb1, 0x20, 0xf9, 0x42, 0xf4, 0xa9, 0xa9, - 0xe5, 0x13, 0x65, 0x32, 0xbf, 0x7c, 0xcc, 0x91, - 0xd3, 0xfd, 0x24, 0x47, 0x0b, 0xe5, 0x53, 0xad, - 0x50, 0x30, 0x56, 0xd1, 0xfa, 0x9c, 0x37, 0xa8, - 0xc1, 0xce, 0xf6, 0x0b, 0x18, 0xaa, 0x7c, 0xab, - 0xbd, 0x1f, 0xdf, 0xe4, 0x80, 0xb8, 0xa7, 0xe0, - 0xad, 0x7d, 0x50, 0x74, 0xf1, 0x98, 0x78, 0xbc, - 0x58, 0xb9, 0xc2, 0x52, 0xbe, 0xd2, 0x5b, 0x81, - 0x94, 0x83, 0x8f, 0xb9, 0x4c, 0xee, 0x01, 0x2b, - 0x5e, 0xc9, 0x6e, 0x9b, 0xf5, 0x63, 0x69, 0xe4, - 0xd8, 0x0b, 0x47, 0xd8, 0xfd, 0xd8, 0xe0, 0xed, - 0xa8, 0x27, 0x03, 0x74, 0x1e, 0x5d, 0x32, 0xe6, - 0x5c, 0x63, 0xc2, 0xfb, 0x3f, 0xee, 0xb4, 0x13, - 0xc6, 0x0e, 0x6e, 0x74, 0xe0, 0x22, 0xac, 0xce, - 0x79, 0xf9, 0x43, 0x68, 0xc1, 0x03, 0x74, 0x2b, - 0xe1, 0x18, 0xf8, 0x7f, 0x76, 0x9a, 0xea, 0x82, - 0x3f, 0xc2, 0xa6, 0xa7, 0x4c, 0xfe, 0xae, 0x29, - 0x3b, 0xc1, 0x10, 0x7c, 0xd5, 0x77, 0x17, 0x79, - 0x5f, 0xcb, 0xad, 0x1f, 0xd8, 0xa1, 0xfd, 0x90, - 0xe1, 0x6b, 0xb2, 0xef, 0xb9, 0x41, 0x26, 0xa4, - 0x0b, 0x4f, 0xc6, 0x83, 0x05, 0x6f, 0xf0, 0x64, - 0x40, 0xe1, 0x44, 0xc4, 0xf9, 0x40, 0x2b, 0x3b, - 0x40, 0xdb, 0xaf, 0x35, 0xa4, 0x9b, 0x9f, 0xc4, - 0x74, 0x07, 0xe5, 0x18, 0x60, 0xc5, 0xfe, 0x15, - 0x0e, 0x3a, 0x25, 0x2a, 0x11, 0xee, 0x78, 0x2f, - 0xb8, 0xd1, 0x6e, 0x4e, 0x3c, 0x0a, 0xb5, 0xb9, - 0x40, 0x86, 0x27, 0x6d, 0x8f, 0x53, 0xb7, 0x77, - 0x36, 0xec, 0x5d, 0xed, 0x32, 0x40, 0x43, 0x82, - 0xc3, 0x52, 0x58, 0xc4, 0x26, 0x39, 0xf3, 0xb3, - 0xad, 0x58, 0xab, 0xb7, 0xf7, 0x8e, 0x0e, 0xba, - 0x8e, 0x78, 0x9d, 0xbf, 0x58, 0x34, 0xbd, 0x77, - 0x73, 0xa6, 0x50, 0x55, 0x00, 0x60, 0x26, 0xbf, - 0x6d, 0xb4, 0x98, 0x8a, 0x18, 0x83, 0x89, 0xf8, - 0xcd, 0x0d, 0x49, 0x06, 0xae, 0x51, 0x6e, 0xaf, - 0xbd, 0xe2, 0x07, 0x13, 0xd8, 0x64, 0xcc, 0xbf, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, - 0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x34, 0x30, - 0x82, 0x02, 0x30, 0x30, 0x82, 0x01, 0xda, 0xa0, - 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, - 0xf2, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, - 0x00, 0x30, 0x81, 0x9d, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, - 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, - 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, - 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, - 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, - 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, - 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x31, - 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, - 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, - 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, - 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, 0x38, 0x30, - 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x35, 0x34, - 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37, - 0x31, 0x31, 0x32, 0x33, 0x30, 0x35, 0x34, 0x35, - 0x34, 0x30, 0x5a, 0x30, 0x81, 0x9d, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, - 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, - 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, - 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, - 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, - 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, - 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, - 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, - 0x55, 0x04, 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, - 0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, - 0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, - 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, - 0x48, 0x02, 0x41, 0x00, 0x95, 0xb3, 0x47, 0x17, - 0x95, 0x0f, 0x57, 0xcf, 0x66, 0x72, 0x0a, 0x7e, - 0x5b, 0x54, 0xea, 0x8c, 0x6f, 0x79, 0xde, 0x94, - 0xac, 0x0b, 0x5a, 0xd4, 0xd6, 0x1b, 0x58, 0x12, - 0x1a, 0x16, 0x3d, 0xfe, 0xdf, 0xa5, 0x2b, 0x86, - 0xbc, 0x64, 0xd4, 0x80, 0x1e, 0x3f, 0xf9, 0xe2, - 0x04, 0x03, 0x79, 0x9b, 0xc1, 0x5c, 0xf0, 0xf1, - 0xf3, 0xf1, 0xe3, 0xbf, 0x3f, 0xc0, 0x1f, 0xdd, - 0xdb, 0xc0, 0x5b, 0x21, 0x02, 0x03, 0x01, 0x00, - 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x03, 0x41, 0x00, 0x02, 0xd7, 0xdd, 0xbd, 0x0c, - 0x8e, 0x21, 0x20, 0xef, 0x9e, 0x4f, 0x1f, 0xf5, - 0x49, 0xf1, 0xae, 0x58, 0x9b, 0x94, 0x3a, 0x1f, - 0x70, 0x33, 0xf0, 0x9b, 0xbb, 0xe9, 0xc0, 0xf3, - 0x72, 0xcb, 0xde, 0xb6, 0x56, 0x72, 0xcc, 0x1c, - 0xf0, 0xd6, 0x5a, 0x2a, 0xbc, 0xa1, 0x7e, 0x23, - 0x83, 0xe9, 0xe7, 0xcf, 0x9e, 0xa5, 0xf9, 0xcc, - 0xc2, 0x61, 0xf4, 0xdb, 0x40, 0x93, 0x1d, 0x63, - 0x8a, 0x50, 0x4c, 0x11, 0x39, 0xb1, 0x91, 0xc1, - 0xe6, 0x9d, 0xd9, 0x1a, 0x62, 0x1b, 0xb8, 0xd3, - 0xd6, 0x9a, 0x6d, 0xb9, 0x8e, 0x15, 0x51 }; - - public static InputStream asInputStream() { - byte[] data = new byte[DATA.length]; - for (int i = 0; i < data.length; i ++) { - data[i] = (byte) DATA[i]; - } - return new ByteArrayInputStream(data); - } - - public static char[] getCertificatePassword() { - return "secret".toCharArray(); - } - - public static char[] getKeyStorePassword() { - return "secret".toCharArray(); - } - - private SecureChatKeyStore() { - // Unused - } -} diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatServer.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatServer.java index b5f9640613..db8c62ecfd 100644 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatServer.java +++ b/src/main/java/org/jboss/netty/example/securechat/SecureChatServer.java @@ -15,21 +15,25 @@ */ package org.jboss.netty.example.securechat; -import java.net.InetSocketAddress; -import java.util.concurrent.Executors; - import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.example.telnet.TelnetServer; +import org.jboss.netty.handler.ssl.SslContext; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; /** * Simple SSL chat server modified from {@link TelnetServer}. */ public class SecureChatServer { + private final SslContext sslCtx; private final int port; - public SecureChatServer(int port) { + public SecureChatServer(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -41,7 +45,7 @@ public class SecureChatServer { Executors.newCachedThreadPool())); // Configure the pipeline factory. - bootstrap.setPipelineFactory(new SecureChatServerPipelineFactory()); + bootstrap.setPipelineFactory(new SecureChatServerPipelineFactory(sslCtx)); // Bind and start to accept incoming connections. bootstrap.bind(new InetSocketAddress(port)); @@ -54,6 +58,9 @@ public class SecureChatServer { } else { port = 8443; } - new SecureChatServer(port).run(); + + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + new SecureChatServer(sslCtx, port).run(); } } diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatServerPipelineFactory.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatServerPipelineFactory.java index 954b581e78..5303b7bc28 100644 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatServerPipelineFactory.java +++ b/src/main/java/org/jboss/netty/example/securechat/SecureChatServerPipelineFactory.java @@ -15,41 +15,36 @@ */ package org.jboss.netty.example.securechat; -import static org.jboss.netty.channel.Channels.*; - -import javax.net.ssl.SSLEngine; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder; import org.jboss.netty.handler.codec.frame.Delimiters; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.codec.string.StringEncoder; -import org.jboss.netty.handler.ssl.SslHandler; +import org.jboss.netty.handler.ssl.SslContext; + +import static org.jboss.netty.channel.Channels.*; /** * Creates a newly configured {@link ChannelPipeline} for a new channel. */ -public class SecureChatServerPipelineFactory implements - ChannelPipelineFactory { +public class SecureChatServerPipelineFactory implements ChannelPipelineFactory { + + private final SslContext sslCtx; + + public SecureChatServerPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; + } public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = pipeline(); // Add SSL handler first to encrypt and decrypt everything. - // In this example, we use a bogus certificate in the server side + // In this example, we use a self-signed certificate in the server side // and accept any invalid certificates in the client side. // You will need something more complicated to identify both // and server in the real world. - // - // Read SecureChatSslContextFactory - // if you need client certificate authentication. - - SSLEngine engine = - SecureChatSslContextFactory.getServerContext().createSSLEngine(); - engine.setUseClientMode(false); - - pipeline.addLast("ssl", new SslHandler(engine)); + pipeline.addLast("ssl", sslCtx.newHandler()); // On top of the SSL handler, add the text line codec. pipeline.addLast("framer", new DelimiterBasedFrameDecoder( diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatSslContextFactory.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatSslContextFactory.java deleted file mode 100644 index 2a32b50d38..0000000000 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatSslContextFactory.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.example.securechat; - -import org.jboss.netty.handler.ssl.SslHandler; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.Security; - -/** - * Creates a bogus {@link SSLContext}. A client-side context created by this - * factory accepts any certificate even if it is invalid. A server-side context - * created by this factory sends a bogus certificate defined in {@link SecureChatKeyStore}. - *

- * You will have to create your context differently in a real world application. - * - *

Client Certificate Authentication

- * - * To enable client certificate authentication: - *
    - *
  • Enable client authentication on the server side by calling - * {@link SSLEngine#setNeedClientAuth(boolean)} before creating - * {@link SslHandler}.
  • - *
  • When initializing an {@link SSLContext} on the client side, - * specify the {@link KeyManager} that contains the client certificate as - * the first argument of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}.
  • - *
  • When initializing an {@link SSLContext} on the server side, - * specify the proper {@link TrustManager} as the second argument of - * {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} - * to validate the client certificate.
  • - *
- */ -public final class SecureChatSslContextFactory { - - private static final String PROTOCOL = "TLS"; - private static final SSLContext SERVER_CONTEXT; - private static final SSLContext CLIENT_CONTEXT; - - static { - String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); - if (algorithm == null) { - algorithm = "SunX509"; - } - - SSLContext serverContext; - SSLContext clientContext; - try { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(SecureChatKeyStore.asInputStream(), - SecureChatKeyStore.getKeyStorePassword()); - - // Set up key manager factory to use our key store - KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); - kmf.init(ks, SecureChatKeyStore.getCertificatePassword()); - - // Initialize the SSLContext to work with our key managers. - serverContext = SSLContext.getInstance(PROTOCOL); - serverContext.init(kmf.getKeyManagers(), null, null); - } catch (Exception e) { - throw new Error( - "Failed to initialize the server-side SSLContext", e); - } - - try { - clientContext = SSLContext.getInstance(PROTOCOL); - clientContext.init(null, SecureChatTrustManagerFactory.getTrustManagers(), null); - } catch (Exception e) { - throw new Error( - "Failed to initialize the client-side SSLContext", e); - } - - SERVER_CONTEXT = serverContext; - CLIENT_CONTEXT = clientContext; - } - - public static SSLContext getServerContext() { - return SERVER_CONTEXT; - } - - public static SSLContext getClientContext() { - return CLIENT_CONTEXT; - } - - private SecureChatSslContextFactory() { - // Unused - } -} diff --git a/src/main/java/org/jboss/netty/example/securechat/SecureChatTrustManagerFactory.java b/src/main/java/org/jboss/netty/example/securechat/SecureChatTrustManagerFactory.java deleted file mode 100644 index 4f460657f2..0000000000 --- a/src/main/java/org/jboss/netty/example/securechat/SecureChatTrustManagerFactory.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.example.securechat; - -import javax.net.ssl.ManagerFactoryParameters; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactorySpi; -import javax.net.ssl.X509TrustManager; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; - -/** - * Bogus {@link TrustManagerFactorySpi} which accepts any certificate - * even if it is invalid. - */ -public class SecureChatTrustManagerFactory extends TrustManagerFactorySpi { - - private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Always trust - it is an example. - // You should do something in the real world. - // You will reach here only if you enabled client certificate auth, - // as described in SecureChatSslContextFactory. - System.err.println( - "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Always trust - it is an example. - // You should do something in the real world. - System.err.println( - "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); - } - }; - - public static TrustManager[] getTrustManagers() { - return new TrustManager[] { DUMMY_TRUST_MANAGER }; - } - - @Override - protected TrustManager[] engineGetTrustManagers() { - return getTrustManagers(); - } - - @Override - protected void engineInit(KeyStore keystore) throws KeyStoreException { - // Unused - } - - @Override - protected void engineInit(ManagerFactoryParameters managerFactoryParameters) - throws InvalidAlgorithmParameterException { - // Unused - } -} diff --git a/src/main/java/org/jboss/netty/handler/ssl/ApplicationProtocolSelector.java b/src/main/java/org/jboss/netty/handler/ssl/ApplicationProtocolSelector.java new file mode 100644 index 0000000000..756e48ab06 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/ApplicationProtocolSelector.java @@ -0,0 +1,32 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import java.util.List; + +/** + * Selects an application layer protocol in SSL NPN or ALPM. + */ +public interface ApplicationProtocolSelector { + /** + * Invoked to select a protocol from the list of specified application layer protocols. + * + * @param protocols the list of application layer protocols sent by the server. + * The list is empty if the server supports neither NPN nor ALPM. + * @return the selected protocol. {@code null} if no protocol was selected. + */ + String selectProtocol(List protocols) throws Exception; +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/JdkSslClientContext.java b/src/main/java/org/jboss/netty/handler/ssl/JdkSslClientContext.java new file mode 100644 index 0000000000..7c88ccdb01 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/JdkSslClientContext.java @@ -0,0 +1,134 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferInputStream; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.x500.X500Principal; +import java.io.File; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; + +public final class JdkSslClientContext extends JdkSslContext { + + private final SSLContext ctx; + private final SSLSessionContext sessCtx; + + public JdkSslClientContext() throws SSLException { + this(null, null, null, null, null, 0, 0); + } + + public JdkSslClientContext(File certChainFile) throws SSLException { + this(certChainFile, null); + } + + public JdkSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { + this(null, trustManagerFactory); + } + + public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + this(null, certChainFile, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new factory that creates a new client-side {@link javax.net.ssl.SSLEngine}. + */ + public JdkSslClientContext( + SslBufferPool bufPool, File certChainFile, TrustManagerFactory trustManagerFactory, + Iterable ciphers, ApplicationProtocolSelector nextProtocolSelector, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + super(bufPool, ciphers); + + if (nextProtocolSelector != null) { + throw new SSLException("NPN/ALPN unsupported: " + nextProtocolSelector); + } + + try { + if (certChainFile == null) { + ctx = SSLContext.getInstance(PROTOCOL); + if (trustManagerFactory == null) { + ctx.init(null, null, null); + } else { + trustManagerFactory.init((KeyStore) null); + ctx.init(null, trustManagerFactory.getTrustManagers(), null); + } + } else { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + for (ChannelBuffer buf: PemReader.readCertificates(certChainFile)) { + X509Certificate cert = (X509Certificate) cf.generateCertificate(new ChannelBufferInputStream(buf)); + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + // Set up trust manager factory to use our key store. + if (trustManagerFactory == null) { + trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + } + trustManagerFactory.init(ks); + + // Initialize the SSLContext to work with the trust managers. + ctx = SSLContext.getInstance(PROTOCOL); + ctx.init(null, trustManagerFactory.getTrustManagers(), null); + } + + sessCtx = ctx.getServerSessionContext(); + if (sessionCacheSize > 0) { + sessCtx.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE)); + } + if (sessionTimeout > 0) { + sessCtx.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE)); + } + } catch (Exception e) { + throw new SSLException("failed to initialize the server-side SSL context", e); + } + } + + @Override + public boolean isClient() { + return true; + } + + @Override + public ApplicationProtocolSelector nextProtocolSelector() { + return null; + } + + @Override + public List nextProtocols() { + return Collections.emptyList(); + } + + /** + * Returns the {@link javax.net.ssl.SSLContext} object of this factory. + */ + @Override + public SSLContext context() { + return ctx; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/JdkSslContext.java b/src/main/java/org/jboss/netty/handler/ssl/JdkSslContext.java new file mode 100644 index 0000000000..ed2c5529b1 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/JdkSslContext.java @@ -0,0 +1,169 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSessionContext; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public abstract class JdkSslContext extends SslContext { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(JdkSslContext.class); + + static final String PROTOCOL = "TLS"; + static final String[] PROTOCOLS; + static final List DEFAULT_CIPHERS; + + static { + SSLContext context; + try { + context = SSLContext.getInstance(PROTOCOL); + context.init(null, null, null); + } catch (Exception e) { + throw new Error("failed to initialize the default SSL context", e); + } + + SSLEngine engine = context.createSSLEngine(); + + // Choose the sensible default list of protocols. + String[] supportedProtocols = engine.getSupportedProtocols(); + List protocols = new ArrayList(); + addIfSupported( + supportedProtocols, protocols, + "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"); + + if (!protocols.isEmpty()) { + PROTOCOLS = protocols.toArray(new String[protocols.size()]); + } else { + PROTOCOLS = engine.getEnabledProtocols(); + } + + // Choose the sensible default list of cipher suites. + String[] supportedCiphers = engine.getSupportedCipherSuites(); + List ciphers = new ArrayList(); + addIfSupported( + supportedCiphers, ciphers, + // XXX: Make sure to sync this list with OpenSslEngineFactory. + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // since JDK 8 + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", // since JDK 8 + "SSL_RSA_WITH_RC4_128_SHA", + "SSL_RSA_WITH_RC4_128_MD5", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "SSL_RSA_WITH_DES_CBC_SHA"); + + if (!ciphers.isEmpty()) { + DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers); + } else { + // Use the default from JDK as fallback. + DEFAULT_CIPHERS = Collections.unmodifiableList(Arrays.asList(engine.getEnabledCipherSuites())); + } + + if (logger.isDebugEnabled()) { + logger.debug("Default protocols (JDK): " + PROTOCOLS); + logger.debug("Default cipher suites (JDK): " + DEFAULT_CIPHERS); + } + } + + private static void addIfSupported(String[] supported, List enabled, String... names) { + for (String n: names) { + for (String s: supported) { + if (n.equals(s)) { + enabled.add(s); + break; + } + } + } + } + + private final String[] cipherSuites; + private final List unmodifiableCipherSuites; + + JdkSslContext(SslBufferPool bufferPool, Iterable ciphers) { + super(bufferPool); + cipherSuites = toCipherSuiteArray(ciphers); + unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites)); + } + + public abstract SSLContext context(); + + public final SSLSessionContext sessionContext() { + if (isServer()) { + return context().getServerSessionContext(); + } else { + return context().getClientSessionContext(); + } + } + + @Override + public final List cipherSuites() { + return unmodifiableCipherSuites; + } + + @Override + public final long sessionCacheSize() { + return sessionContext().getSessionCacheSize(); + } + + @Override + public final long sessionTimeout() { + return sessionContext().getSessionTimeout(); + } + + @Override + public final SSLEngine newEngine() { + SSLEngine engine = context().createSSLEngine(); + engine.setEnabledCipherSuites(cipherSuites); + engine.setEnabledProtocols(PROTOCOLS); + engine.setUseClientMode(isClient()); + return engine; + } + + @Override + public final SSLEngine newEngine(String host, int port) { + SSLEngine engine = context().createSSLEngine(host, port); + engine.setEnabledCipherSuites(cipherSuites); + engine.setEnabledProtocols(PROTOCOLS); + engine.setUseClientMode(isClient()); + return engine; + } + + private static String[] toCipherSuiteArray(Iterable ciphers) { + if (ciphers == null) { + return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]); + } else { + List newCiphers = new ArrayList(); + for (String c: ciphers) { + if (c == null) { + break; + } + newCiphers.add(c); + } + return newCiphers.toArray(new String[newCiphers.size()]); + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/JdkSslServerContext.java b/src/main/java/org/jboss/netty/handler/ssl/JdkSslServerContext.java new file mode 100644 index 0000000000..1f3d2648bc --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/JdkSslServerContext.java @@ -0,0 +1,152 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferInputStream; + +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; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class JdkSslServerContext extends JdkSslContext { + + private final SSLContext ctx; + private final SSLSessionContext sessCtx; + + public JdkSslServerContext(File certChainFile, File keyFile) throws SSLException { + this(certChainFile, keyFile, null); + } + + public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { + this(null, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + /** + * Creates a new factory that creates a new server-side {@link SSLEngine}. + */ + public JdkSslServerContext( + SslBufferPool bufPool, + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + super(bufPool, ciphers); + + if (certChainFile == null) { + throw new NullPointerException("certChainFile"); + } + if (keyFile == null) { + throw new NullPointerException("keyFile"); + } + + if (keyPassword == null) { + keyPassword = ""; + } + + if (nextProtocols != null && nextProtocols.iterator().hasNext()) { + throw new SSLException("NPN/ALPN unsupported: " + nextProtocols); + } + + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + if (algorithm == null) { + algorithm = "SunX509"; + } + + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + KeyFactory rsaKF = KeyFactory.getInstance("RSA"); + KeyFactory dsaKF = KeyFactory.getInstance("DSA"); + + ChannelBuffer encodedKeyBuf = PemReader.readPrivateKey(keyFile); + byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()]; + encodedKeyBuf.readBytes(encodedKey); + PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(encodedKey); + + PrivateKey key; + try { + key = rsaKF.generatePrivate(encodedKeySpec); + } catch (InvalidKeySpecException ignore) { + key = dsaKF.generatePrivate(encodedKeySpec); + } + + List certChain = new ArrayList(); + for (ChannelBuffer buf: PemReader.readCertificates(certChainFile)) { + certChain.add(cf.generateCertificate(new ChannelBufferInputStream(buf))); + } + + ks.setKeyEntry("key", key, keyPassword.toCharArray(), certChain.toArray(new Certificate[certChain.size()])); + + // Set up key manager factory to use our key store + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + kmf.init(ks, keyPassword.toCharArray()); + + // Initialize the SSLContext to work with our key managers. + ctx = SSLContext.getInstance(PROTOCOL); + ctx.init(kmf.getKeyManagers(), null, null); + + sessCtx = ctx.getServerSessionContext(); + if (sessionCacheSize > 0) { + sessCtx.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE)); + } + if (sessionTimeout > 0) { + sessCtx.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE)); + } + } catch (Exception e) { + throw new SSLException("failed to initialize the server-side SSL context", e); + } + } + + @Override + public boolean isClient() { + return false; + } + + @Override + public ApplicationProtocolSelector nextProtocolSelector() { + return null; + } + + @Override + public List nextProtocols() { + return Collections.emptyList(); + } + + /** + * Returns the {@link SSLContext} object of this factory. + */ + @Override + public SSLContext context() { + return ctx; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java index d7a0b72d04..9842287239 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java @@ -30,6 +30,8 @@ public final class OpenSsl { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); private static final Throwable UNAVAILABILITY_CAUSE; + static final String IGNORABLE_ERROR_PREFIX = "error:00000000:"; + static { Throwable cause = null; try { diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java deleted file mode 100644 index fbf2d91a95..0000000000 --- a/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java +++ /dev/null @@ -1,95 +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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.util.internal.EmptyArrays; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * Manages a pool of directly-allocated ByteBuffers. - * - * This is necessary as the reclamation of these buffers does not work appropriately - * on some platforms. - * - * TODO: Attempt to replace the directly-allocated ByteBuffers this with one APR pool. - */ -public final class OpenSslBufferPool { - - private static final RuntimeException ALLOCATION_INTERRUPTED = - new IllegalStateException("buffer allocation interrupted"); - - static { - ALLOCATION_INTERRUPTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); - } - - // BUFFER_SIZE must be large enough to accomodate the maximum SSL record size. - // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256) - private static final int BUFFER_SIZE = 18713; - - private final BlockingQueue buffers; - - /** - * Construct a new pool with the specified capacity. - * - * @param capacity The number of buffers to instantiate. - */ - public OpenSslBufferPool(int capacity) { - OpenSsl.ensureAvailability(); - buffers = new LinkedBlockingQueue(capacity); - while (buffers.remainingCapacity() > 0) { - ByteBuffer buf = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder()); - buffers.offer(buf); - } - } - - /** - * Take a buffer from the pool. - * - * @return a ByteBuffer. - */ - public ByteBuffer acquire() { - try { - return buffers.take(); - } catch (InterruptedException ignore) { - throw ALLOCATION_INTERRUPTED; - } - } - - /** - * Release a buffer back into the stream - * - * @param buffer the ByteBuffer to release - */ - public void release(ByteBuffer buffer) { - buffer.clear(); - buffers.offer(buffer); - } - - @Override - public String toString() { - return "[DirectBufferPool " + - buffers.size() + " buffers * " + - BUFFER_SIZE + " bytes = " + - buffers.size() * BUFFER_SIZE + " total bytes; " + - "size: " + buffers.size() + - " remainingCapacity: " + buffers.remainingCapacity() + - ']'; - } -} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java deleted file mode 100644 index fa09d56fc5..0000000000 --- a/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java +++ /dev/null @@ -1,164 +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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.util.internal.StringUtil; - -import javax.net.ssl.SSLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Builds a new OpenSSL context object. - */ -public class OpenSslContextBuilder { - - private long aprPool; - private OpenSslBufferPool bufPool; - private String certPath; - private String keyPath; - private List cipherSpec; - private int sessionCacheSize; - private int sessionTimeout; - private String keyPassword; - private String caPath; - private String nextProtos; - - public OpenSslContextBuilder() { - OpenSsl.ensureAvailability(); - } - - public OpenSslContextBuilder certPath(String certPath) { - if (certPath == null) { - throw new NullPointerException("certPath"); - } - this.certPath = certPath; - return this; - } - - public OpenSslContextBuilder keyPath(String keyPath) { - if (keyPath == null) { - throw new NullPointerException("keyPath"); - } - this.keyPath = keyPath; - return this; - } - - public OpenSslContextBuilder cipherSpec(Iterable cipherSpec) { - if (cipherSpec == null) { - this.cipherSpec = null; - return this; - } - - List list = new ArrayList(); - for (String c: cipherSpec) { - if (c == null) { - break; - } - if (c.contains(":")) { - for (String cc : StringUtil.split(c, ':')) { - if (cc.length() != 0) { - list.add(cc); - } - } - } else { - list.add(c); - } - } - - if (list.isEmpty()) { - this.cipherSpec = null; - } else { - this.cipherSpec = list; - } - return this; - } - - public OpenSslContextBuilder cipherSpec(String... cipherSpec) { - if (cipherSpec == null) { - throw new NullPointerException("cipherSpec"); - } - return cipherSpec(Arrays.asList(cipherSpec)); - } - - public OpenSslContextBuilder keyPassword(String keyPassword) { - this.keyPassword = keyPassword; - return this; - } - - public OpenSslContextBuilder caPath(String caPath) { - this.caPath = caPath; - return this; - } - - public OpenSslContextBuilder nextProtos(String nextProtos) { - this.nextProtos = nextProtos; - return this; - } - - public OpenSslContextBuilder sessionCacheSize(int sessionCacheSize) { - this.sessionCacheSize = sessionCacheSize; - return this; - } - - public OpenSslContextBuilder sessionTimeout(int sessionTimeout) { - this.sessionTimeout = sessionTimeout; - return this; - } - - public OpenSslContextBuilder aprPool(long aprPool) { - this.aprPool = aprPool; - return this; - } - - public OpenSslContextBuilder bufPool(OpenSslBufferPool bufPool) { - this.bufPool = bufPool; - return this; - } - - /** - * Creates a new server context. - * - * @throws SSLException if the required fields are not assigned - */ - public OpenSslServerContext newServerContext() throws SSLException { - // If the cipherSpec was not specified or empty, use the default. - if (cipherSpec == null) { - cipherSpec = new ArrayList(); - } - - if (cipherSpec.isEmpty()) { - Collections.addAll( - cipherSpec, - "ECDHE-RSA-AES128-GCM-SHA256", - "ECDHE-RSA-RC4-SHA", - "ECDHE-RSA-AES128-SHA", - "ECDHE-RSA-AES256-SHA", - "AES128-GCM-SHA256", - "RC4-SHA", - "RC4-MD5", - "AES128-SHA", - "AES256-SHA", - "DES-CBC3-SHA"); - } - - return new OpenSslServerContext( - aprPool, bufPool, - certPath, keyPath, keyPassword, caPath, nextProtos, cipherSpec, sessionCacheSize, sessionTimeout); - } -} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java index 8a985a0f96..0a370bfd2b 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java @@ -67,8 +67,6 @@ public final class OpenSslEngine extends SSLEngine { private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024; private static final int MAX_ENCRYPTED_PACKET = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256; - private static final String SSL_IGNORABLE_ERROR_PREFIX = "error:00000000:"; - private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); @@ -95,10 +93,10 @@ public final class OpenSslEngine extends SSLEngine { private int lastPrimingReadResult; - private final OpenSslBufferPool bufPool; + private final SslBufferPool bufPool; private SSLSession session; - public OpenSslEngine(long sslContext, OpenSslBufferPool bufPool) { + public OpenSslEngine(long sslContext, SslBufferPool bufPool) { OpenSsl.ensureAvailability(); if (sslContext == 0) { throw new NullPointerException("sslContext"); @@ -129,107 +127,148 @@ public final class OpenSslEngine extends SSLEngine { * Calling this function with src.remaining == 0 is undefined. */ private int writePlaintextData(final ByteBuffer src) { - final ByteBuffer buf = bufPool.acquire(); - final long addr = Buffer.address(buf); - try { - int position = src.position(); - int limit = src.limit(); - int len = Math.min(src.remaining(), MAX_PLAINTEXT_LENGTH); - if (len > buf.capacity()) { - throw new IllegalStateException("buffer pool write overflow"); - } - src.limit(position + len); + final int pos = src.position(); + final int limit = src.limit(); + final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH); + final int sslWrote; - buf.put(src); - src.limit(limit); - - final int sslWrote = SSL.writeToSSL(ssl, addr, len); + if (src.isDirect()) { + final long addr = Buffer.address(src) + pos; + sslWrote = SSL.writeToSSL(ssl, addr, len); if (sslWrote > 0) { - src.position(position + sslWrote); + src.position(pos + sslWrote); return sslWrote; - } else { - src.position(position); - throw new IllegalStateException("SSL.writeToSSL() returned a non-positive value: " + sslWrote); } - } finally { - bufPool.release(buf); + } else { + final ByteBuffer buf = bufPool.acquireBuffer(); + try { + assert buf.isDirect(); + assert len <= buf.capacity() : "buffer pool write overflow"; + final long addr = Buffer.address(buf); + + src.limit(pos + len); + + buf.put(src); + src.limit(limit); + + sslWrote = SSL.writeToSSL(ssl, addr, len); + if (sslWrote > 0) { + src.position(pos + sslWrote); + return sslWrote; + } else { + src.position(pos); + } + } finally { + bufPool.releaseBuffer(buf); + } } + + throw new IllegalStateException("SSL.writeToSSL() returned a non-positive value: " + sslWrote); } /** * Write encrypted data to the OpenSSL network BIO */ private int writeEncryptedData(final ByteBuffer src) { - final ByteBuffer buf = bufPool.acquire(); - final long addr = Buffer.address(buf); - try { - int position = src.position(); - int len = src.remaining(); - if (len > buf.capacity()) { - throw new IllegalStateException("buffer pool write overflow"); - } - - buf.put(src); - + final int pos = src.position(); + final int len = src.remaining(); + if (src.isDirect()) { + final long addr = Buffer.address(src) + pos; final int netWrote = SSL.writeToBIO(networkBIO, addr, len); if (netWrote >= 0) { - src.position(position + netWrote); + src.position(pos + netWrote); lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read return netWrote; - } else { - src.position(position); - return 0; } - } finally { - bufPool.release(buf); + } else { + final ByteBuffer buf = bufPool.acquireBuffer(); + try { + assert buf.isDirect(); + assert len <= buf.capacity(); + final long addr = Buffer.address(buf); + + buf.put(src); + + final int netWrote = SSL.writeToBIO(networkBIO, addr, len); + src.position(pos + netWrote); + if (netWrote >= 0) { + lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read + return netWrote; + } + } finally { + bufPool.releaseBuffer(buf); + } } + + return 0; } /** * Read plaintext data from the OpenSSL internal BIO */ private int readPlaintextData(final ByteBuffer dst) { - final ByteBuffer buf = bufPool.acquire(); - final long addr = Buffer.address(buf); - try { - final int len = Math.min(buf.capacity(), dst.capacity()); - buf.limit(len); + if (dst.isDirect()) { + final int pos = dst.position(); + final long addr = Buffer.address(dst) + pos; + final int len = dst.limit() - pos; final int sslRead = SSL.readFromSSL(ssl, addr, len); if (sslRead > 0) { - buf.limit(sslRead); - dst.put(buf); + dst.position(pos + sslRead); return sslRead; - } else { - return 0; } - } finally { - bufPool.release(buf); + } else { + final ByteBuffer buf = bufPool.acquireBuffer(); + try { + assert buf.isDirect(); + final long addr = Buffer.address(buf); + final int len = Math.min(buf.capacity(), dst.remaining()); + buf.limit(len); + final int sslRead = SSL.readFromSSL(ssl, addr, len); + if (sslRead > 0) { + buf.limit(sslRead); + dst.put(buf); + return sslRead; + } + } finally { + bufPool.releaseBuffer(buf); + } } + + return 0; } /** * Read encrypted data from the OpenSSL network BIO */ private int readEncryptedData(final ByteBuffer dst, final int pending) { - final ByteBuffer buf = bufPool.acquire(); - final long addr = Buffer.address(buf); - try { - if (pending > buf.capacity()) { - throw new IllegalStateException("network BIO read overflow " + - "(pending: " + pending + ", capacity: " + buf.capacity() + ')'); - } - + if (dst.isDirect() && dst.remaining() >= pending) { + final int pos = dst.position(); + final long addr = Buffer.address(dst) + pos; final int bioRead = SSL.readFromBIO(networkBIO, addr, pending); if (bioRead > 0) { - buf.limit(bioRead); - dst.put(buf); + dst.position(pos + bioRead); return bioRead; - } else { - return 0; } - } finally { - bufPool.release(buf); + } else { + final ByteBuffer buf = bufPool.acquireBuffer(); + try { + assert buf.isDirect(); + final long addr = Buffer.address(buf); + assert buf.capacity() >= pending : + "network BIO read overflow (pending: " + pending + ", capacity: " + buf.capacity() + ')'; + + final int bioRead = SSL.readFromBIO(networkBIO, addr, pending); + if (bioRead > 0) { + buf.limit(bioRead); + dst.put(buf); + return bioRead; + } + } finally { + bufPool.releaseBuffer(buf); + } } + + return 0; } /** @@ -412,7 +451,7 @@ public final class OpenSslEngine extends SSLEngine { // Check for OpenSSL errors caused by the priming read String error = SSL.getLastError(); - if (error != null && !error.startsWith(SSL_IGNORABLE_ERROR_PREFIX)) { + if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { if (logger.isInfoEnabled()) { logger.info( "SSL_read failed: primingReadResult: " + lastPrimingReadResult + diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java index 950ae281bb..394367679d 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java @@ -21,9 +21,12 @@ import org.apache.tomcat.jni.SSLContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -46,101 +49,106 @@ import java.util.List; * * */ -public final class OpenSslServerContext { +public final class OpenSslServerContext extends SslContext { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class); + private static final List DEFAULT_CIPHERS; + + static { + List ciphers = new ArrayList(); + // XXX: Make sure to sync this list with JdkSslEngineFactory. + Collections.addAll( + ciphers, + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-RC4-SHA", + "ECDHE-RSA-AES128-SHA", + "ECDHE-RSA-AES256-SHA", + "AES128-GCM-SHA256", + "RC4-SHA", + "RC4-MD5", + "AES128-SHA", + "AES256-SHA", + "DES-CBC3-SHA"); + DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers); + + if (logger.isDebugEnabled()) { + logger.debug("Default cipher suite (OpenSSL): " + ciphers); + } + } private final long aprPool; - private final OpenSslBufferPool bufPool; - private final boolean destroyAprPool; - private final List cipherSpec = new ArrayList(); - private final List unmodifiableCipherSpec = Collections.unmodifiableList(cipherSpec); - private final String cipherSpecText; + private final List ciphers = new ArrayList(); + private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers); private final long sessionCacheSize; private final long sessionTimeout; - private final String nextProtos; + private final List nextProtocols = new ArrayList(); + private final List unmodifiableNextProtocols = Collections.unmodifiableList(nextProtocols); /** The OpenSSL SSL_CTX object */ private final long ctx; private final OpenSslSessionStats stats; + public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException { + this(certChainFile, keyFile, null); + } + + public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { + this(null, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + public OpenSslServerContext( - long aprPool, OpenSslBufferPool bufPool, - String certPath, String keyPath, String keyPassword, - String caPath, String nextProtos, Iterable ciphers, + SslBufferPool bufPool, + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, long sessionCacheSize, long sessionTimeout) throws SSLException { + super(bufPool); + OpenSsl.ensureAvailability(); - if (certPath == null) { - throw new NullPointerException("certPath"); + if (certChainFile == null) { + throw new NullPointerException("certChainFile"); } - if (certPath.length() == 0) { - throw new IllegalArgumentException("certPath is empty."); + if (!certChainFile.isFile()) { + throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile); } - if (keyPath == null) { + if (keyFile == null) { throw new NullPointerException("keyPath"); } - if (keyPath.length() == 0) { - throw new IllegalArgumentException("keyPath is empty."); + if (!keyFile.isFile()) { + throw new IllegalArgumentException("keyPath is not a file: " + keyFile); } if (ciphers == null) { - throw new NullPointerException("ciphers"); + ciphers = DEFAULT_CIPHERS; } if (keyPassword == null) { keyPassword = ""; } - if (caPath == null) { - caPath = ""; - } - if (nextProtos == null) { - nextProtos = ""; + if (nextProtocols == null) { + nextProtocols = Collections.emptyList(); } for (String c: ciphers) { if (c == null) { break; } - cipherSpec.add(c); + this.ciphers.add(c); } - this.nextProtos = nextProtos; - - // Convert the cipher list into a colon-separated string. - StringBuilder cipherSpecBuf = new StringBuilder(); - for (String c: cipherSpec) { - cipherSpecBuf.append(c); - cipherSpecBuf.append(':'); - } - cipherSpecBuf.setLength(cipherSpecBuf.length() - 1); - cipherSpecText = cipherSpecBuf.toString(); - - // Allocate a new APR pool if necessary. - if (aprPool == 0) { - aprPool = Pool.create(0); - destroyAprPool = true; - } else { - destroyAprPool = false; - } - - // Allocate a new OpenSSL buffer pool if necessary. - boolean success = false; - try { - if (bufPool == null) { - bufPool = new OpenSslBufferPool(Runtime.getRuntime().availableProcessors() * 2); - } - success = true; - } finally { - if (!success && destroyAprPool) { - Pool.destroy(aprPool); + for (String p: nextProtocols) { + if (p == null) { + break; } + this.nextProtocols.add(p); } - this.aprPool = aprPool; - this.bufPool = bufPool; + // Allocate a new APR pool. + aprPool = Pool.create(0); // Create a new SSL_CTX and configure it. - success = false; + boolean success = false; try { synchronized (OpenSslServerContext.class) { try { @@ -158,11 +166,19 @@ public final class OpenSslServerContext { /* List the ciphers that the client is permitted to negotiate. */ try { - SSLContext.setCipherSuite(ctx, cipherSpecText); + // Convert the cipher list into a colon-separated string. + StringBuilder cipherBuf = new StringBuilder(); + for (String c: this.ciphers) { + cipherBuf.append(c); + cipherBuf.append(':'); + } + cipherBuf.setLength(cipherBuf.length() - 1); + + SSLContext.setCipherSuite(ctx, cipherBuf.toString()); } catch (SSLException e) { throw e; } catch (Exception e) { - throw new SSLException("failed to set cipher suite: " + cipherSpecText, e); + throw new SSLException("failed to set cipher suite: " + this.ciphers, e); } /* Set certificate verification policy. */ @@ -171,30 +187,36 @@ public final class OpenSslServerContext { /* Load the certificate file and private key. */ try { if (!SSLContext.setCertificate( - ctx, certPath, keyPath, keyPassword, SSL.SSL_AIDX_RSA)) { - throw new SSLException( - "failed to set certificate: " + certPath + " (" + SSL.getLastError() + ')'); + ctx, certChainFile.getPath(), keyFile.getPath(), keyPassword, SSL.SSL_AIDX_RSA)) { + throw new SSLException("failed to set certificate: " + + certChainFile + " and " + keyFile + " (" + SSL.getLastError() + ')'); } } catch (SSLException e) { throw e; } catch (Exception e) { - throw new SSLException("failed to set certificate: " + certPath, e); + throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e); } - /* Load certificate chain file, if specified */ - if (caPath.length() != 0) { - /* If named same as cert file, we must skip the first cert since it was loaded above. */ - boolean skipFirstCert = certPath.equals(caPath); - - if (!SSLContext.setCertificateChainFile(ctx, caPath, skipFirstCert)) { + /* Load the certificate chain. We must skip the first cert since it was loaded above. */ + if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) { + String error = SSL.getLastError(); + if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { throw new SSLException( - "failed to set certificate chain: " + caPath + " (" + SSL.getLastError() + ')'); + "failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')'); } } /* Set next protocols for next protocol negotiation extension, if specified */ - if (nextProtos.length() != 0) { - SSLContext.setNextProtos(ctx, nextProtos); + if (!this.nextProtocols.isEmpty()) { + // Convert the protocol list into a comma-separated string. + StringBuilder nextProtocolBuf = new StringBuilder(); + for (String p: this.nextProtocols) { + nextProtocolBuf.append(p); + nextProtocolBuf.append(','); + } + nextProtocolBuf.setLength(nextProtocolBuf.length() - 1); + + SSLContext.setNextProtos(ctx, nextProtocolBuf.toString()); } /* Set session cache size, if specified */ @@ -229,31 +251,69 @@ public final class OpenSslServerContext { stats = new OpenSslSessionStats(ctx); } - public List cipherSpec() { - return unmodifiableCipherSpec; + @Override + SslBufferPool newBufferPool() { + return new SslBufferPool(true, true); } - public String cipherSpecText() { - return cipherSpecText; + @Override + public boolean isClient() { + return false; } - public String nextProtos() { - return nextProtos; + @Override + public List cipherSuites() { + return unmodifiableCiphers; } - public int sessionCacheSize() { - return sessionCacheSize > Integer.MAX_VALUE? Integer.MAX_VALUE : (int) sessionCacheSize; + @Override + public long sessionCacheSize() { + return sessionCacheSize; } - public int sessionTimeout() { - return sessionTimeout > Integer.MAX_VALUE? Integer.MAX_VALUE : (int) sessionTimeout; + @Override + public long sessionTimeout() { + return sessionTimeout; + } + + @Override + public ApplicationProtocolSelector nextProtocolSelector() { + return null; + } + + @Override + public List nextProtocols() { + return unmodifiableNextProtocols; + } + + /** + * Returns the {@code SSL_CTX} object of this factory. + */ + public long context() { + return ctx; } public OpenSslSessionStats stats() { return stats; } + /** + * Returns a new server-side {@link SSLEngine} with the current configuration. + */ + @Override + public SSLEngine newEngine() { + return new OpenSslEngine(ctx, bufferPool()); + } + + @Override + public SSLEngine newEngine(String host, int port) { + throw new UnsupportedOperationException(); + } + public void setTicketKeys(byte[] keys) { + if (keys != null) { + throw new NullPointerException("keys"); + } SSLContext.setSessionTicketKeys(ctx, keys); } @@ -271,15 +331,8 @@ public final class OpenSslServerContext { } private void destroyPools() { - if (destroyAprPool && aprPool != 0) { + if (aprPool != 0) { Pool.destroy(aprPool); } } - - /** - * Returns a new server-side {@link SSLEngine} with the current configuration. - */ - public SSLEngine newEngine() { - return new OpenSslEngine(ctx, bufPool); - } } diff --git a/src/main/java/org/jboss/netty/handler/ssl/PemReader.java b/src/main/java/org/jboss/netty/handler/ssl/PemReader.java new file mode 100644 index 0000000000..5876d4ea97 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/PemReader.java @@ -0,0 +1,137 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.handler.codec.base64.Base64; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; +import org.jboss.netty.util.CharsetUtil; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.KeyException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link KeyStore} easily. + */ +final class PemReader { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(PemReader.class); + + private static final Pattern CERT_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*CERTIFICATE[^-]*-+", // Footer + Pattern.CASE_INSENSITIVE); + private static final Pattern KEY_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer + Pattern.CASE_INSENSITIVE); + + static ChannelBuffer[] readCertificates(File file) throws CertificateException { + String content; + try { + content = readContent(file); + } catch (IOException e) { + throw new CertificateException("failed to read a file: " + file, e); + } + + List certs = new ArrayList(); + Matcher m = CERT_PATTERN.matcher(content); + int start = 0; + for (;;) { + if (!m.find(start)) { + break; + } + + certs.add(Base64.decode(ChannelBuffers.copiedBuffer(m.group(1), CharsetUtil.US_ASCII))); + start = m.end(); + } + + if (certs.isEmpty()) { + throw new CertificateException("found no certificates: " + file); + } + + return certs.toArray(new ChannelBuffer[certs.size()]); + } + + static ChannelBuffer readPrivateKey(File file) throws KeyException { + String content; + try { + content = readContent(file); + } catch (IOException e) { + throw new KeyException("failed to read a file: " + file, e); + } + + Matcher m = KEY_PATTERN.matcher(content); + if (!m.find()) { + throw new KeyException("found no private key: " + file); + } + + return Base64.decode(ChannelBuffers.copiedBuffer(m.group(1), CharsetUtil.US_ASCII)); + } + + private static String readContent(File file) throws IOException { + InputStream in = new FileInputStream(file); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + byte[] buf = new byte[8192]; + for (;;) { + int ret = in.read(buf); + if (ret < 0) { + break; + } + out.write(buf, 0, ret); + } + return out.toString(CharsetUtil.US_ASCII.name()); + } finally { + safeClose(in); + safeClose(out); + } + } + + private static void safeClose(InputStream in) { + try { + in.close(); + } catch (IOException e) { + logger.warn("Failed to close a stream.", e); + } + } + + private static void safeClose(OutputStream out) { + try { + out.close(); + } catch (IOException e) { + logger.warn("Failed to close a stream.", e); + } + } + + private PemReader() { } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslBufferPool.java b/src/main/java/org/jboss/netty/handler/ssl/SslBufferPool.java index e6e3e38d3f..06d25dcbfb 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/SslBufferPool.java +++ b/src/main/java/org/jboss/netty/handler/ssl/SslBufferPool.java @@ -17,6 +17,9 @@ package org.jboss.netty.handler.ssl; import javax.net.ssl.SSLEngine; import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; /** * A {@link ByteBuffer} pool dedicated for {@link SslHandler} performance @@ -36,37 +39,77 @@ public class SslBufferPool { // Add 1024 as a room for compressed data and another 1024 for Apache Harmony compatibility. private static final int MAX_PACKET_SIZE = 16665 + 2048; - private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE * 1024; + private static final int MAX_PACKET_SIZE_ALIGNED = (MAX_PACKET_SIZE / 128 + 1) * 128; - private final ByteBuffer[] pool; + private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE_ALIGNED * 1024; + + private final ByteBuffer preallocated; + private final BlockingQueue pool; private final int maxBufferCount; - private int index; + private final boolean allocateDirect; /** - * Creates a new buffer pool whose size is {@code 18113536}, which can + * The number of buffers allocated so far. Used only when {@link #preallocated} is null. + */ + private final AtomicInteger numAllocations; + + /** + * Creates a new buffer pool whose size is {@code 19267584}, which can * hold {@code 1024} buffers. */ public SslBufferPool() { this(DEFAULT_POOL_SIZE); } + /** + * Creates a new buffer pool whose size is {@code 19267584}, which can + * hold {@code 1024} buffers. + */ + public SslBufferPool(boolean preallocate, boolean allocateDirect) { + this(DEFAULT_POOL_SIZE, preallocate, allocateDirect); + } + /** * Creates a new buffer pool. * * @param maxPoolSize the maximum number of bytes that this pool can hold */ public SslBufferPool(int maxPoolSize) { + this(maxPoolSize, false, false); + } + + /** + * Creates a new buffer pool. + * + * @param maxPoolSize the maximum number of bytes that this pool can hold + */ + public SslBufferPool(int maxPoolSize, boolean preallocate, boolean allocateDirect) { if (maxPoolSize <= 0) { throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize); } - int maxBufferCount = maxPoolSize / MAX_PACKET_SIZE; - if (maxPoolSize % MAX_PACKET_SIZE != 0) { + int maxBufferCount = maxPoolSize / MAX_PACKET_SIZE_ALIGNED; + if (maxPoolSize % MAX_PACKET_SIZE_ALIGNED != 0) { maxBufferCount ++; } - pool = new ByteBuffer[maxBufferCount]; this.maxBufferCount = maxBufferCount; + this.allocateDirect = allocateDirect; + + pool = new ArrayBlockingQueue(maxBufferCount); + + if (preallocate) { + preallocated = allocate(maxBufferCount * MAX_PACKET_SIZE_ALIGNED); + numAllocations = null; + for (int i = 0; i < maxBufferCount; i ++) { + int pos = i * MAX_PACKET_SIZE_ALIGNED; + preallocated.clear().position(pos).limit(pos + MAX_PACKET_SIZE_ALIGNED); + pool.add(preallocated.slice()); + } + } else { + preallocated = null; + numAllocations = new AtomicInteger(); + } } /** @@ -74,7 +117,7 @@ public class SslBufferPool { * can be somewhat different from what was specified in the constructor. */ public int getMaxPoolSize() { - return maxBufferCount * MAX_PACKET_SIZE; + return maxBufferCount * MAX_PACKET_SIZE_ALIGNED; } /** @@ -84,45 +127,55 @@ public class SslBufferPool { * pool is getting exhausted. If it keeps returns a unnecessarily big * value, it means the pool is wasting the heap space. */ - public synchronized int getUnacquiredPoolSize() { - return index * MAX_PACKET_SIZE; + public int getUnacquiredPoolSize() { + return pool.size() * MAX_PACKET_SIZE_ALIGNED; } /** * Acquire a new {@link ByteBuffer} out of the {@link SslBufferPool} * */ - public synchronized ByteBuffer acquireBuffer() { - if (index == 0) { - return ByteBuffer.allocate(MAX_PACKET_SIZE); + public ByteBuffer acquireBuffer() { + ByteBuffer buf; + if (preallocated != null || numAllocations.get() >= maxBufferCount) { + boolean interrupted = false; + for (;;) { + try { + buf = pool.take(); + break; + } catch (InterruptedException ignore) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } } else { - return (ByteBuffer) pool[-- index].clear(); + buf = pool.poll(); + if (buf == null) { + // Note that we can allocate more buffers than maxBufferCount. + // We will discard the buffers allocated after numAllocations reached maxBufferCount in releaseBuffer(). + numAllocations.incrementAndGet(); + buf = allocate(MAX_PACKET_SIZE); + } } - } - /** - * Will get removed. Please use {@link #acquireBuffer()} - * - */ - @Deprecated - ByteBuffer acquire() { - return acquireBuffer(); + buf.clear(); + return buf; } /** * Release a previous acquired {@link ByteBuffer} */ - public synchronized void releaseBuffer(ByteBuffer buffer) { - if (index < maxBufferCount) { - pool[index ++] = buffer; - } + public void releaseBuffer(ByteBuffer buffer) { + pool.offer(buffer); } - /** - * @deprecated Use {@link #releaseBuffer(ByteBuffer)} - */ - @Deprecated - void release(ByteBuffer buffer) { - releaseBuffer(buffer); + private ByteBuffer allocate(int capacity) { + if (allocateDirect) { + return ByteBuffer.allocateDirect(capacity); + } else { + return ByteBuffer.allocate(capacity); + } } } diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslContext.java b/src/main/java/org/jboss/netty/handler/ssl/SslContext.java new file mode 100644 index 0000000000..763b14cd73 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/SslContext.java @@ -0,0 +1,206 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.util.List; + +/** + * Creates a new {@link SSLEngine}. Using its factory methods, you can let it choose the optimal {@link SSLEngine} + * implementation available to you (the default JDK {@link SSLEngine} or the one that uses OpenSSL native library). + */ +public abstract class SslContext { + + public static SslProvider defaultServerProvider() { + if (OpenSsl.isAvailable()) { + return SslProvider.OPENSSL; + } else { + return SslProvider.JDK; + } + } + + public static SslProvider defaultClientProvider() { + return SslProvider.JDK; + } + + public static SslContext newServerContext(File certChainFile, File keyFile) throws SSLException { + return newServerContext(null, null, certChainFile, keyFile, null, null, null, 0, 0); + } + + public static SslContext newServerContext( + File certChainFile, File keyFile, String keyPassword) throws SSLException { + return newServerContext(null, null, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + public static SslContext newServerContext( + SslBufferPool bufPool, + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + return newServerContext( + null, bufPool, certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + } + + public static SslContext newServerContext( + SslProvider provider, File certChainFile, File keyFile) throws SSLException { + return newServerContext(provider, null, certChainFile, keyFile, null, null, null, 0, 0); + } + + public static SslContext newServerContext( + SslProvider provider, File certChainFile, File keyFile, String keyPassword) throws SSLException { + return newServerContext(provider, null, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + public static SslContext newServerContext( + SslProvider provider, SslBufferPool bufPool, + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + if (provider == null) { + provider = OpenSsl.isAvailable()? SslProvider.OPENSSL : SslProvider.JDK; + } + + switch (provider) { + case JDK: + return new JdkSslServerContext( + bufPool, certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + case OPENSSL: + return new OpenSslServerContext( + bufPool, certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + default: + throw new Error(provider.toString()); + } + } + + public static SslContext newClientContext() throws SSLException { + return newClientContext(null, null, null, null, null, null, 0, 0); + } + + public static SslContext newClientContext(File certChainFile) throws SSLException { + return newClientContext(null, null, certChainFile, null, null, null, 0, 0); + } + + public static SslContext newClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(null, null, null, trustManagerFactory, null, null, 0, 0); + } + + public static SslContext newClientContext( + File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(null, null, certChainFile, trustManagerFactory, null, null, 0, 0); + } + + public static SslContext newClientContext( + SslBufferPool bufPool, + File certChainFile, TrustManagerFactory trustManagerFactory, + Iterable ciphers, ApplicationProtocolSelector nextProtocolSelector, + long sessionCacheSize, long sessionTimeout) throws SSLException { + return newClientContext( + null, bufPool, certChainFile, trustManagerFactory, + ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); + } + + public static SslContext newClientContext(SslProvider provider) throws SSLException { + return newClientContext(provider, null, null, null, null, null, 0, 0); + } + + public static SslContext newClientContext(SslProvider provider, File certChainFile) throws SSLException { + return newClientContext(provider, null, certChainFile, null, null, null, 0, 0); + } + + public static SslContext newClientContext( + SslProvider provider, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(provider, null, null, trustManagerFactory, null, null, 0, 0); + } + + public static SslContext newClientContext( + SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(provider, null, certChainFile, trustManagerFactory, null, null, 0, 0); + } + + public static SslContext newClientContext( + SslProvider provider, SslBufferPool bufPool, + File certChainFile, TrustManagerFactory trustManagerFactory, + Iterable ciphers, ApplicationProtocolSelector nextProtocolSelector, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + if (provider != null && provider != SslProvider.JDK) { + throw new SSLException("client context unsupported for: " + provider); + } + + return new JdkSslClientContext( + bufPool, certChainFile, trustManagerFactory, + ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); + } + + private final SslBufferPool bufferPool; + + SslContext(SslBufferPool bufferPool) { + this.bufferPool = bufferPool == null? newBufferPool() : bufferPool; + } + + SslBufferPool newBufferPool() { + return new SslBufferPool(false, false); + } + + public final boolean isServer() { + return !isClient(); + } + + public final SslBufferPool bufferPool() { + return bufferPool; + } + + public abstract boolean isClient(); + + public abstract List cipherSuites(); + + public abstract long sessionCacheSize(); + + public abstract long sessionTimeout(); + + public abstract ApplicationProtocolSelector nextProtocolSelector(); + + public abstract List nextProtocols(); + + public abstract SSLEngine newEngine(); + + public abstract SSLEngine newEngine(String host, int port); + + public final SslHandler newHandler() { + return newHandler(newEngine()); + } + + public final SslHandler newHandler(String host, int port) { + return newHandler(newEngine(host, port)); + } + + private SslHandler newHandler(SSLEngine engine) { + SslHandler handler = new SslHandler(engine, bufferPool()); + if (isClient()) { + handler.setIssueHandshake(true); + } + handler.setCloseOnSSLException(true); + return handler; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java index 4bb4a0ce18..3628609562 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java +++ b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java @@ -243,7 +243,7 @@ public class SslHandler extends FrameDecoder private volatile boolean writeBeforeHandshakeDone; private final SSLEngineInboundCloseFuture sslEngineCloseFuture = new SSLEngineInboundCloseFuture(); - private boolean closeOnSSLException; + private boolean closeOnSslException; private int packetLength; @@ -440,7 +440,7 @@ public class SslHandler extends FrameDecoder hsFuture.setFailure(cause); fireExceptionCaught(ctx, cause); - if (closeOnSSLException) { + if (closeOnSslException) { Channels.close(ctx, future(channel)); } } @@ -450,13 +450,13 @@ public class SslHandler extends FrameDecoder handshakeFuture.setFailure(e); fireExceptionCaught(ctx, e); - if (closeOnSSLException) { + if (closeOnSslException) { Channels.close(ctx, future(channel)); } } } else { // Failed to initiate handshake. fireExceptionCaught(ctx, exception); - if (closeOnSSLException) { + if (closeOnSslException) { Channels.close(ctx, future(channel)); } } @@ -484,7 +484,7 @@ public class SslHandler extends FrameDecoder return wrapNonAppData(ctx, channel); } catch (SSLException e) { fireExceptionCaught(ctx, e); - if (closeOnSSLException) { + if (closeOnSslException) { Channels.close(ctx, future(channel)); } return failedFuture(channel, e); @@ -562,11 +562,11 @@ public class SslHandler extends FrameDecoder if (ctx != null) { throw new IllegalStateException("Can only get changed before attached to ChannelPipeline"); } - closeOnSSLException = closeOnSslException; + this.closeOnSslException = closeOnSslException; } public boolean getCloseOnSSLException() { - return closeOnSSLException; + return closeOnSslException; } public void handleDownstream( @@ -933,7 +933,7 @@ public class SslHandler extends FrameDecoder NotSslRecordException e = new NotSslRecordException( "not an SSL/TLS record: " + ChannelBuffers.hexDump(in)); in.skipBytes(in.readableBytes()); - if (closeOnSSLException) { + if (closeOnSslException) { // first trigger the exception and then close the channel fireExceptionCaught(ctx, e); Channels.close(ctx, future(channel)); @@ -1271,9 +1271,9 @@ public class SslHandler extends FrameDecoder ChannelBuffer nettyInNetBuf, ByteBuffer nioInNetBuf, ChannelBuffer nettyOutAppBuf, int initialNettyOutAppBufCapacity) throws SSLException { - final ByteBuffer nioOutAppBuf = bufferPool.acquireBuffer(); final int nettyInNetBufStartOffset = nettyInNetBuf.readerIndex(); final int nioInNetBufStartOffset = nioInNetBuf.position(); + final ByteBuffer nioOutAppBuf = bufferPool.acquireBuffer(); try { boolean needsWrap = false; @@ -1545,6 +1545,10 @@ public class SslHandler extends FrameDecoder cancelHandshakeTimeout(); } + if (logger.isDebugEnabled()) { + logger.debug(channel + " HANDSHAKEN: " + engine.getSession().getCipherSuite()); + } + handshakeFuture.setSuccess(); } @@ -1580,7 +1584,7 @@ public class SslHandler extends FrameDecoder } handshakeFuture.setFailure(cause); - if (closeOnSSLException) { + if (closeOnSslException) { Channels.close(ctx, future(channel)); } } diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslProvider.java b/src/main/java/org/jboss/netty/handler/ssl/SslProvider.java new file mode 100644 index 0000000000..1376782258 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/SslProvider.java @@ -0,0 +1,25 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +/** + * An enumeration of SSL/TLS protocol providers. + */ +public enum SslProvider { + JDK, + OPENSSL +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java b/src/main/java/org/jboss/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java new file mode 100644 index 0000000000..218ae47d61 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java @@ -0,0 +1,58 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import static org.jboss.netty.handler.ssl.util.SelfSignedCertificate.*; + +final class BouncyCastleSelfSignedCertGenerator { + + private static final Provider PROVIDER = new BouncyCastleProvider(); + + static String[] generate(String fqdn, KeyPair keypair, SecureRandom random) throws Exception { + PrivateKey key = keypair.getPrivate(); + + // Prepare the information required for generating an X.509 certificate. + X500Name owner = new X500Name("CN=" + fqdn); + X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + owner, new BigInteger(64, random), NOT_BEFORE, NOT_AFTER, owner, keypair.getPublic()); + + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(key); + X509CertificateHolder certHolder = builder.build(signer); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder); + cert.verify(keypair.getPublic()); + + return newSelfSignedCertificate(fqdn, key, cert); + } + + private BouncyCastleSelfSignedCertGenerator() { } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/FingerprintTrustManagerFactory.java b/src/main/java/org/jboss/netty/handler/ssl/util/FingerprintTrustManagerFactory.java new file mode 100644 index 0000000000..e1739430e5 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/FingerprintTrustManagerFactory.java @@ -0,0 +1,162 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.util.internal.EmptyArrays; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public final class FingerprintTrustManagerFactory extends SimpleTrustManagerFactory { + + private static final Pattern FINGERPRINT_PATTERN = Pattern.compile("^[0-9a-fA-F:]+$"); + private static final Pattern FINGERPRINT_STRIP_PATTERN = Pattern.compile(":"); + private static final int SHA1_BYTE_LEN = 20; + private static final int SHA1_HEX_LEN = SHA1_BYTE_LEN * 2; + + private static final ThreadLocal tlmd = new ThreadLocal() { + @Override + protected MessageDigest initialValue() { + try { + return MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + // All Java implementation must have SHA1 digest algorithm. + throw new Error(e); + } + } + }; + + private final TrustManager tm = new X509TrustManager() { + + public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException { + checkTrusted("client", chain); + } + + public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException { + checkTrusted("server", chain); + } + + private void checkTrusted(String type, X509Certificate[] chain) throws CertificateException { + X509Certificate cert = chain[0]; + byte[] fingerprint = fingerprint(cert); + boolean found = false; + for (byte[] allowedFingerprint: fingerprints) { + if (Arrays.equals(fingerprint, allowedFingerprint)) { + found = true; + break; + } + } + + if (!found) { + throw new CertificateException( + type + " certificate with unknown fingerprint: " + cert.getSubjectDN()); + } + } + + private byte[] fingerprint(X509Certificate cert) throws CertificateEncodingException { + MessageDigest md = tlmd.get(); + md.reset(); + return md.digest(cert.getEncoded()); + } + + public X509Certificate[] getAcceptedIssuers() { + return EmptyArrays.EMPTY_X509_CERTIFICATES; + } + }; + + private final byte[][] fingerprints; + + public FingerprintTrustManagerFactory(Iterable fingerprints) { + this(toFingerprintArray(fingerprints)); + } + + public FingerprintTrustManagerFactory(String... fingerprints) { + this(toFingerprintArray(Arrays.asList(fingerprints))); + } + + public FingerprintTrustManagerFactory(byte[]... fingerprints) { + if (fingerprints == null) { + throw new NullPointerException("fingerprints"); + } + + List list = new ArrayList(); + for (byte[] f: fingerprints) { + if (f == null) { + break; + } + if (f.length != SHA1_BYTE_LEN) { + throw new IllegalArgumentException("malformed fingerprint: " + + ChannelBuffers.hexDump(ChannelBuffers.wrappedBuffer(f)) + " (expected: SHA1)"); + } + list.add(f.clone()); + } + + this.fingerprints = list.toArray(new byte[list.size()][]); + } + + private static byte[][] toFingerprintArray(Iterable fingerprints) { + if (fingerprints == null) { + throw new NullPointerException("fingerprints"); + } + + List list = new ArrayList(); + for (String f: fingerprints) { + if (f == null) { + break; + } + + if (!FINGERPRINT_PATTERN.matcher(f).matches()) { + throw new IllegalArgumentException("malformed fingerprint: " + f); + } + f = FINGERPRINT_STRIP_PATTERN.matcher(f).replaceAll(""); + if (f.length() != SHA1_HEX_LEN) { + throw new IllegalArgumentException("malformed fingerprint: " + f + " (expected: SHA1)"); + } + + byte[] farr = new byte[SHA1_BYTE_LEN]; + for (int i = 0; i < farr.length; i ++) { + int strIdx = i << 1; + farr[i] = (byte) Integer.parseInt(f.substring(strIdx, strIdx + 2), 16); + } + } + + return list.toArray(new byte[list.size()][]); + } + + @Override + protected void engineInit(KeyStore keyStore) throws Exception { } + + @Override + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return new TrustManager[] { tm }; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/InsecureTrustManagerFactory.java b/src/main/java/org/jboss/netty/handler/ssl/util/InsecureTrustManagerFactory.java new file mode 100644 index 0000000000..348dd0cd48 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/InsecureTrustManagerFactory.java @@ -0,0 +1,61 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +public final class InsecureTrustManagerFactory extends SimpleTrustManagerFactory { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(InsecureTrustManagerFactory.class); + + public static final TrustManagerFactory INSTANCE = new InsecureTrustManagerFactory(); + + private static final TrustManager tm = new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String s) { + logger.debug("Accepting a client certificate: " + chain[0].getSubjectDN()); + } + + public void checkServerTrusted(X509Certificate[] chain, String s) { + logger.debug("Accepting a server certificate: " + chain[0].getSubjectDN()); + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + private InsecureTrustManagerFactory() { } + + @Override + protected void engineInit(KeyStore keyStore) throws Exception { } + + @Override + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return new TrustManager[] { tm }; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java b/src/main/java/org/jboss/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java new file mode 100644 index 0000000000..4d6f9a2a26 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java @@ -0,0 +1,69 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import sun.security.x509.AlgorithmId; +import sun.security.x509.CertificateAlgorithmId; +import sun.security.x509.CertificateIssuerName; +import sun.security.x509.CertificateSerialNumber; +import sun.security.x509.CertificateSubjectName; +import sun.security.x509.CertificateValidity; +import sun.security.x509.CertificateVersion; +import sun.security.x509.CertificateX509Key; +import sun.security.x509.X500Name; +import sun.security.x509.X509CertImpl; +import sun.security.x509.X509CertInfo; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; + +import static org.jboss.netty.handler.ssl.util.SelfSignedCertificate.*; + +final class OpenJdkSelfSignedCertGenerator { + + static String[] generate(String fqdn, KeyPair keypair, SecureRandom random) throws Exception { + PrivateKey key = keypair.getPrivate(); + + // Prepare the information required for generating an X.509 certificate. + X509CertInfo info = new X509CertInfo(); + X500Name owner = new X500Name("CN=" + fqdn); + info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); + info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, random))); + info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner)); + info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner)); + info.set(X509CertInfo.VALIDITY, new CertificateValidity(NOT_BEFORE, NOT_AFTER)); + info.set(X509CertInfo.KEY, new CertificateX509Key(keypair.getPublic())); + info.set(X509CertInfo.ALGORITHM_ID, + new CertificateAlgorithmId(new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid))); + + // Sign the cert to identify the algorithm that's used. + X509CertImpl cert = new X509CertImpl(info); + cert.sign(key, "SHA1withRSA"); + + // Update the algorithm and sign again. + info.set(CertificateAlgorithmId.NAME + '.' + CertificateAlgorithmId.ALGORITHM, cert.get(X509CertImpl.SIG_ALG)); + cert = new X509CertImpl(info); + cert.sign(key, "SHA1withRSA"); + cert.verify(keypair.getPublic()); + + return newSelfSignedCertificate(fqdn, key, cert); + } + + private OpenJdkSelfSignedCertGenerator() { } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/SelfSignedCertificate.java b/src/main/java/org/jboss/netty/handler/ssl/util/SelfSignedCertificate.java new file mode 100644 index 0000000000..85b9a6cf1c --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/SelfSignedCertificate.java @@ -0,0 +1,177 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.handler.codec.base64.Base64; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; +import org.jboss.netty.util.CharsetUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; + +/** + * Generates a temporary self-signed certificate for testing purposes. + * A X.509 certificate file and a RSA private key file are generated in a system's temporary directory + * using {@link java.io.File#createTempFile(String, String)}, and they are deleted when the JVM exits + * using {@link java.io.File#deleteOnExit()}. + *

+ * At first, this method tries to use OpenJDK's X.509 implementation ({@code sun.security.x509}). + * If it fails, it secondly tries to use Bouncy Castle. + *

+ */ +public final class SelfSignedCertificate { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SelfSignedCertificate.class); + + /** Current time minus 1 year, just in case software clock goes back due to time synchronization */ + static final Date NOT_BEFORE = new Date(System.currentTimeMillis() - 86400000L * 365); + /** The maximum possible value in X.509 specification: 9999-12-31 23:59:59 */ + static final Date NOT_AFTER = new Date(253402300799000L); + + private final File certificate; + private final File privateKey; + + public SelfSignedCertificate() throws CertificateException { + this("example.com"); + } + + public SelfSignedCertificate(String fqdn) throws CertificateException { + // Bypass entrophy collection by using insecure random generator. + // We just want to generate it without any delay because it's for testing purposes only. + this(fqdn, ThreadLocalInsecureRandom.current(), 1024); + } + + public SelfSignedCertificate(String fqdn, SecureRandom random, int bits) throws CertificateException { + // Generate an RSA key pair. + final KeyPair keypair; + try { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(bits, random); + keypair = keyGen.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + // Should not reach here because every Java implementation must have RSA key pair generator. + throw new Error(e); + } + + String[] paths; + try { + // Try the OpenJDK's proprietary implementation. + paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random); + } catch (Throwable t) { + logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t); + try { + // Try Bouncy Castle if the current JVM didn't have sun.security.x509. + paths = BouncyCastleSelfSignedCertGenerator.generate(fqdn, keypair, random); + } catch (Throwable t2) { + logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t2); + throw new CertificateException( + "No provider succeeded to generate a self-signed certificate. " + + "See debug log for the root cause."); + } + } + + certificate = new File(paths[0]); + privateKey = new File(paths[1]); + } + + public File certificate() { + return certificate; + } + + public File privateKey() { + return privateKey; + } + + public void delete() { + safeDelete(certificate); + safeDelete(privateKey); + } + + static String[] newSelfSignedCertificate( + String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException { + + // Encode the private key into a file. + String keyText = "-----BEGIN PRIVATE KEY-----\n" + + Base64.encode(ChannelBuffers.wrappedBuffer(key.getEncoded()), true).toString(CharsetUtil.US_ASCII) + + "\n-----END PRIVATE KEY-----\n"; + + File keyFile = File.createTempFile("keyutil_" + fqdn + '_', ".key"); + keyFile.deleteOnExit(); + + OutputStream keyOut = new FileOutputStream(keyFile); + try { + keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII.name())); + keyOut.close(); + keyOut = null; + } finally { + if (keyOut != null) { + safeClose(keyFile, keyOut); + safeDelete(keyFile); + } + } + + // Encode the certificate into a CRT file. + String certText = "-----BEGIN CERTIFICATE-----\n" + + Base64.encode(ChannelBuffers.wrappedBuffer(cert.getEncoded()), true).toString(CharsetUtil.US_ASCII) + + "\n-----END CERTIFICATE-----\n"; + + File certFile = File.createTempFile("keyutil_" + fqdn + '_', ".crt"); + certFile.deleteOnExit(); + + OutputStream certOut = new FileOutputStream(certFile); + try { + certOut.write(certText.getBytes(CharsetUtil.US_ASCII.name())); + certOut.close(); + certOut = null; + } finally { + if (certOut != null) { + safeClose(certFile, certOut); + safeDelete(certFile); + safeDelete(keyFile); + } + } + + return new String[] { certFile.getPath(), keyFile.getPath() }; + } + + private static void safeDelete(File certFile) { + if (!certFile.delete()) { + logger.warn("Failed to delete a file: " + certFile); + } + } + + private static void safeClose(File keyFile, OutputStream keyOut) { + try { + keyOut.close(); + } catch (IOException e) { + logger.warn("Failed to close a file: " + keyFile, e); + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/SimpleTrustManagerFactory.java b/src/main/java/org/jboss/netty/handler/ssl/util/SimpleTrustManagerFactory.java new file mode 100644 index 0000000000..3554c0aba8 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/SimpleTrustManagerFactory.java @@ -0,0 +1,109 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManagerFactorySpi; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; + +/** + * Helps to implement a custom {@link TrustManagerFactory}. + */ +public abstract class SimpleTrustManagerFactory extends TrustManagerFactory { + + private static final Provider PROVIDER = new Provider("", 0.0, "") { + private static final long serialVersionUID = -2680540247105807895L; + }; + + /** + * {@link SimpleTrustManagerFactorySpi} must have a reference to {@link SimpleTrustManagerFactory} + * to delegate its callbacks back to {@link SimpleTrustManagerFactory}. However, it is impossible to do so, + * because {@link TrustManagerFactory} requires {@link TrustManagerFactorySpi} at construction time and + * does not provide a way to access it later. + * + * To work around this issue, we use an ugly hack which uses a {@link ThreadLocal}. + */ + private static final ThreadLocal CURRENT_SPI = + new ThreadLocal() { + @Override + protected SimpleTrustManagerFactorySpi initialValue() { + return new SimpleTrustManagerFactorySpi(); + } + }; + + protected SimpleTrustManagerFactory() { + this(""); + } + + protected SimpleTrustManagerFactory(String name) { + super(CURRENT_SPI.get(), PROVIDER, name); + CURRENT_SPI.get().init(this); + CURRENT_SPI.remove(); + + if (name == null) { + throw new NullPointerException("name"); + } + } + + protected abstract void engineInit(KeyStore keyStore) throws Exception; + + protected abstract void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception; + + protected abstract TrustManager[] engineGetTrustManagers(); + + static final class SimpleTrustManagerFactorySpi extends TrustManagerFactorySpi { + + private SimpleTrustManagerFactory parent; + + void init(SimpleTrustManagerFactory parent) { + this.parent = parent; + } + + @Override + protected void engineInit(KeyStore keyStore) throws KeyStoreException { + try { + parent.engineInit(keyStore); + } catch (KeyStoreException e) { + throw e; + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + @Override + protected void engineInit( + ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException { + try { + parent.engineInit(managerFactoryParameters); + } catch (InvalidAlgorithmParameterException e) { + throw e; + } catch (Exception e) { + throw new InvalidAlgorithmParameterException(e); + } + } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return parent.engineGetTrustManagers(); + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/ThreadLocalInsecureRandom.java b/src/main/java/org/jboss/netty/handler/ssl/util/ThreadLocalInsecureRandom.java new file mode 100644 index 0000000000..7aeda52a9d --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/ThreadLocalInsecureRandom.java @@ -0,0 +1,100 @@ +/* + * 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 org.jboss.netty.handler.ssl.util; + +import org.jboss.netty.util.internal.ThreadLocalRandom; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * Insecure {@link SecureRandom} which relies on {@link ThreadLocalRandom} for random number generation. + */ +final class ThreadLocalInsecureRandom extends SecureRandom { + + private static final long serialVersionUID = -8209473337192526191L; + + private static final SecureRandom INSTANCE = new ThreadLocalInsecureRandom(); + + static SecureRandom current() { + return INSTANCE; + } + + private ThreadLocalInsecureRandom() { } + + @Override + public String getAlgorithm() { + return "insecure"; + } + + @Override + public void setSeed(byte[] seed) { } + + @Override + public void setSeed(long seed) { } + + @Override + public void nextBytes(byte[] bytes) { + random().nextBytes(bytes); + } + + @Override + public byte[] generateSeed(int numBytes) { + byte[] seed = new byte[numBytes]; + random().nextBytes(seed); + return seed; + } + + @Override + public int nextInt() { + return random().nextInt(); + } + + @Override + public int nextInt(int n) { + return random().nextInt(n); + } + + @Override + public boolean nextBoolean() { + return random().nextBoolean(); + } + + @Override + public long nextLong() { + return random().nextLong(); + } + + @Override + public float nextFloat() { + return random().nextFloat(); + } + + @Override + public double nextDouble() { + return random().nextDouble(); + } + + @Override + public double nextGaussian() { + return random().nextGaussian(); + } + + private static Random random() { + return ThreadLocalRandom.current(); + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/util/package-info.java b/src/main/java/org/jboss/netty/handler/ssl/util/package-info.java new file mode 100644 index 0000000000..b9d7c3e7b0 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 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. + */ + +/** + * Utility classes that helps easier development of TLS/SSL applications. + */ +package org.jboss.netty.handler.ssl.util; diff --git a/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java b/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java index 65cb0f2dd6..7393f35691 100644 --- a/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java +++ b/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java @@ -17,6 +17,7 @@ package org.jboss.netty.util.internal; import java.nio.ByteBuffer; +import java.security.cert.X509Certificate; public final class EmptyArrays { @@ -31,6 +32,7 @@ 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 X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; private EmptyArrays() { } } diff --git a/src/main/java/org/jboss/netty/util/internal/ThreadLocalRandom.java b/src/main/java/org/jboss/netty/util/internal/ThreadLocalRandom.java index dcb4b2074c..723d54d756 100644 --- a/src/main/java/org/jboss/netty/util/internal/ThreadLocalRandom.java +++ b/src/main/java/org/jboss/netty/util/internal/ThreadLocalRandom.java @@ -46,7 +46,7 @@ import java.util.Random; * * @since 1.7 */ -final class ThreadLocalRandom extends Random { +public final class ThreadLocalRandom extends Random { // same constants as Random, but must be redeclared because private private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; @@ -87,7 +87,7 @@ final class ThreadLocalRandom extends Random { * * @return the current thread's {@code ThreadLocalRandom} */ - static ThreadLocalRandom current() { + public static ThreadLocalRandom current() { return localRandom.get(); } diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java deleted file mode 100644 index a51cdf54fa..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; - -import java.util.concurrent.Executor; - -public class NioNioSocketSslEchoTest extends AbstractSocketSslEchoTest { - - public NioNioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new NioClientSocketChannelFactory(executor, executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new NioServerSocketChannelFactory(executor, executor); - } -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java deleted file mode 100644 index 3aac984624..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java +++ /dev/null @@ -1,41 +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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory; - -import java.util.concurrent.Executor; - -public class NioNioSocketSslGreetingTest extends AbstractSocketSslGreetingTest { - - public NioNioSocketSslGreetingTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new NioClientSocketChannelFactory(executor, executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new NioServerSocketChannelFactory(executor, executor); - } - -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java deleted file mode 100644 index 35d771f982..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory; - -import java.util.concurrent.Executor; - -public class NioOioSocketSslEchoTest extends AbstractSocketSslEchoTest { - - public NioOioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new NioClientSocketChannelFactory(executor, executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new OioServerSocketChannelFactory(executor, executor); - } - -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java deleted file mode 100644 index bfeea1bb83..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; - -import java.util.concurrent.Executor; - -public class OioNioSocketSslEchoTest extends AbstractSocketSslEchoTest { - - public OioNioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new OioClientSocketChannelFactory(executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new NioServerSocketChannelFactory(executor, executor); - } - -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java deleted file mode 100644 index 8227ac96d9..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java +++ /dev/null @@ -1,41 +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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory; - -import java.util.concurrent.Executor; - -public class OioNioSocketSslGreetingTest extends AbstractSocketSslGreetingTest { - - public OioNioSocketSslGreetingTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new OioClientSocketChannelFactory(executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new NioServerSocketChannelFactory(executor, executor); - } - -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java deleted file mode 100644 index 345f36f375..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012 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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory; - -import java.util.concurrent.Executor; - -public class OioOioSocketSslEchoTest extends AbstractSocketSslEchoTest { - - public OioOioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - super(serverEngineFactory, clientEngineFactory); - } - - @Override - protected ChannelFactory newClientSocketChannelFactory(Executor executor) { - return new OioClientSocketChannelFactory(executor); - } - - @Override - protected ChannelFactory newServerSocketChannelFactory(Executor executor) { - return new OioServerSocketChannelFactory(executor, executor); - } - - @Override - protected boolean isExecutorRequired() { - return true; - } -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/SocketSslEchoTest.java similarity index 53% rename from src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java rename to src/test/java/org/jboss/netty/handler/ssl/SocketSslEchoTest.java index f56cf00cfa..c858b89a54 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/SocketSslEchoTest.java @@ -27,156 +27,35 @@ import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; -import org.jboss.netty.handler.execution.ExecutionHandler; -import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.util.TestUtil; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import javax.net.ssl.SSLEngine; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.Random; -import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; -@RunWith(Parameterized.class) -public abstract class AbstractSocketSslEchoTest { - static final InternalLogger logger = - InternalLoggerFactory.getInstance(AbstractSocketSslEchoTest.class); +public class SocketSslEchoTest extends SslTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslEchoTest.class); private static final Random random = new Random(); + static final byte[] data = new byte[1048576]; static { random.nextBytes(data); } - @Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}") - public static Collection engines() throws Exception { - List params = new ArrayList(); - - List serverEngines = new ArrayList(); - serverEngines.add(new SSLEngineFactory("JDK") { - @Override - public SSLEngine newEngine() { - return SecureChatSslContextFactory.getServerContext().createSSLEngine(); - } - }); - - List clientEngines = new ArrayList(); - clientEngines.add(new SSLEngineFactory("JDK") { - @Override - public SSLEngine newEngine() { - return SecureChatSslContextFactory.getClientContext().createSSLEngine(); - } - }); - - boolean hasOpenSsl = OpenSsl.isAvailable(); - if (hasOpenSsl) { - final String certPath = File.createTempFile("ssl_test_", ".crt").getAbsolutePath(); - new File(certPath).deleteOnExit(); - final String keyPath = File.createTempFile("ssl_test_", ".pem").getAbsolutePath(); - new File(keyPath).deleteOnExit(); - - ClassLoader cl = AbstractSocketSslEchoTest.class.getClassLoader(); - FileOutputStream certOut = new FileOutputStream(certPath); - InputStream certIn = cl.getResourceAsStream("openssl.crt"); - for (;;) { - int b = certIn.read(); - if (b < 0) { - break; - } - certOut.write(b); - } - certOut.close(); - - FileOutputStream keyOut = new FileOutputStream(keyPath); - InputStream keyIn = cl.getResourceAsStream("openssl.pem"); - for (;;) { - int b = keyIn.read(); - if (b < 0) { - break; - } - keyOut.write(b); - } - keyOut.close(); - - final OpenSslContextBuilder ctxBuilder = new OpenSslContextBuilder(); - final OpenSslServerContext ctx = ctxBuilder.certPath(certPath).keyPath(keyPath).newServerContext(); - - serverEngines.add(new SSLEngineFactory("TCNative") { - @Override - public SSLEngine newEngine() throws Exception { - return ctx.newEngine(); - } - }); - - // TODO: Client mode is not supported yet. - /* - clientEngines.add(new SSLEngineFactory("TCNative") { - public SSLEngine newEngine() { - return new OpenSSLEngine(contextHolder, sslBufferPool); - } - }); - */ - } else { - logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); - } - - for (SSLEngineFactory sf: serverEngines) { - for (SSLEngineFactory cf: clientEngines) { - params.add(new SSLEngineFactory[] { sf, cf }); - } - } - - return params; - } - - protected abstract static class SSLEngineFactory { - - private final String name; - - SSLEngineFactory(String name) { - this.name = name; - } - - public abstract SSLEngine newEngine() throws Exception; - - @Override - public String toString() { - return name; - } - } - - private final SSLEngineFactory serverEngineFactory; - private final SSLEngineFactory clientEngineFactory; - - protected AbstractSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - this.serverEngineFactory = serverEngineFactory; - this.clientEngineFactory = clientEngineFactory; - } - - protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); - protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); - - protected boolean isExecutorRequired() { - return false; + public SocketSslEchoTest( + SslContext serverCtx, SslContext clientCtx, + ChannelFactory serverChannelFactory, ChannelFactory clientChannelFactory) { + super(serverCtx, clientCtx, serverChannelFactory, clientChannelFactory); } @Test @@ -203,44 +82,34 @@ public abstract class AbstractSocketSslEchoTest { private void testSslEcho( boolean serverUsesDelegatedTaskExecutor, boolean clientUsesDelegatedTaskExecutor) throws Throwable { ExecutorService delegatedTaskExecutor = Executors.newCachedThreadPool(); - ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(Executors.newCachedThreadPool())); - ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(Executors.newCachedThreadPool())); + ServerBootstrap sb = new ServerBootstrap(serverChannelFactory); + ClientBootstrap cb = new ClientBootstrap(clientChannelFactory); EchoHandler sh = new EchoHandler(true); EchoHandler ch = new EchoHandler(false); - SSLEngine sse = serverEngineFactory.newEngine(); - SSLEngine cse = clientEngineFactory.newEngine(); - sse.setUseClientMode(false); - cse.setUseClientMode(true); - // Workaround for blocking I/O transport write-write dead lock. sb.setOption("receiveBufferSize", 1048576); sb.setOption("receiveBufferSize", 1048576); // Configure the server pipeline. if (serverUsesDelegatedTaskExecutor) { - sb.getPipeline().addFirst("ssl", new SslHandler(sse, delegatedTaskExecutor)); + sb.getPipeline().addFirst( + "ssl", new SslHandler(serverCtx.newEngine(), serverCtx.bufferPool(), delegatedTaskExecutor)); } else { - sb.getPipeline().addFirst("ssl", new SslHandler(sse)); + sb.getPipeline().addFirst("ssl", serverCtx.newHandler()); } sb.getPipeline().addLast("handler", sh); // Configure the client pipeline. if (clientUsesDelegatedTaskExecutor) { - cb.getPipeline().addFirst("ssl", new SslHandler(cse, delegatedTaskExecutor)); + cb.getPipeline().addFirst( + "ssl", new SslHandler(clientCtx.newEngine(), clientCtx.bufferPool(), delegatedTaskExecutor)); } else { - cb.getPipeline().addFirst("ssl", new SslHandler(cse)); + cb.getPipeline().addFirst("ssl", clientCtx.newHandler()); } cb.getPipeline().addLast("handler", ch); - ExecutorService eventExecutor = null; - if (isExecutorRequired()) { - eventExecutor = new OrderedMemoryAwareThreadPoolExecutor(16, 0, 0); - sb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor)); - cb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor)); - } - Channel sc = sb.bind(new InetSocketAddress(0)); int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); @@ -303,15 +172,8 @@ public abstract class AbstractSocketSslEchoTest { sh.channel.close().awaitUninterruptibly(); ch.channel.close().awaitUninterruptibly(); sc.close().awaitUninterruptibly(); - cb.shutdown(); - sb.shutdown(); - cb.releaseExternalResources(); - sb.releaseExternalResources(); delegatedTaskExecutor.shutdown(); - if (eventExecutor != null) { - eventExecutor.shutdown(); - } if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { throw sh.exception.get(); } diff --git a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/SocketSslGreetingTest.java similarity index 73% rename from src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java rename to src/test/java/org/jboss/netty/handler/ssl/SocketSslGreetingTest.java index 4ce7b4ad0a..2a06ae6064 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/SocketSslGreetingTest.java @@ -27,70 +27,45 @@ import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.util.TestUtil; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import javax.net.ssl.SSLEngine; import java.io.IOException; import java.net.InetSocketAddress; -import java.util.Collection; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; -@RunWith(Parameterized.class) -public abstract class AbstractSocketSslGreetingTest { - static final InternalLogger logger = - InternalLoggerFactory.getInstance(AbstractSocketSslGreetingTest.class); +public class SocketSslGreetingTest extends SslTest { - @Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}") - public static Collection engines() throws Exception { - return AbstractSocketSslEchoTest.engines(); - } + static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslGreetingTest.class); private final ChannelBuffer greeting = ChannelBuffers.wrappedBuffer(new byte[] {'a'}); - protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); - protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); - - private final SSLEngineFactory serverEngineFactory; - private final SSLEngineFactory clientEngineFactory; - - protected AbstractSocketSslGreetingTest( - SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) { - this.serverEngineFactory = serverEngineFactory; - this.clientEngineFactory = clientEngineFactory; + public SocketSslGreetingTest( + SslContext serverCtx, SslContext clientCtx, + ChannelFactory serverChannelFactory, ChannelFactory clientChannelFactory) { + super(serverCtx, clientCtx, serverChannelFactory, clientChannelFactory); } @Test public void testSslEcho() throws Throwable { - ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(Executors.newCachedThreadPool())); - ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(Executors.newCachedThreadPool())); + ServerBootstrap sb = new ServerBootstrap(serverChannelFactory); + ClientBootstrap cb = new ClientBootstrap(clientChannelFactory); ServerHandler sh = new ServerHandler(); ClientHandler ch = new ClientHandler(); - SSLEngine sse = serverEngineFactory.newEngine(); - SSLEngine cse = clientEngineFactory.newEngine(); - sse.setUseClientMode(false); - cse.setUseClientMode(true); - // Workaround for blocking I/O transport write-write dead lock. sb.setOption("receiveBufferSize", 1048576); sb.setOption("receiveBufferSize", 1048576); - sb.getPipeline().addFirst("ssl", new SslHandler(sse)); + sb.getPipeline().addFirst("ssl", serverCtx.newHandler()); sb.getPipeline().addLast("handler", sh); - cb.getPipeline().addFirst("ssl", new SslHandler(cse)); + cb.getPipeline().addFirst("ssl", clientCtx.newHandler()); cb.getPipeline().addLast("handler", ch); Channel sc = sb.bind(new InetSocketAddress(0)); @@ -120,10 +95,6 @@ public abstract class AbstractSocketSslGreetingTest { sh.channel.close().awaitUninterruptibly(); cc.close().awaitUninterruptibly(); sc.close().awaitUninterruptibly(); - cb.shutdown(); - sb.shutdown(); - cb.releaseExternalResources(); - sb.releaseExternalResources(); if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { throw sh.exception.get(); diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java b/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java index 3664ac4d47..060ad9b570 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java @@ -19,33 +19,33 @@ import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; import org.jboss.netty.util.CharsetUtil; import org.junit.Assert; import org.junit.Test; -import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; -public class SslCloseTest { +public class SslCloseTest extends SslTest{ + + public SslCloseTest( + SslContext serverCtx, SslContext clientCtx, + ChannelFactory serverChannelFactory, ChannelFactory clientChannelFactory) { + super(serverCtx, clientCtx, serverChannelFactory, clientChannelFactory); + } /** * Try to write a testcase to reproduce #343 */ @Test public void testCloseOnSslException() { - ServerBootstrap sb = new ServerBootstrap(new NioServerSocketChannelFactory()); - ClientBootstrap cb = new ClientBootstrap(new NioClientSocketChannelFactory()); + ServerBootstrap sb = new ServerBootstrap(serverChannelFactory); + ClientBootstrap cb = new ClientBootstrap(clientChannelFactory); - SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - sse.setUseClientMode(false); - - sb.getPipeline().addFirst("ssl", new SslHandler(sse)); + sb.getPipeline().addFirst("ssl", serverCtx.newHandler()); sb.getPipeline().addLast("handler", new SimpleChannelUpstreamHandler() { @Override @@ -64,8 +64,5 @@ public class SslCloseTest { Assert.assertTrue(cc.getCloseFuture().awaitUninterruptibly(5000)); serverChannel.close(); - - cb.releaseExternalResources(); - sb.releaseExternalResources(); } } diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslHandlerTest.java b/src/test/java/org/jboss/netty/handler/ssl/SslHandlerTest.java deleted file mode 100644 index dec067cf5a..0000000000 --- a/src/test/java/org/jboss/netty/handler/ssl/SslHandlerTest.java +++ /dev/null @@ -1,45 +0,0 @@ - /* - * 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 org.jboss.netty.handler.ssl; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; -import org.jboss.netty.handler.codec.embedder.CodecEmbedderException; -import org.jboss.netty.handler.codec.embedder.DecoderEmbedder; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import java.util.Random; - -public class SslHandlerTest { - - private final Random random = new Random(); - - @Test - @Ignore - public void testDetectNonSslRecord() { - byte[] data = new byte[1024]; - random.nextBytes(data); - - DecoderEmbedder em = new DecoderEmbedder(new SslHandler(SecureChatSslContextFactory.getServerContext().createSSLEngine())); - try { - em.offer(ChannelBuffers.wrappedBuffer(data)); - Assert.fail(); - } catch (CodecEmbedderException e) { - Assert.assertTrue(e.getCause() instanceof NotSslRecordException); - } - } -} diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java b/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTest.java similarity index 69% rename from src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java rename to src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTest.java index fdf99d897e..7508b4a9b0 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java +++ b/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTest.java @@ -19,6 +19,7 @@ import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; @@ -29,20 +30,15 @@ import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.channel.socket.ClientSocketChannelFactory; -import org.jboss.netty.channel.socket.ServerSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.example.securechat.SecureChatSslContextFactory; +import org.junit.Test; -import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; -public class SslHandshakeRaceTester { +public class SslHandshakeRaceTest extends SslTest { + + private static final int COUNT = 10; private static final Random random = new Random(); static final byte[] data = new byte[1048576]; @@ -52,35 +48,25 @@ public class SslHandshakeRaceTester { random.nextBytes(data); } - public void run(int rounds, boolean nio) throws Throwable { - ClientSocketChannelFactory clientFactory; - if (nio) { - clientFactory = new NioClientSocketChannelFactory(); - } else { - clientFactory = new OioClientSocketChannelFactory(); - } - ClientBootstrap cb = new ClientBootstrap(clientFactory); + public SslHandshakeRaceTest( + SslContext serverCtx, SslContext clientCtx, + ChannelFactory serverChannelFactory, ChannelFactory clientChannelFactory) { + super(serverCtx, clientCtx, serverChannelFactory, clientChannelFactory); + } + + @Test + public void testHandshakeRace() throws Throwable { + ClientBootstrap cb = new ClientBootstrap(clientChannelFactory); cb.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { ChannelPipeline cp = Channels.pipeline(); - - SSLEngine cse = SecureChatSslContextFactory.getClientContext().createSSLEngine(); - cse.setUseClientMode(true); - cp.addFirst("ssl", new SslHandler(cse)); - + cp.addFirst("ssl", clientCtx.newHandler()); cp.addLast("handler", new TestHandler()); return cp; } }); - ServerSocketChannelFactory serverFactory; - if (nio) { - serverFactory = new NioServerSocketChannelFactory(); - } else { - serverFactory = new OioServerSocketChannelFactory(); - } - ServerBootstrap sb = new ServerBootstrap(serverFactory); - + ServerBootstrap sb = new ServerBootstrap(serverChannelFactory); sb.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { ChannelPipeline cp = Channels.pipeline(); @@ -98,14 +84,13 @@ public class SslHandshakeRaceTester { }); ++count; - System.out.println("Connection #" + count); + if (count % 100 == 0) { + System.out.println("Connection #" + count); + } } }); - SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - sse.setUseClientMode(false); - cp.addFirst("ssl", new SslHandler(sse)); - + cp.addFirst("ssl", serverCtx.newHandler()); cp.addLast("handler", new TestHandler()); return cp; } @@ -114,22 +99,17 @@ public class SslHandshakeRaceTester { Channel sc = sb.bind(new InetSocketAddress(0)); int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); - for (int i = 0; i < rounds; i++) { + for (int i = 0; i < COUNT; i++) { connectAndSend(cb, port); } - cb.shutdown(); - cb.releaseExternalResources(); sc.close().awaitUninterruptibly(); - sb.shutdown(); - sb.releaseExternalResources(); } private static void connectAndSend(ClientBootstrap cb, int port) throws Throwable { ChannelFuture ccf = cb.connect(new InetSocketAddress("127.0.0.1", port)); ccf.awaitUninterruptibly(); if (!ccf.isSuccess()) { - ccf.getCause().printStackTrace(); throw ccf.getCause(); } TestHandler ch = ccf.getChannel().getPipeline().get(TestHandler.class); @@ -138,7 +118,6 @@ public class SslHandshakeRaceTester { ChannelFuture hf = cc.getPipeline().get(SslHandler.class).handshake(); hf.awaitUninterruptibly(); if (!hf.isSuccess()) { - hf.getCause().printStackTrace(); ch.channel.close(); throw hf.getCause(); } @@ -183,15 +162,4 @@ public class SslHandshakeRaceTester { e.getChannel().close(); } } - - public static void main(String[] args) throws Throwable { - int count = 20000; - boolean nio = false; - if (args.length == 2) { - count = Integer.parseInt(args[0]); - nio = Boolean.parseBoolean(args[1]); - } - SslHandshakeRaceTester test = new SslHandshakeRaceTester(); - test.run(count, nio); - } } diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslTest.java b/src/test/java/org/jboss/netty/handler/ssl/SslTest.java new file mode 100644 index 0000000000..5bf7522365 --- /dev/null +++ b/src/test/java/org/jboss/netty/handler/ssl/SslTest.java @@ -0,0 +1,120 @@ +/* + * 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 org.jboss.netty.handler.ssl; + +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; +import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.util.SelfSignedCertificate; +import org.jboss.netty.logging.InternalLogger; +import org.jboss.netty.logging.InternalLoggerFactory; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.io.File; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@RunWith(Parameterized.class) +public abstract class SslTest { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SslTest.class); + private static final ExecutorService executor = Executors.newCachedThreadPool(); + + private static final File CERT_FILE; + private static final File KEY_FILE; + + static { + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + @Parameters(name = + "{index}: serverCtx = {0}, clientCtx = {1}, serverChannelFactory = {2}, clientChannelFactory = {3}") + public static Collection params() throws Exception { + // Populate the permutations. + List params = new ArrayList(); + + List serverContexts = new ArrayList(); + serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE)); + + List clientContexts = new ArrayList(); + clientContexts.add(new JdkSslClientContext(CERT_FILE)); + + List serverChannelFactories = new ArrayList(); + serverChannelFactories.add(new NioServerSocketChannelFactory(executor, executor)); + serverChannelFactories.add(new OioServerSocketChannelFactory(executor, executor)); + + List clientChannelFactories = new ArrayList(); + clientChannelFactories.add(new NioClientSocketChannelFactory(executor, executor)); + clientChannelFactories.add(new OioClientSocketChannelFactory(executor)); + + boolean hasOpenSsl = OpenSsl.isAvailable(); + if (hasOpenSsl) { + serverContexts.add(new OpenSslServerContext(null, CERT_FILE, KEY_FILE, null, null, null, 0, 0)); + + // TODO: Client mode is not supported yet. + // clientContexts.add(new OpenSslContext(null, CERT_FILE, null, null, null, 0, 0)); + } else { + logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); + } + + for (SslContext sctx: serverContexts) { + for (SslContext cctx: clientContexts) { + for (ChannelFactory scf: serverChannelFactories) { + for (ChannelFactory ccf: clientChannelFactories) { + if (scf instanceof OioServerSocketChannelFactory && + ccf instanceof OioClientSocketChannelFactory) { + continue; + } + + params.add(new Object[] { sctx, cctx, scf, ccf }); + } + } + } + } + + return params; + } + + protected final SslContext serverCtx; + protected final SslContext clientCtx; + protected final ChannelFactory serverChannelFactory; + protected final ChannelFactory clientChannelFactory; + + protected SslTest( + SslContext serverCtx, SslContext clientCtx, + ChannelFactory serverChannelFactory, ChannelFactory clientChannelFactory) { + this.serverCtx = serverCtx; + this.clientCtx = clientCtx; + this.serverChannelFactory = serverChannelFactory; + this.clientChannelFactory = clientChannelFactory; + } +} diff --git a/src/test/resources/openssl.crt b/src/test/resources/openssl.crt deleted file mode 100644 index cda3ea6464..0000000000 --- a/src/test/resources/openssl.crt +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFGzCCAwOgAwIBAgIJAKuufohIAUhRMA0GCSqGSIb3DQEBBQUAMCMxCzAJBgNV -BAYTAktSMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAgFw0xNDA0MjExMzMxMzJaGA8y -Mjg4MDIwNDEzMzEzMlowIzELMAkGA1UEBhMCS1IxFDASBgNVBAMMC2V4YW1wbGUu -Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxtiOHUgcCtSvEvH4 -cPgOuBEIy6EPoHlOfUm1S0m7vOy8ESynBuOAn5Be1QP8lB/F69DQoRD41OBbdrA1 -pcpIQmCORheKUwv3EZSt3kzVJonqE4HW1FKyQA8Ej5RqsfHtDaCfO8uzAmuJXMg2 -2Gj/6fO/BKuPdkrAwywIqAxW2bKSVwKSId1sRilRku+rK9a1d0xiWAGr8YC8KZrv -5101I7A9OcTAJtoI46OB5pcltjLhXKSyrN3wyoxsKVQkGHzB5Uvnua/E9CnOPmW8 -SDuQ/1w+/haUftKsFAPdjy5auDEIctti89YcOJaJfMzZP1hxrSAuDaMHKEdjySpx -DXxp3Nvab0uiwYEPCHKE72JjY7/3oaZ6AzNxpjSwyHLCmuFFg4BsL9V1zAuz7E6F -zxrm+CBq3J5U0NNBW9/zBTAqPTjBJSX7KZTvwlWMTs/kKs4pfeJX/Z97JYwUk7Kk -6uVfaoHIIezfksJX8/dzzXn0p11+3OkPqcHw/7dDtxlX5rtontCrhLbq0LhvAsrh -7dEOBnNFXxYzF7yNM7spBeRvqxcNPKNCclQiINeYbO8JCRBw+jmtevUDxqLiRJsu -7Q1SxUlnGD0MQZUvy4p5S+L/amE74qJDgSaCftWW7KKnAB0Z2ZaNHV3aZ+Sn6ExF -CSrTFEJzaXT7Cvbo6pa6XQ2sij0CAwEAAaNQME4wHQYDVR0OBBYEFHWMy8+VATLk -sDQGt9fMc6MssoS2MB8GA1UdIwQYMBaAFHWMy8+VATLksDQGt9fMc6MssoS2MAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggIBAFoGovC4fNFJjiSIo8BDNjOx -XEBdhUpzm+9i6h0PjBriwd2SyQHmldVXsshJmcmnNaC7cMrQG4cSk4DzzHAZIFl9 -xQmhktlJTqSzKr+bda27URQqw3rPIntvcQe3SchOG73wOLxtoar3z8G/o3m6kPyB -5l2ZjrI658iN11tjuc/wqmm5VCIOanWmJUDoryh/Ndg4nLcxsx36/iD+V/NV//xO -jTL1HdnLtK895hrW5Q6/1qN3KlerVoI4WOYvfcpNg380vfcxYuhZ+db5WHRJXG+I -V/nnDMmnFHgQbVqat7TZFUoEyac7BqScs5x25uXx6liTGZ9I7kpPrx3mE1fhatyp -poc+gSm1Va6tNo/UI+oo8NRbKWHJE2TSsMAuHDfyKzDi0yMdTPuVzpbLVjCgXVdv -cOUD05AX3imqlucwqhtFciwhvRgB82CZhBONrMr2zwWGedWMv50m+r80AFFvaTht -Xs+LWAtT29RuhhtsRz1yZFxvCiYHclpONGqy09Z1qYPQvqv51tz/SunbCjizhJm9 -/qNvQ/oqUPsPz30TV6GE9G6q6exS/S7zZdlwI/fNdnOTRbd0r0bvb8hLXc9Ooup8 -7F9Py5ZIvjgpmFj6aZHG2inzw5KVT/sR8kznipuTR5uqQNBlfUypjpY2BX+5sJ7d -UL8VfLsSGIwQN3CFZG8k ------END CERTIFICATE----- diff --git a/src/test/resources/openssl.pem b/src/test/resources/openssl.pem deleted file mode 100644 index 76aa8ebff3..0000000000 --- a/src/test/resources/openssl.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDG2I4dSBwK1K8S -8fhw+A64EQjLoQ+geU59SbVLSbu87LwRLKcG44CfkF7VA/yUH8Xr0NChEPjU4Ft2 -sDWlykhCYI5GF4pTC/cRlK3eTNUmieoTgdbUUrJADwSPlGqx8e0NoJ87y7MCa4lc -yDbYaP/p878Eq492SsDDLAioDFbZspJXApIh3WxGKVGS76sr1rV3TGJYAavxgLwp -mu/nXTUjsD05xMAm2gjjo4HmlyW2MuFcpLKs3fDKjGwpVCQYfMHlS+e5r8T0Kc4+ -ZbxIO5D/XD7+FpR+0qwUA92PLlq4MQhy22Lz1hw4lol8zNk/WHGtIC4NowcoR2PJ -KnENfGnc29pvS6LBgQ8IcoTvYmNjv/ehpnoDM3GmNLDIcsKa4UWDgGwv1XXMC7Ps -ToXPGub4IGrcnlTQ00Fb3/MFMCo9OMElJfsplO/CVYxOz+Qqzil94lf9n3sljBST -sqTq5V9qgcgh7N+Swlfz93PNefSnXX7c6Q+pwfD/t0O3GVfmu2ie0KuEturQuG8C -yuHt0Q4Gc0VfFjMXvI0zuykF5G+rFw08o0JyVCIg15hs7wkJEHD6Oa169QPGouJE -my7tDVLFSWcYPQxBlS/LinlL4v9qYTviokOBJoJ+1ZbsoqcAHRnZlo0dXdpn5Kfo -TEUJKtMUQnNpdPsK9ujqlrpdDayKPQIDAQABAoICAQC/zPzv07Fw5Qvmm04IMc2I -0K2KNVYsdTY3dZSRBZM4PaV+b3LBG0rjHs/KaukEO82elDHZWtSaCbtPtdJZk8+1 -bwttIqHDT0RHSgGX7safQOJvZItDDG1xisrcb82mzPPadDeD5w1JZU7/FwSIJGfN -U9bJ+24LLTnYSK4k4poXrL6pfQpV7g3Vc1+C+vlB9P3fD+fAegRPk9xryU5k/iwW -u5WjFlw+XYu7f+j58otmvpdQ1HCgfAgaZ+6gws96a0RgF6JyItA4r+aHm3xMtGA9 -YM5GKqOb3TwspjndNVo+VtWObH17M+jO0K3XmoRnLLmhw+uILdvmh04CKZUY7Avz -UJybZkchnODvC0htfpHB5+KYGu8S1RqnaV9UjQPBcw93Zvlz0+Z3lpd/kn3vzi/9 -03qiDId2bCsKamxFRMvdSzua3ZWfwT3y4/UApEASDc4lqbNU6Cck0vyJFmy06fEv -11ybp9bxovAE4vewHYSCrEZoLaFMafvhgSzv5IBU7+dLkGJ2EYyoQ9govJVrRoaY -OoSjUpUFhKfGAP5ExgV1oPKMySlGUyZa+SsYiZO7BEklQyLHYed5h+b5Mc0/O6m5 -yw6NXgLgPBswE1i7XNMSVstgyfF0+whh92FUSuFdcnC0l020qWkBg0e5t5CTV0Wt -BtO7eEH5XBLgcPJKtejApQKCAQEA7iXmGVn4OgYOXtloYFTsYMrf3JwX4u8lk3i8 -3giyhm5857aiHmToe+3PjH3xk9N7qmuBB39Oc6lH52wvpXMOQUMdZxgQpNTjmmlk -a4K2QGrHaef30nU8ZAIhSIz/Wz9XzMUyj7sgbNbuLtTa8xsasDEE+u4YO1dfSWp1 -dZuwgCH+dSnHiK/u7xaIEDoZrgK4IKYNfkVILM+Z6RaVmOoWM5nwbYaVjyfW5BLP -ER4LhlWSAsnnXSLx8WmV0sLnXaDQ+1ELx456bJr7xV/TYyDZ/eobCRuhFVRh4Wn8 -6mLZMklV621dogZDC+j0dXpXszi3B9qfD+ZpzFH4XPZvXfY1GwKCAQEA1cBxITNe -MzfCTMGfj6sgPIciMFfU2rLk2dajiAZy15tLl9Ggb3AwF5I4HAU9bWsQd2eossZR -s7AKppAHNO5KgQ9gap+kAhthOEvFMutODMc/YeOYAh00XtzXVA9cm5ll58W0M0tw -YOSf/sdkH45sXIh17IeSSCxYJGj9upc0RDmHIiKOxn2c/A7VcmWLUN/0iHBgJahw -g1CnZ3WSTpk+geB6WVabQZVVh/yW0Ew8nwPNqmfbYhIiQlaZrlQA40evXkuqI4RS -PIlPTuDSLmeJEAoCUCrjdnomVXSe9+64BIyvriTU2SVGfoW13Jb3l4jaKMNX5gU0 -tj/svTLSb3ErhwKCAQBK3Bz1kSOHUcoIpLy7s1ZOotc16NdVXalpLwXnocJEocyf -pfhWJ5AxDLM2TYy31D3Gd16q9ai6kNbqd0aO8pjjiO5gLt9kgQs+yaxoY0FZBHfE -4cc0H+go48aEoNXQYBwAYWigZ8ksjW93xy9ARh/gjLgtDNUPKkrosA9WNeeIj6cG -O2jenbc07tc8ipbx7SbmFGyfabXjaCrQa2oBFGyAMf6y3yB6TKKvNP5kOhaTVqi8 -Oa2ByG9XXMJc6ymBdADdTz+trirrothL1fCD85qyx3lSJp4/LzRJgGChaQsvVIAu -rkVFnkRLJSJbH2ccDlLypBVmER7mvEmfJ1QgrDlLAoIBABLFKmoK87u1qsdIvXVF -Rrhp+XObUuK9kOEcOxIH1u/eAD5dtyPyCxcWzPyQ7Tr9it2haawBRE1uKRFFVoys -XDiHHCVgzWst8lSaOr/k796pZIR5EvEklWO7KuGD09MKHmvC7bcPadfoW7Bo2/71 -Z8icZxaVNSBHXJod6iIHgxx2nMo/lKATicSMnOvFxP0eHYAZtie4aeDuJTUtmuJy -DI4lIzlTJKEWtwMRCEFUYIJqOBY9toMfKoj+x9IKKGXM9rZmGJOz2/Da8e7wSSSw -DNYuutNFYOIXieQ5MHh/KwOjQw4cx+AlqNIVdcngK+/PobCwrrhRPra+WFCo9Ne6 -bWMCggEBAJT6tQ9J5lFbrPKa9MDhK7vYP9VWiKqGImhM/P3dx/EGo5RtChmLSUMM -miQg+r6nK6OQ/RhBEUb0lNFVCc4vTIh9rWkwt8M42MJnyE6orHltcKqPWcY3/Ml1 -FII+ueMLoqdJ97SFi7F06Awaga7T/kmR2btqr6AVF1ZRbaoDkdpfNhJibMCX5Sq1 -/t8s+OiHM5vlu8kag0JWoZ47RZYBY+DrElSckHj5r4RxG1htbvbm/7ZCpwVwU4cy -PF07MBqOAI+arljgedPO3DeKb7yKC/XXrq/OsXjsPo257kONg2jnOdfw8/mU2nPS -bduIWhdShKYyRjkDQQXwrb5KQtHeOqU= ------END PRIVATE KEY-----