diff --git a/common/src/main/java/io/netty/util/internal/EmptyArrays.java b/common/src/main/java/io/netty/util/internal/EmptyArrays.java index d4e46685a6..d31fa02f13 100644 --- a/common/src/main/java/io/netty/util/internal/EmptyArrays.java +++ b/common/src/main/java/io/netty/util/internal/EmptyArrays.java @@ -17,6 +17,7 @@ package io.netty.util.internal; import java.nio.ByteBuffer; +import java.security.cert.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/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java index 4f52b2beef..0bb8d007a6 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java @@ -27,6 +27,8 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import java.net.URI; @@ -59,7 +61,13 @@ public class HttpSnoopClient { return; } + final SslContext sslCtx; boolean ssl = "https".equalsIgnoreCase(scheme); + if (ssl) { + sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); + } else { + sslCtx = null; + } // Configure the client. EventLoopGroup group = new NioEventLoopGroup(); @@ -67,7 +75,7 @@ public class HttpSnoopClient { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) - .handler(new HttpSnoopClientInitializer(ssl)); + .handler(new HttpSnoopClientInitializer(sslCtx)); // Make the connection attempt. Channel ch = b.connect(host, port).sync().channel(); diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java index 9989b12878..7cb7844e15 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java @@ -18,21 +18,18 @@ package io.netty.example.http.snoop; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; -import io.netty.example.securechat.SecureChatSslContextFactory; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import io.netty.handler.ssl.SslHandler; - -import javax.net.ssl.SSLEngine; +import io.netty.handler.ssl.SslContext; public class HttpSnoopClientInitializer extends ChannelInitializer { - private final boolean ssl; + private final SslContext sslCtx; - public HttpSnoopClientInitializer(boolean ssl) { - this.ssl = ssl; + public HttpSnoopClientInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; } @Override @@ -41,13 +38,10 @@ public class HttpSnoopClientInitializer extends ChannelInitializer> headers = formGet(b, host, port, get, uriSimple); diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java index 1e7eaa6b5c..202f7ca2bf 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java @@ -18,19 +18,17 @@ package io.netty.example.http.upload; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; -import io.netty.example.securechat.SecureChatSslContextFactory; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.stream.ChunkedWriteHandler; -import javax.net.ssl.SSLEngine; - public class HttpUploadClientIntializer extends ChannelInitializer { - private final boolean ssl; - public HttpUploadClientIntializer(boolean ssl) { - this.ssl = ssl; + private final SslContext sslCtx; + + public HttpUploadClientIntializer(SslContext sslCtx) { + this.sslCtx = sslCtx; } @Override @@ -38,10 +36,8 @@ public class HttpUploadClientIntializer extends ChannelInitializer 1) { isSSL = true; } - new HttpUploadServer(port).run(); + + // Configure SSL. + SslContext sslCtx; + if (isSSL) { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + } else { + sslCtx = null; + } + new HttpUploadServer(sslCtx, port).run(); } } diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java index 78ebf6e43f..04df45d4ae 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java @@ -18,24 +18,26 @@ package io.netty.example.http.upload; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; -import io.netty.example.securechat.SecureChatSslContextFactory; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; -import io.netty.handler.ssl.SslHandler; - -import javax.net.ssl.SSLEngine; +import io.netty.handler.ssl.SslContext; public class HttpUploadServerInitializer extends ChannelInitializer { + + private final SslContext sslCtx; + + public HttpUploadServerInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + @Override public void initChannel(SocketChannel ch) throws Exception { // Create a default pipeline implementation. ChannelPipeline pipeline = ch.pipeline(); - if (HttpUploadServer.isSSL) { - SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - engine.setUseClientMode(false); - pipeline.addLast("ssl", new SslHandler(engine)); + if (sslCtx != null) { + pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc())); } pipeline.addLast("decoder", new HttpRequestDecoder()); diff --git a/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java b/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java index 04533918c9..226712fdc0 100644 --- a/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java +++ b/example/src/main/java/io/netty/example/portunification/PortUnificationServer.java @@ -21,6 +21,8 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.SelfSignedCertificate; /** * Serves two protocols (HTTP and Factorial) using only one port, enabling @@ -31,9 +33,11 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; */ 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; } @@ -47,7 +51,7 @@ public class PortUnificationServer { .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { - ch.pipeline().addLast(new PortUnificationServerHandler()); + ch.pipeline().addLast(new PortUnificationServerHandler(sslCtx)); } }); @@ -66,6 +70,11 @@ public class PortUnificationServer { } else { port = 8080; } - new PortUnificationServer(port).run(); + + // Configure SSL. + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + + new PortUnificationServer(sslCtx, port).run(); } } diff --git a/example/src/main/java/io/netty/example/portunification/PortUnificationServerHandler.java b/example/src/main/java/io/netty/example/portunification/PortUnificationServerHandler.java index d3f4b2ede7..dae32b0356 100644 --- a/example/src/main/java/io/netty/example/portunification/PortUnificationServerHandler.java +++ b/example/src/main/java/io/netty/example/portunification/PortUnificationServerHandler.java @@ -22,16 +22,15 @@ import io.netty.example.factorial.BigIntegerDecoder; import io.netty.example.factorial.FactorialServerHandler; import io.netty.example.factorial.NumberEncoder; import io.netty.example.http.snoop.HttpSnoopServerHandler; -import io.netty.example.securechat.SecureChatSslContextFactory; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.compression.ZlibWrapper; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; -import javax.net.ssl.SSLEngine; import java.util.List; /** @@ -40,14 +39,16 @@ import java.util.List; */ public class PortUnificationServerHandler extends ByteToMessageDecoder { + 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; } @@ -111,13 +112,8 @@ public class PortUnificationServerHandler extends ByteToMessageDecoder { private void enableSsl(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); - - 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(ctx.alloc())); + p.addLast("unificationA", new PortUnificationServerHandler(sslCtx, false, detectGzip)); p.remove(this); } @@ -125,7 +121,7 @@ public class PortUnificationServerHandler extends ByteToMessageDecoder { ChannelPipeline p = ctx.pipeline(); p.addLast("gzipdeflater", ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP)); p.addLast("gzipinflater", ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)); - p.addLast("unificationB", new PortUnificationServerHandler(detectSsl, false)); + p.addLast("unificationB", new PortUnificationServerHandler(sslCtx, detectSsl, false)); p.remove(this); } diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java index 66230bd7c2..4a0b5bef66 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatClient.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatClient.java @@ -22,6 +22,8 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.example.telnet.TelnetClient; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -31,10 +33,12 @@ import java.io.InputStreamReader; */ 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; } @@ -45,7 +49,7 @@ public class SecureChatClient { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) - .handler(new SecureChatClientInitializer()); + .handler(new SecureChatClientInitializer(sslCtx)); // Start the connection attempt. Channel ch = b.connect(host, port).sync().channel(); @@ -93,6 +97,8 @@ public class SecureChatClient { String host = args[0]; int port = Integer.parseInt(args[1]); - new SecureChatClient(host, port).run(); + // Configure SSL. + SslContext sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); + new SecureChatClient(sslCtx, host, port).run(); } } diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatClientInitializer.java b/example/src/main/java/io/netty/example/securechat/SecureChatClientInitializer.java index 413d659c1a..cbd42e9756 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatClientInitializer.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatClientInitializer.java @@ -22,15 +22,19 @@ import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; -import io.netty.handler.ssl.SslHandler; - -import javax.net.ssl.SSLEngine; +import io.netty.handler.ssl.SslContext; /** * Creates a newly configured {@link ChannelPipeline} for a new channel. */ public class SecureChatClientInitializer extends ChannelInitializer { + private final SslContext sslCtx; + + public SecureChatClientInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); @@ -40,12 +44,7 @@ public class SecureChatClientInitializer extends ChannelInitializer - * 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/example/src/main/java/io/netty/example/securechat/SecureChatServer.java b/example/src/main/java/io/netty/example/securechat/SecureChatServer.java index 14de0d4827..f1bb26d651 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatServer.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatServer.java @@ -20,15 +20,19 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.example.telnet.TelnetServer; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.SelfSignedCertificate; /** * 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; } @@ -39,7 +43,7 @@ public class SecureChatServer { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) - .childHandler(new SecureChatServerInitializer()); + .childHandler(new SecureChatServerInitializer(sslCtx)); b.bind(port).sync().channel().closeFuture().sync(); } finally { @@ -55,6 +59,11 @@ public class SecureChatServer { } else { port = 8443; } - new SecureChatServer(port).run(); + + // Configure SSL context. + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + + new SecureChatServer(sslCtx, port).run(); } } diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatServerInitializer.java b/example/src/main/java/io/netty/example/securechat/SecureChatServerInitializer.java index f089a4f4f8..98f762aa1f 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatServerInitializer.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatServerInitializer.java @@ -22,15 +22,19 @@ import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; -import io.netty.handler.ssl.SslHandler; - -import javax.net.ssl.SSLEngine; +import io.netty.handler.ssl.SslContext; /** * Creates a newly configured {@link ChannelPipeline} for a new channel. */ public class SecureChatServerInitializer extends ChannelInitializer { + private final SslContext sslCtx; + + public SecureChatServerInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); @@ -43,12 +47,7 @@ public class SecureChatServerInitializer extends ChannelInitializer - * 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 = SystemPropertyUtil.get("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/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java b/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java deleted file mode 100644 index 28dafea70f..0000000000 --- a/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java +++ /dev/null @@ -1,77 +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 io.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() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - @Override - 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()); - } - - @Override - 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/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java index e334cd4673..f5fa16f513 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java @@ -27,7 +27,11 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import javax.net.ssl.SSLException; import java.net.InetSocketAddress; import java.util.concurrent.BlockingQueue; @@ -48,13 +52,15 @@ import static java.util.concurrent.TimeUnit.*; */ public class SpdyClient { + private final SslContext sslCtx; private final String host; private final int port; private final HttpResponseClientHandler httpResponseHandler; private Channel channel; private EventLoopGroup workerGroup; - public SpdyClient(String host, int port) { + public SpdyClient(String host, int port) throws SSLException { + sslCtx = SslContext.newClientContext(SslProvider.JDK, InsecureTrustManagerFactory.INSTANCE); this.host = host; this.port = port; httpResponseHandler = new HttpResponseClientHandler(); @@ -73,7 +79,7 @@ public class SpdyClient { b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); b.remoteAddress(new InetSocketAddress(host, port)); - b.handler(new SpdyClientInitializer(httpResponseHandler)); + b.handler(new SpdyClientInitializer(sslCtx, httpResponseHandler)); // Start the client. channel = b.connect().syncUninterruptibly().channel(); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java index ffc6d75fd5..63400c472d 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java @@ -18,11 +18,11 @@ package io.netty.example.spdy.client; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; -import io.netty.example.securechat.SecureChatSslContextFactory; import io.netty.handler.codec.spdy.SpdyFrameCodec; import io.netty.handler.codec.spdy.SpdyHttpDecoder; import io.netty.handler.codec.spdy.SpdyHttpEncoder; import io.netty.handler.codec.spdy.SpdySessionHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import org.eclipse.jetty.npn.NextProtoNego; @@ -33,9 +33,11 @@ import static io.netty.util.internal.logging.InternalLogLevel.*; public class SpdyClientInitializer extends ChannelInitializer { + private final SslContext sslCtx; private final HttpResponseClientHandler httpResponseHandler; - public SpdyClientInitializer(HttpResponseClientHandler httpResponseHandler) { + public SpdyClientInitializer(SslContext sslCtx, HttpResponseClientHandler httpResponseHandler) { + this.sslCtx = sslCtx; this.httpResponseHandler = httpResponseHandler; } @@ -43,14 +45,14 @@ public class SpdyClientInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { - SSLEngine engine = SecureChatSslContextFactory.getClientContext().createSSLEngine(); - engine.setUseClientMode(true); + SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); + SSLEngine engine = sslHandler.engine(); NextProtoNego.put(engine, new SpdyClientProvider()); NextProtoNego.debug = true; ChannelPipeline pipeline = ch.pipeline(); - pipeline.addLast("ssl", new SslHandler(engine)); + pipeline.addLast("ssl", sslHandler); pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(SPDY_3_1)); pipeline.addLast("spdyFrameLogger", new SpdyFrameLogger(INFO)); pipeline.addLast("spdySessionHandler", new SpdySessionHandler(SPDY_3_1, false)); diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java index 8e6c177ad7..a384e3da54 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java @@ -21,6 +21,8 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.SelfSignedCertificate; /** * A SPDY Server that responds to a GET request with a Hello World. @@ -42,9 +44,11 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; */ public class SpdyServer { + private final SslContext sslCtx; private final int port; - public SpdyServer(int port) { + public SpdyServer(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -56,7 +60,7 @@ public class SpdyServer { ServerBootstrap b = new ServerBootstrap(); b.option(ChannelOption.SO_BACKLOG, 1024); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) - .childHandler(new SpdyServerInitializer()); + .childHandler(new SpdyServerInitializer(sslCtx)); Channel ch = b.bind(port).sync().channel(); ch.closeFuture().sync(); @@ -79,7 +83,10 @@ public class SpdyServer { System.out.println("Open your SPDY enabled browser and navigate to https://localhost:" + port + '/'); System.out.println("If using Chrome browser, check your SPDY sessions at chrome://net-internals/#spdy"); - new SpdyServer(port).run(); + // Configure SSL. + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + new SpdyServer(sslCtx, port).run(); } private static void checkForNpnSupport() { diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java index 33b0dbed01..9c5ee4ccdb 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java @@ -18,7 +18,7 @@ package io.netty.example.spdy.server; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; -import io.netty.example.securechat.SecureChatSslContextFactory; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import org.eclipse.jetty.npn.NextProtoNego; @@ -28,12 +28,19 @@ import javax.net.ssl.SSLEngine; * Sets up the Netty pipeline */ public class SpdyServerInitializer extends ChannelInitializer { + + private final SslContext sslCtx; + + public SpdyServerInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); - SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); - engine.setUseClientMode(false); + SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); + SSLEngine engine = sslHandler.engine(); p.addLast("ssl", new SslHandler(engine)); // Setup NextProtoNego with our server provider diff --git a/handler/pom.xml b/handler/pom.xml index e385a47133..bfb3125b81 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -44,6 +44,12 @@ netty-codec ${project.version} + + ${project.groupId} + netty-tcnative + ${os.detected.classifier} + true + org.bouncycastle bcpkix-jdk15on diff --git a/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolSelector.java b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolSelector.java new file mode 100644 index 0000000000..4a69861db7 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolSelector.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import java.util.List; + +/** + * Selects an application layer protocol in TLS NPN + * (Next Protocol Negotiation) or ALPN + * (Application Layer Protocol Negotiation). + */ +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/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java new file mode 100644 index 0000000000..bd4647cb62 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java @@ -0,0 +1,174 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.TrustManager; +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; + +/** + * A client-side {@link SslContext} which uses JDK's SSL/TLS implementation. + */ +public final class JdkSslClientContext extends JdkSslContext { + + private final SSLContext ctx; + + /** + * Creates a new instance. + */ + public JdkSslClientContext() throws SSLException { + this(null, null, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + */ + public JdkSslClientContext(File certChainFile) throws SSLException { + this(certChainFile, null); + } + + /** + * Creates a new instance. + * + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + */ + public JdkSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { + this(null, trustManagerFactory); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + */ + public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + this(certChainFile, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocolSelector the {@link ApplicationProtocolSelector} that chooses one of the application layer + * protocols returned by a TLS server. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + */ + public JdkSslClientContext( + File certChainFile, TrustManagerFactory trustManagerFactory, + Iterable ciphers, ApplicationProtocolSelector nextProtocolSelector, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + super(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 (ByteBuf buf: PemReader.readCertificates(certChainFile)) { + X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(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); + } + + SSLSessionContext sessCtx = ctx.getClientSessionContext(); + 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(); + } + + @Override + public SSLContext context() { + return ctx; + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java new file mode 100644 index 0000000000..4e5825cb9b --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java @@ -0,0 +1,178 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.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; + +/** + * An {@link SslContext} which uses JDK's SSL/TLS implementation. + */ +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): {} ", Arrays.asList(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(Iterable ciphers) { + cipherSuites = toCipherSuiteArray(ciphers); + unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites)); + } + + /** + * Returns the JDK {@link SSLContext} object held by this context. + */ + public abstract SSLContext context(); + + /** + * Returns the JDK {@link SSLSessionContext} object held by this 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(ByteBufAllocator alloc) { + SSLEngine engine = context().createSSLEngine(); + engine.setEnabledCipherSuites(cipherSuites); + engine.setEnabledProtocols(PROTOCOLS); + engine.setUseClientMode(isClient()); + return engine; + } + + @Override + public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { + SSLEngine engine = context().createSSLEngine(peerHost, peerPort); + 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/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java new file mode 100644 index 0000000000..236d334374 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java @@ -0,0 +1,176 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +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; + +/** + * A server-side {@link SslContext} which uses JDK's SSL/TLS implementation. + */ +public final class JdkSslServerContext extends JdkSslContext { + + private final SSLContext ctx; + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + */ + public JdkSslServerContext(File certChainFile, File keyFile) throws SSLException { + this(certChainFile, keyFile, null); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + */ + public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { + this(certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocols the application layer protocols to accept, in the order of preference. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + */ + public JdkSslServerContext( + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + super(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"); + + ByteBuf 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 (ByteBuf buf: PemReader.readCertificates(certChainFile)) { + certChain.add(cf.generateCertificate(new ByteBufInputStream(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); + + SSLSessionContext 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(); + } + + @Override + public SSLContext context() { + return ctx; + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java new file mode 100644 index 0000000000..206c27a38f --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.util.internal.NativeLibraryLoader; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.Library; +import org.apache.tomcat.jni.SSL; + +/** + * Tells if {@code netty-tcnative} and its OpenSSL support + * are available. + */ +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 { + NativeLibraryLoader.load("netty-tcnative", SSL.class.getClassLoader()); + Library.initialize("provided"); + SSL.initialize(null); + } catch (Throwable t) { + cause = t; + logger.debug( + "Failed to load netty-tcnative; " + + OpenSslEngine.class.getSimpleName() + " will be unavailable.", t); + } + UNAVAILABILITY_CAUSE = cause; + } + + /** + * Returns {@code true} if and only if + * {@code netty-tcnative} and its OpenSSL support + * are available. + */ + public static boolean isAvailable() { + return UNAVAILABILITY_CAUSE == null; + } + + /** + * Ensure that {@code netty-tcnative} and + * its OpenSSL support are available. + * + * @throws UnsatisfiedLinkError if unavailable + */ + public static void ensureAvailability() { + if (UNAVAILABILITY_CAUSE != null) { + throw (Error) new UnsatisfiedLinkError( + "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); + } + } + + /** + * Returns the cause of unavailability of + * {@code netty-tcnative} and its OpenSSL support. + * + * @return the cause if unavailable. {@code null} if available. + */ + public static Throwable unavailabilityCause() { + return UNAVAILABILITY_CAUSE; + } + + private OpenSsl() { } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java new file mode 100644 index 0000000000..c0b1e400e2 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -0,0 +1,867 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.Buffer; +import org.apache.tomcat.jni.SSL; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.X509Certificate; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; +import static javax.net.ssl.SSLEngineResult.Status.*; + +/** + * Implements a {@link SSLEngine} using + * OpenSSL BIO abstractions. + */ +public final class OpenSslEngine extends SSLEngine { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class); + + private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0]; + private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; + + private static final SSLException ENGINE_CLOSED = new SSLException("engine closed"); + private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported"); + private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized"); + + static { + ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + ENCRYPTED_PACKET_OVERSIZED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + } + + private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14 + private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024; + private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024; + + // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256) + static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256; + + private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); + + // OpenSSL state + private long ssl; + private long networkBIO; + + /** + * 0 - not accepted, 1 - accepted implicitly via wrap()/unwrap(), 2 - accepted explicitly via beginHandshake() call + */ + private int accepted; + private boolean handshakeFinished; + private boolean receivedShutdown; + @SuppressWarnings("UnusedDeclaration") + private volatile int destroyed; + + private String cipher; + private String protocol; + + // SSL Engine status variables + private boolean isInboundDone; + private boolean isOutboundDone; + private boolean engineClosed; + + private int lastPrimingReadResult; + + private final ByteBufAllocator alloc; + private SSLSession session; + + /** + * Creates a new instance + * + * @param sslCtx an OpenSSL {@code SSL_CTX} object + * @param alloc the {@link ByteBufAllocator} that will be used by this engine + */ + public OpenSslEngine(long sslCtx, ByteBufAllocator alloc) { + OpenSsl.ensureAvailability(); + if (sslCtx == 0) { + throw new NullPointerException("sslContext"); + } + if (alloc == null) { + throw new NullPointerException("alloc"); + } + + this.alloc = alloc; + ssl = SSL.newSSL(sslCtx, true); + networkBIO = SSL.makeNetworkBIO(ssl); + } + + /** + * Destroys this engine. + */ + public synchronized void shutdown() { + if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) { + SSL.freeSSL(ssl); + SSL.freeBIO(networkBIO); + ssl = networkBIO = 0; + + // internal errors can cause shutdown without marking the engine closed + isInboundDone = isOutboundDone = engineClosed = true; + } + } + + /** + * Write plaintext data to the OpenSSL internal BIO + * + * Calling this function with src.remaining == 0 is undefined. + */ + private int writePlaintextData(final ByteBuffer src) { + final int pos = src.position(); + final int limit = src.limit(); + final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH); + final int sslWrote; + + if (src.isDirect()) { + final long addr = Buffer.address(src) + pos; + sslWrote = SSL.writeToSSL(ssl, addr, len); + if (sslWrote > 0) { + src.position(pos + sslWrote); + return sslWrote; + } + } else { + ByteBuf buf = alloc.directBuffer(len); + try { + final long addr; + if (buf.hasMemoryAddress()) { + addr = buf.memoryAddress(); + } else { + addr = Buffer.address(buf.nioBuffer()); + } + + src.limit(pos + len); + + buf.setBytes(0, src); + src.limit(limit); + + sslWrote = SSL.writeToSSL(ssl, addr, len); + if (sslWrote > 0) { + src.position(pos + sslWrote); + return sslWrote; + } else { + src.position(pos); + } + } finally { + buf.release(); + } + } + + 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 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(pos + netWrote); + lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read + return netWrote; + } + } else { + final ByteBuf buf = alloc.directBuffer(len); + try { + final long addr; + if (buf.hasMemoryAddress()) { + addr = buf.memoryAddress(); + } else { + addr = Buffer.address(buf.nioBuffer()); + } + + buf.setBytes(0, src); + + final int netWrote = SSL.writeToBIO(networkBIO, addr, len); + if (netWrote >= 0) { + src.position(pos + netWrote); + lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read + return netWrote; + } else { + src.position(pos); + } + } finally { + buf.release(); + } + } + + return 0; + } + + /** + * Read plaintext data from the OpenSSL internal BIO + */ + private int readPlaintextData(final ByteBuffer dst) { + 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) { + dst.position(pos + sslRead); + return sslRead; + } + } else { + final int pos = dst.position(); + final int limit = dst.limit(); + final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos); + final ByteBuf buf = alloc.directBuffer(len); + try { + final long addr; + if (buf.hasMemoryAddress()) { + addr = buf.memoryAddress(); + } else { + addr = Buffer.address(buf.nioBuffer()); + } + + final int sslRead = SSL.readFromSSL(ssl, addr, len); + if (sslRead > 0) { + dst.limit(pos + sslRead); + buf.getBytes(0, dst); + dst.limit(limit); + return sslRead; + } + } finally { + buf.release(); + } + } + + return 0; + } + + /** + * Read encrypted data from the OpenSSL network BIO + */ + private int readEncryptedData(final ByteBuffer dst, final int pending) { + 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) { + dst.position(pos + bioRead); + return bioRead; + } + } else { + final ByteBuf buf = alloc.directBuffer(pending); + try { + final long addr; + if (buf.hasMemoryAddress()) { + addr = buf.memoryAddress(); + } else { + addr = Buffer.address(buf.nioBuffer()); + } + + final int bioRead = SSL.readFromBIO(networkBIO, addr, pending); + if (bioRead > 0) { + int oldLimit = dst.limit(); + dst.limit(dst.position() + bioRead); + buf.getBytes(0, dst); + dst.limit(oldLimit); + return bioRead; + } + } finally { + buf.release(); + } + } + + return 0; + } + + @Override + public synchronized SSLEngineResult wrap( + final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException { + + // Check to make sure the engine has not been closed + if (destroyed != 0) { + return new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); + } + + // Throw required runtime exceptions + if (srcs == null) { + throw new NullPointerException("srcs"); + } + if (dst == null) { + throw new NullPointerException("dst"); + } + + if (offset >= srcs.length || offset + length > srcs.length) { + throw new IndexOutOfBoundsException( + "offset: " + offset + ", length: " + length + + " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); + } + + if (dst.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + + // Prepare OpenSSL to work in server mode and receive handshake + if (accepted == 0) { + beginHandshakeImplicitly(); + } + + // In handshake or close_notify stages, check if call to wrap was made + // without regard to the handshake status. + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); + if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) { + return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0); + } + + int bytesProduced = 0; + int pendingNet; + + // Check for pending data in the network BIO + pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO); + if (pendingNet > 0) { + // Do we have enough room in dst to write encrypted data? + int capacity = dst.remaining(); + if (capacity < pendingNet) { + return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus, 0, bytesProduced); + } + + // Write the pending data from the network BIO into the dst buffer + try { + bytesProduced += readEncryptedData(dst, pendingNet); + } catch (Exception e) { + throw new SSLException(e); + } + + // If isOuboundDone is set, then the data from the network BIO + // was the close_notify message -- we are not required to wait + // for the receipt the peer's close_notify message -- shutdown. + if (isOutboundDone) { + shutdown(); + } + + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced); + } + + // There was no pending data in the network BIO -- encrypt any application data + int bytesConsumed = 0; + for (int i = offset; i < length; ++ i) { + final ByteBuffer src = srcs[i]; + while (src.hasRemaining()) { + + // Write plaintext application data to the SSL engine + try { + bytesConsumed += writePlaintextData(src); + } catch (Exception e) { + throw new SSLException(e); + } + + // Check to see if the engine wrote data into the network BIO + pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO); + if (pendingNet > 0) { + // Do we have enough room in dst to write encrypted data? + int capacity = dst.remaining(); + if (capacity < pendingNet) { + return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); + } + + // Write the pending data from the network BIO into the dst buffer + try { + bytesProduced += readEncryptedData(dst, pendingNet); + } catch (Exception e) { + throw new SSLException(e); + } + + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + } + } + } + + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + } + + @Override + public synchronized SSLEngineResult unwrap( + final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { + + // Check to make sure the engine has not been closed + if (destroyed != 0) { + return new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); + } + + // Throw requried runtime exceptions + if (src == null) { + throw new NullPointerException("src"); + } + if (dsts == null) { + throw new NullPointerException("dsts"); + } + if (offset >= dsts.length || offset + length > dsts.length) { + throw new IndexOutOfBoundsException( + "offset: " + offset + ", length: " + length + + " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))"); + } + + int capacity = 0; + final int endOffset = offset + length; + for (int i = offset; i < endOffset; i ++) { + ByteBuffer dst = dsts[i]; + if (dst == null) { + throw new IllegalArgumentException(); + } + if (dst.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + capacity += dst.remaining(); + } + + // Prepare OpenSSL to work in server mode and receive handshake + if (accepted == 0) { + beginHandshakeImplicitly(); + } + + // In handshake or close_notify stages, check if call to unwrap was made + // without regard to the handshake status. + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); + if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) { + return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0); + } + + // protect against protocol overflow attack vector + if (src.remaining() > MAX_ENCRYPTED_PACKET_LENGTH) { + isInboundDone = true; + isOutboundDone = true; + engineClosed = true; + shutdown(); + throw ENCRYPTED_PACKET_OVERSIZED; + } + + // Write encrypted data to network BIO + int bytesConsumed = 0; + lastPrimingReadResult = 0; + try { + bytesConsumed += writeEncryptedData(src); + } catch (Exception e) { + throw new SSLException(e); + } + + // Check for OpenSSL errors caused by the priming read + String error = SSL.getLastError(); + if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { + if (logger.isInfoEnabled()) { + logger.info( + "SSL_read failed: primingReadResult: " + lastPrimingReadResult + + "; OpenSSL error: '" + error + '\''); + } + + // There was an internal error -- shutdown + shutdown(); + throw new SSLException(error); + } + + // There won't be any application data until we're done handshaking + int pendingApp = SSL.isInInit(ssl) == 0 ? SSL.pendingReadableBytesInSSL(ssl) : 0; + + // Do we have enough room in dsts to write decrypted data? + if (capacity < pendingApp) { + return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0); + } + + // Write decrypted data to dsts buffers + int bytesProduced = 0; + int idx = offset; + while (idx < endOffset) { + ByteBuffer dst = dsts[idx]; + if (!dst.hasRemaining()) { + idx ++; + continue; + } + + if (pendingApp <= 0) { + break; + } + + int bytesRead; + try { + bytesRead = readPlaintextData(dst); + } catch (Exception e) { + throw new SSLException(e); + } + + if (bytesRead == 0) { + break; + } + + bytesProduced += bytesRead; + pendingApp -= bytesRead; + + if (!dst.hasRemaining()) { + idx ++; + } + } + + // Check to see if we received a close_notify message from the peer + if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) { + receivedShutdown = true; + closeOutbound(); + closeInbound(); + } + + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced); + } + + @Override + public Runnable getDelegatedTask() { + // Currently, we do not delegate SSL computation tasks + // TODO: in the future, possibly create tasks to do encrypt / decrypt async + + return null; + } + + @Override + public synchronized void closeInbound() throws SSLException { + if (isInboundDone) { + return; + } + + isInboundDone = true; + engineClosed = true; + + if (accepted != 0) { + if (!receivedShutdown) { + shutdown(); + throw new SSLException( + "Inbound closed before receiving peer's close_notify: possible truncation attack?"); + } + } else { + // engine closing before initial handshake + shutdown(); + } + } + + @Override + public synchronized boolean isInboundDone() { + return isInboundDone || engineClosed; + } + + @Override + public synchronized void closeOutbound() { + if (isOutboundDone) { + return; + } + + isOutboundDone = true; + engineClosed = true; + + if (accepted != 0 && destroyed == 0) { + int mode = SSL.getShutdown(ssl); + if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) { + SSL.shutdownSSL(ssl); + } + } else { + // engine closing before initial handshake + shutdown(); + } + } + + @Override + public synchronized boolean isOutboundDone() { + return isOutboundDone; + } + + @Override + public String[] getSupportedCipherSuites() { + return EmptyArrays.EMPTY_STRINGS; + } + + @Override + public String[] getEnabledCipherSuites() { + return EmptyArrays.EMPTY_STRINGS; + } + + @Override + public void setEnabledCipherSuites(String[] strings) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getSupportedProtocols() { + return EmptyArrays.EMPTY_STRINGS; + } + + @Override + public String[] getEnabledProtocols() { + return EmptyArrays.EMPTY_STRINGS; + } + + @Override + public void setEnabledProtocols(String[] strings) { + throw new UnsupportedOperationException(); + } + + @Override + public SSLSession getSession() { + SSLSession session = this.session; + if (session == null) { + this.session = session = new SSLSession() { + @Override + public byte[] getId() { + return String.valueOf(ssl).getBytes(); + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() { + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void putValue(String s, Object o) { + } + + @Override + public Object getValue(String s) { + return null; + } + + @Override + public void removeValue(String s) { + } + + @Override + public String[] getValueNames() { + return EmptyArrays.EMPTY_STRINGS; + } + + @Override + public Certificate[] getPeerCertificates() { + return EMPTY_CERTIFICATES; + } + + @Override + public Certificate[] getLocalCertificates() { + return EMPTY_CERTIFICATES; + } + + @Override + public X509Certificate[] getPeerCertificateChain() { + return EMPTY_X509_CERTIFICATES; + } + + @Override + public Principal getPeerPrincipal() { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return cipher; + } + + @Override + public String getProtocol() { + return protocol; + } + + @Override + public String getPeerHost() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return MAX_ENCRYPTED_PACKET_LENGTH; + } + + @Override + public int getApplicationBufferSize() { + return MAX_PLAINTEXT_LENGTH; + } + }; + } + + return session; + } + + @Override + public synchronized void beginHandshake() throws SSLException { + if (engineClosed) { + throw ENGINE_CLOSED; + } + + switch (accepted) { + case 0: + SSL.doHandshake(ssl); + accepted = 2; + break; + case 1: + // A user did not start handshake by calling this method by him/herself, + // but handshake has been started already by wrap() or unwrap() implicitly. + // Because it's the user's first time to call this method, it is unfair to + // raise an exception. From the user's standpoint, he or she never asked + // for renegotiation. + + accepted = 2; // Next time this method is invoked by the user, we should raise an exception. + break; + case 2: + throw RENEGOTIATION_UNSUPPORTED; + default: + throw new Error(); + } + } + + private synchronized void beginHandshakeImplicitly() throws SSLException { + if (engineClosed) { + throw ENGINE_CLOSED; + } + + if (accepted == 0) { + SSL.doHandshake(ssl); + accepted = 1; + } + } + + private SSLEngineResult.Status getEngineStatus() { + return engineClosed? CLOSED : OK; + } + + @Override + public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { + if (accepted == 0 || destroyed != 0) { + return NOT_HANDSHAKING; + } + + // Check if we are in the initial handshake phase + if (!handshakeFinished) { + // There is pending data in the network BIO -- call wrap + if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) { + return NEED_WRAP; + } + + // No pending data to be sent to the peer + // Check to see if we have finished handshaking + if (SSL.isInInit(ssl) == 0) { + handshakeFinished = true; + cipher = SSL.getCipherForSSL(ssl); + protocol = SSL.getNextProtoNegotiated(ssl); + return FINISHED; + } + + // No pending data and still handshaking + // Must be waiting on the peer to send more data + return NEED_UNWRAP; + } + + // Check if we are in the shutdown phase + if (engineClosed) { + // Waiting to send the close_notify message + if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) { + return NEED_WRAP; + } + + // Must be waiting to receive the close_notify message + return NEED_UNWRAP; + } + + return NOT_HANDSHAKING; + } + + @Override + public void setUseClientMode(boolean clientMode) { + if (clientMode) { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean b) { + if (b) { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean b) { + if (b) { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean b) { + if (b) { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java new file mode 100644 index 0000000000..04142440b6 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -0,0 +1,349 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.apache.tomcat.jni.Pool; +import org.apache.tomcat.jni.SSL; +import org.apache.tomcat.jni.SSLContext; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. + */ +public final class OpenSslServerContext extends SslContext { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class); + private static final List DEFAULT_CIPHERS; + + static { + List ciphers = new ArrayList(); + // XXX: Make sure to sync this list with JdkSslEngineFactory. + Collections.addAll( + ciphers, + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-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 List ciphers = new ArrayList(); + private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers); + private final long sessionCacheSize; + private final long sessionTimeout; + 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; + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + */ + public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException { + this(certChainFile, keyFile, null); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + */ + public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { + this(certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + /** + * Creates a new instance. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocols the application layer protocols to accept, in the order of preference. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + */ + public OpenSslServerContext( + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + + OpenSsl.ensureAvailability(); + + if (certChainFile == null) { + throw new NullPointerException("certChainFile"); + } + if (!certChainFile.isFile()) { + throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile); + } + if (keyFile == null) { + throw new NullPointerException("keyPath"); + } + if (!keyFile.isFile()) { + throw new IllegalArgumentException("keyPath is not a file: " + keyFile); + } + if (ciphers == null) { + ciphers = DEFAULT_CIPHERS; + } + + if (keyPassword == null) { + keyPassword = ""; + } + if (nextProtocols == null) { + nextProtocols = Collections.emptyList(); + } + + for (String c: ciphers) { + if (c == null) { + break; + } + this.ciphers.add(c); + } + + for (String p: nextProtocols) { + if (p == null) { + break; + } + this.nextProtocols.add(p); + } + + // Allocate a new APR pool. + aprPool = Pool.create(0); + + // Create a new SSL_CTX and configure it. + boolean success = false; + try { + synchronized (OpenSslServerContext.class) { + try { + ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + } catch (Exception e) { + throw new SSLException("failed to create an SSL_CTX", e); + } + + SSLContext.setOptions(ctx, SSL.SSL_OP_ALL); + SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2); + SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); + SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE); + SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE); + SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + /* List the ciphers that the client is permitted to negotiate. */ + try { + // Convert the cipher list into a colon-separated string. + StringBuilder cipherBuf = new StringBuilder(); + for (String c: this.ciphers) { + cipherBuf.append(c); + cipherBuf.append(':'); + } + cipherBuf.setLength(cipherBuf.length() - 1); + + SSLContext.setCipherSuite(ctx, cipherBuf.toString()); + } catch (SSLException e) { + throw e; + } catch (Exception e) { + throw new SSLException("failed to set cipher suite: " + this.ciphers, e); + } + + /* Set certificate verification policy. */ + SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10); + + /* Load the certificate file and private key. */ + try { + if (!SSLContext.setCertificate( + 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: " + certChainFile + " and " + keyFile, e); + } + + /* Load the certificate chain. We must skip the first cert since it was loaded above. */ + if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) { + String error = SSL.getLastError(); + if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) { + throw new SSLException( + "failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')'); + } + } + + /* Set next protocols for next protocol negotiation extension, if specified */ + 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 */ + if (sessionCacheSize > 0) { + this.sessionCacheSize = sessionCacheSize; + SSLContext.setSessionCacheSize(ctx, sessionCacheSize); + } else { + // Get the default session cache size using SSLContext.setSessionCacheSize() + this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480); + // Revert the session cache size to the default value. + SSLContext.setSessionCacheSize(ctx, sessionCacheSize); + } + + /* Set session timeout, if specified */ + if (sessionTimeout > 0) { + this.sessionTimeout = sessionTimeout; + SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); + } else { + // Get the default session timeout using SSLContext.setSessionCacheTimeout() + this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300); + // Revert the session timeout to the default value. + SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); + } + } + success = true; + } finally { + if (!success) { + destroyPools(); + } + } + + stats = new OpenSslSessionStats(ctx); + } + + @Override + public boolean isClient() { + return false; + } + + @Override + public List cipherSuites() { + return unmodifiableCiphers; + } + + @Override + public long sessionCacheSize() { + return sessionCacheSize; + } + + @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 context. + */ + public long context() { + return ctx; + } + + /** + * Returns the stats of this context. + */ + public OpenSslSessionStats stats() { + return stats; + } + + /** + * Returns a new server-side {@link javax.net.ssl.SSLEngine} with the current configuration. + */ + @Override + public SSLEngine newEngine(ByteBufAllocator alloc) { + return new OpenSslEngine(ctx, alloc); + } + + @Override + public SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the SSL session ticket keys of this context. + */ + public void setTicketKeys(byte[] keys) { + if (keys != null) { + throw new NullPointerException("keys"); + } + SSLContext.setSessionTicketKeys(ctx, keys); + } + + @Override + @SuppressWarnings("FinalizeDeclaration") + protected void finalize() throws Throwable { + super.finalize(); + synchronized (OpenSslServerContext.class) { + if (ctx != 0) { + SSLContext.free(ctx); + } + } + + destroyPools(); + } + + private void destroyPools() { + if (aprPool != 0) { + Pool.destroy(aprPool); + } + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java new file mode 100644 index 0000000000..2ec514681d --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import org.apache.tomcat.jni.SSLContext; + +/** + * Stats exposed by an OpenSSL session context. + * + * @see SSL_CTX_sess_number + */ +public final class OpenSslSessionStats { + + private final long context; + + OpenSslSessionStats(long context) { + this.context = context; + } + + /** + * Returns the current number of sessions in the internal session cache. + */ + public long number() { + return SSLContext.sessionNumber(context); + } + + /** + * Returns the number of started SSL/TLS handshakes in client mode. + */ + public long connect() { + return SSLContext.sessionConnect(context); + } + + /** + * Returns the number of successfully established SSL/TLS sessions in client mode. + */ + public long connectGood() { + return SSLContext.sessionConnectGood(context); + } + + /** + * Returns the number of start renegotiations in client mode. + */ + public long connectRenegotiate() { + return SSLContext.sessionConnectRenegotiate(context); + } + + /** + * Returns the number of started SSL/TLS handshakes in server mode. + */ + public long accept() { + return SSLContext.sessionAccept(context); + } + + /** + * Returns the number of successfully established SSL/TLS sessions in server mode. + */ + public long acceptGood() { + return SSLContext.sessionAcceptGood(context); + } + + /** + * Returns the number of start renegotiations in server mode. + */ + public long acceptRenegotiate() { + return SSLContext.sessionAcceptRenegotiate(context); + } + + /** + * Returns the number of successfully reused sessions. In client mode, a session set with {@code SSL_set_session} + * successfully reused is counted as a hit. In server mode, a session successfully retrieved from internal or + * external cache is counted as a hit. + */ + public long hits() { + return SSLContext.sessionHits(context); + } + + /** + * Returns the number of successfully retrieved sessions from the external session cache in server mode. + */ + public long cbHits() { + return SSLContext.sessionCbHits(context); + } + + /** + * Returns the number of sessions proposed by clients that were not found in the internal session cache + * in server mode. + */ + public long misses() { + return SSLContext.sessionMisses(context); + } + + /** + * Returns the number of sessions proposed by clients and either found in the internal or external session cache + * in server mode, but that were invalid due to timeout. These sessions are not included in the {@link #hits()} + * count. + */ + public long timeouts() { + return SSLContext.sessionTimeouts(context); + } + + /** + * Returns the number of sessions that were removed because the maximum session cache size was exceeded. + */ + public long cacheFull() { + return SSLContext.sessionCacheFull(context); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/PemReader.java b/handler/src/main/java/io/netty/handler/ssl/PemReader.java new file mode 100644 index 0000000000..491359fe22 --- /dev/null +++ b/handler/src/main/java/io/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 io.netty.handler.ssl; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +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 ByteBuf[] 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(Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII))); + start = m.end(); + } + + if (certs.isEmpty()) { + throw new CertificateException("found no certificates: " + file); + } + + return certs.toArray(new ByteBuf[certs.size()]); + } + + static ByteBuf 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(Unpooled.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/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java new file mode 100644 index 0000000000..8da2a14757 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -0,0 +1,463 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.util.List; + +/** + * A secure socket protocol implementation which acts as a factory for {@link SSLEngine} and {@link SslHandler}. + * Internally, it is implemented via JDK's {@link SSLContext} or OpenSSL's {@code SSL_CTX}. + * + *

Making your server support SSL/TLS

+ *
+ * // In your {@link ChannelInitializer}:
+ * {@link ChannelPipeline} p = channel.pipeline();
+ * {@link SslContext} sslCtx = {@link #newServerContext(File, File) SslContext.newServerContext(...)};
+ * p.addLast("ssl", {@link #newEngine(ByteBufAllocator) sslCtx.newEngine(channel.alloc())});
+ * ...
+ * 
+ * + *

Making your client support SSL/TLS

+ *
+ * // In your {@link ChannelInitializer}:
+ * {@link ChannelPipeline} p = channel.pipeline();
+ * {@link SslContext} sslCtx = {@link #newClientContext(File) SslContext.newClientContext(...)};
+ * p.addLast("ssl", {@link #newEngine(ByteBufAllocator, String, int) sslCtx.newEngine(channel.alloc(), host, port)});
+ * ...
+ * 
+ */ +public abstract class SslContext { + + /** + * Returns the default server-side implementation provider currently in use. + * + * @return {@link SslProvider#OPENSSL} if OpenSSL is available. {@link SslProvider#JDK} otherwise. + */ + public static SslProvider defaultServerProvider() { + if (OpenSsl.isAvailable()) { + return SslProvider.OPENSSL; + } else { + return SslProvider.JDK; + } + } + + /** + * Returns the default client-side implementation provider currently in use. + * + * @return {@link SslProvider#JDK}, because it is the only implementation at the moment + */ + public static SslProvider defaultClientProvider() { + return SslProvider.JDK; + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext(File certChainFile, File keyFile) throws SSLException { + return newServerContext(null, certChainFile, keyFile, null, null, null, 0, 0); + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext( + File certChainFile, File keyFile, String keyPassword) throws SSLException { + return newServerContext(null, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocols the application layer protocols to accept, in the order of preference. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext( + File certChainFile, File keyFile, String keyPassword, + Iterable ciphers, Iterable nextProtocols, + long sessionCacheSize, long sessionTimeout) throws SSLException { + return newServerContext( + null, certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext( + SslProvider provider, File certChainFile, File keyFile) throws SSLException { + return newServerContext(provider, certChainFile, keyFile, null, null, null, 0, 0); + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext( + SslProvider provider, File certChainFile, File keyFile, String keyPassword) throws SSLException { + return newServerContext(provider, certChainFile, keyFile, keyPassword, null, null, 0, 0); + } + + /** + * Creates a new server-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format + * @param keyFile a PKCS#8 private key file in PEM format + * @param keyPassword the password of the {@code keyFile}. + * {@code null} if it's not password-protected. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocols the application layer protocols to accept, in the order of preference. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + * @return a new server-side {@link SslContext} + */ + public static SslContext newServerContext( + SslProvider provider, + 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( + certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + case OPENSSL: + return new OpenSslServerContext( + certChainFile, keyFile, keyPassword, + ciphers, nextProtocols, sessionCacheSize, sessionTimeout); + default: + throw new Error(provider.toString()); + } + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext() throws SSLException { + return newClientContext(null, null, null, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext(File certChainFile) throws SSLException { + return newClientContext(null, certChainFile, null, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(null, null, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext( + File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(null, certChainFile, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocolSelector the {@link ApplicationProtocolSelector} that chooses one of the application layer + * protocols returned by a TLS server. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext( + File certChainFile, TrustManagerFactory trustManagerFactory, + Iterable ciphers, ApplicationProtocolSelector nextProtocolSelector, + long sessionCacheSize, long sessionTimeout) throws SSLException { + return newClientContext( + null, certChainFile, trustManagerFactory, + ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext(SslProvider provider) throws SSLException { + return newClientContext(provider, null, null, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext(SslProvider provider, File certChainFile) throws SSLException { + return newClientContext(provider, certChainFile, null, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext( + SslProvider provider, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(provider, null, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext( + SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { + return newClientContext(provider, certChainFile, trustManagerFactory, null, null, 0, 0); + } + + /** + * Creates a new client-side {@link SslContext}. + * + * @param provider the {@link SslContext} implementation to use. + * {@code null} to use the current default one. + * @param certChainFile an X.509 certificate chain file in PEM format. + * {@code null} to use the system default + * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s + * that verifies the certificates sent from servers. + * {@code null} to use the default. + * @param ciphers the cipher suites to enable, in the order of preference. + * {@code null} to use the default cipher suites. + * @param nextProtocolSelector the {@link ApplicationProtocolSelector} that chooses one of the application layer + * protocols returned by a TLS server. + * {@code null} to disable TLS NPN/ALPN extension. + * @param sessionCacheSize the size of the cache used for storing SSL session objects. + * {@code 0} to use the default value. + * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. + * {@code 0} to use the default value. + * + * @return a new client-side {@link SslContext} + */ + public static SslContext newClientContext( + SslProvider provider, + 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( + certChainFile, trustManagerFactory, + ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); + } + + SslContext() { } + + /** + * Returns {@code true} if and only if this context is for server-side. + */ + public final boolean isServer() { + return !isClient(); + } + + /** + * Returns the {@code true} if and only if this context is for client-side. + */ + public abstract boolean isClient(); + + /** + * Returns the list of enabled cipher suites, in the order of preference. + */ + public abstract List cipherSuites(); + + /** + * Returns the size of the cache used for storing SSL session objects. + */ + public abstract long sessionCacheSize(); + + /** + * Returns the timeout for the cached SSL session objects, in seconds. + */ + public abstract long sessionTimeout(); + + /** + * Returns the client-side {@link ApplicationProtocolSelector} for the TLS NPN/ALPN extension. + * + * @return the client-side {@link ApplicationProtocolSelector}. + * {@code null} if NPN/ALPN extension has been disabled. + */ + public abstract ApplicationProtocolSelector nextProtocolSelector(); + + /** + * Returns the list of server-side application layer protocols for the TLS NPN/ALPN extension, + * in the order of preference. + * + * @return the list of server-side application layer protocols. + * {@code null} if NPN/ALPN extension has been disabled. + */ + public abstract List nextProtocols(); + + /** + * Creates a new {@link SSLEngine}. + * + * @return a new {@link SSLEngine} + */ + public abstract SSLEngine newEngine(ByteBufAllocator alloc); + + /** + * Creates a new {@link SSLEngine} using advisory peer information. + * + * @param peerHost the non-authoritative name of the host + * @param peerPort the non-authoritative port + * + * @return a new {@link SSLEngine} + */ + public abstract SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort); + + /** + * Creates a new {@link SslHandler}. + * + * @return a new {@link SslHandler} + */ + public final SslHandler newHandler(ByteBufAllocator alloc) { + return newHandler(newEngine(alloc)); + } + + /** + * Creates a new {@link SslHandler} with advisory peer information. + * + * @param peerHost the non-authoritative name of the host + * @param peerPort the non-authoritative port + * + * @return a new {@link SslHandler} + */ + public final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort) { + return newHandler(newEngine(alloc, peerHost, peerPort)); + } + + private static SslHandler newHandler(SSLEngine engine) { + return new SslHandler(engine); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index a76d35d244..6249d0d3c3 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -896,7 +896,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH packet.limit(packet.position() + recordLengths[i]); try { unwrapSingle(ctx, packet, totalLength); - assert !packet.hasRemaining(); + assert !packet.hasRemaining() || engine.isInboundDone(); } finally { ByteBuf decodeOut = this.decodeOut; if (decodeOut != null && decodeOut.isReadable()) { @@ -1083,6 +1083,9 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH */ private void setHandshakeSuccess() { if (handshakePromise.trySuccess(ctx.channel())) { + if (logger.isDebugEnabled()) { + logger.debug(ctx.channel() + " HANDSHAKEN: " + engine.getSession().getCipherSuite()); + } ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); } } @@ -1147,7 +1150,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH public void handlerAdded(final ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; - if (ctx.channel().isActive()) { + if (ctx.channel().isActive() && engine.getUseClientMode()) { // channelActive() event has been fired already, which means this.channelActive() will // not be invoked. We have to initialize here instead. handshake(); diff --git a/handler/src/main/java/io/netty/handler/ssl/SslProvider.java b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java new file mode 100644 index 0000000000..3d4f08bfa9 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +/** + * An enumeration of SSL/TLS protocol providers. + */ +public enum SslProvider { + /** + * JDK's default implementation. + */ + JDK, + /** + * OpenSSL-based implementation. + */ + OPENSSL +} diff --git a/handler/src/main/java/io/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java b/handler/src/main/java/io/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java new file mode 100644 index 0000000000..88a7c9dbab --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.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 io.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 io.netty.handler.ssl.util.SelfSignedCertificate.*; + +/** + * Generates a self-signed certificate using Bouncy Castle. + */ +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/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java new file mode 100644 index 0000000000..218ae1a48b --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java @@ -0,0 +1,207 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl.util; + +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.util.internal.EmptyArrays; + +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.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; + +/** + * An {@link TrustManagerFactory} that trusts an X.509 certificate whose SHA1 checksum matches. + *

+ * NOTE: + * Never use this {@link TrustManagerFactory} in production unless you are not sure what you are exactly doing with it. + *

+ * The SHA1 checksum of an X.509 certificate is calculated from its DER encoded format. You can get the fingerprint of + * an X.509 certificate using the {@code openssl} command. For example: + *

+ * $ openssl x509 -fingerprint -sha1 -in my_certificate.crt
+ * SHA1 Fingerprint=4E:85:10:55:BC:7B:12:08:D1:EA:0A:12:C9:72:EE:F3:AA:B2:C7:CB
+ * -----BEGIN CERTIFICATE-----
+ * MIIBqjCCAROgAwIBAgIJALiT3Nvp0kvmMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
+ * BAMTC2V4YW1wbGUuY29tMCAXDTcwMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5
+ * WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+ * gYkCgYEAnadvODG0QCiHhaFZlLHtr5gLIkDQS8ErZ//KfqeCHTC/KJsl3xYFk0zG
+ * aCv2FcmkOlokm77qV8qOW2DZdND7WuYzX6nLVuLb+GYxZ7b45iMAbAajvGh8jc9U
+ * o07fUIahGqTDAIAGCWsoLUOQ9nMzO/8GRHcXJAeQ2MGY2VpCcv0CAwEAATANBgkq
+ * hkiG9w0BAQUFAAOBgQBpRCnmjmNM0D7yrpkUJpBTNiqinhKLbeOvPWm+YmdInUUs
+ * LoMu0mZ1IANemLwqbwJJ76fknngeB+YuVAj46SurvVCV6ekwHcbgpW1u063IRwKk
+ * tQhOBO0HQxldUS4+4MYv/kuvnKkbjfgh5qfWw89Kx4kD+cycpP4yPtgDGk8ZMA==
+ * -----END CERTIFICATE-----
+ * 
+ *

+ */ +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() { + + @Override + public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException { + checkTrusted("client", chain); + } + + @Override + 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()); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return EmptyArrays.EMPTY_X509_CERTIFICATES; + } + }; + + private final byte[][] fingerprints; + + /** + * Creates a new instance. + * + * @param fingerprints a list of SHA1 fingerprints in heaxdecimal form + */ + public FingerprintTrustManagerFactory(Iterable fingerprints) { + this(toFingerprintArray(fingerprints)); + } + + /** + * Creates a new instance. + * + * @param fingerprints a list of SHA1 fingerprints in heaxdecimal form + */ + public FingerprintTrustManagerFactory(String... fingerprints) { + this(toFingerprintArray(Arrays.asList(fingerprints))); + } + + /** + * Creates a new instance. + * + * @param fingerprints a list of SHA1 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: " + + ByteBufUtil.hexDump(Unpooled.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/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java new file mode 100644 index 0000000000..04799758fd --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl.util; + +import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.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; + +/** + * An insecure {@link javax.net.ssl.TrustManagerFactory} that trusts all X.509 certificates without any verification. + *

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

+ */ +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() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String s) { + logger.debug("Accepting a client certificate: " + chain[0].getSubjectDN()); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String s) { + logger.debug("Accepting a server certificate: " + chain[0].getSubjectDN()); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return EmptyArrays.EMPTY_X509_CERTIFICATES; + } + }; + + 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/handler/src/main/java/io/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java b/handler/src/main/java/io/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java new file mode 100644 index 0000000000..691f50b0cd --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl.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 io.netty.handler.ssl.util.SelfSignedCertificate.*; + +/** + * Generates a self-signed certificate using {@code sun.security.x509} package provided by OpenJDK. + */ +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/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java new file mode 100644 index 0000000000..54257a7697 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java @@ -0,0 +1,207 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl.util; + +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +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. + *

+ * NOTE: + * Never use the certificate and private key generated by this class in production. + * It is purely for testing purposes, and thus it is very insecure. + * It even uses an insecure pseudo-random generator for faster generation internally. + *

+ * 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 (the {@code sun.security.x509} package). + * If it fails, it tries to use Bouncy Castle as a fallback. + *

+ */ +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; + + /** + * Creates a new instance. + */ + public SelfSignedCertificate() throws CertificateException { + this("example.com"); + } + + /** + * Creates a new instance. + * + * @param fqdn a fully qualified domain name + */ + 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); + } + + /** + * Creates a new instance. + * + * @param fqdn a fully qualified domain name + * @param random the {@link java.security.SecureRandom} to use + * @param bits the number of bits of the generated private key + */ + 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]); + } + + /** + * Returns the generated X.509 certificate file in PEM format. + */ + public File certificate() { + return certificate; + } + + /** + * Returns the generated RSA private key file in PEM format. + */ + public File privateKey() { + return privateKey; + } + + /** + * Deletes the generated X.509 certificate file and RSA private key file. + */ + 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(Unpooled.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)); + 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(Unpooled.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)); + 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/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java new file mode 100644 index 0000000000..b05e506ec9 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl.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(); + } + }; + + /** + * Creates a new instance. + */ + protected SimpleTrustManagerFactory() { + this(""); + } + + /** + * Creates a new instance. + * + * @param name the name of this {@link TrustManagerFactory} + */ + 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"); + } + } + + /** + * Initializes this factory with a source of certificate authorities and related trust material. + * + * @see TrustManagerFactorySpi#engineInit(KeyStore) + */ + protected abstract void engineInit(KeyStore keyStore) throws Exception; + + /** + * Initializes this factory with a source of provider-specific key material. + * + * @see TrustManagerFactorySpi#engineInit(ManagerFactoryParameters) + */ + protected abstract void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception; + + /** + * Returns one trust manager for each type of trust material. + * + * @see TrustManagerFactorySpi#engineGetTrustManagers() + */ + 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/handler/src/main/java/io/netty/handler/ssl/util/ThreadLocalInsecureRandom.java b/handler/src/main/java/io/netty/handler/ssl/util/ThreadLocalInsecureRandom.java new file mode 100644 index 0000000000..c69f886cd5 --- /dev/null +++ b/handler/src/main/java/io/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 io.netty.handler.ssl.util; + +import io.netty.util.internal.ThreadLocalRandom; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * Insecure {@link java.security.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/handler/src/main/java/io/netty/handler/ssl/util/package-info.java b/handler/src/main/java/io/netty/handler/ssl/util/package-info.java new file mode 100644 index 0000000000..fbdf16d7d2 --- /dev/null +++ b/handler/src/main/java/io/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 io.netty.handler.ssl.util; diff --git a/pom.xml b/pom.xml index 24fb68f7e7..a3eee935ba 100644 --- a/pom.xml +++ b/pom.xml @@ -252,6 +252,16 @@ 2.5.0
+ + + ${project.groupId} + netty-tcnative + 1.1.30.Fork1 + ${os.detected.classifier} + compile + true + +