From 7bf7c7b16a98cbcd25e2710c430a16c1612fde25 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 14 May 2014 06:19:12 +0200 Subject: [PATCH 01/26] Correctly release buffer when testing DelegatingHttp2ConnectionHandler --- .../http2/DelegatingHttp2ConnectionHandlerTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java index 317709a948..83b41b5826 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java @@ -51,6 +51,7 @@ import java.util.Arrays; import java.util.Collections; import io.netty.channel.DefaultChannelPromise; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -166,6 +167,11 @@ public class DelegatingHttp2ConnectionHandlerTest { handler.handlerAdded(ctx); } + @After + public void tearDown() throws Exception { + handler.handlerRemoved(ctx); + } + @Test public void clientShouldSendClientPrefaceStringWhenActive() throws Exception { when(connection.isServer()).thenReturn(false); @@ -189,7 +195,7 @@ public class DelegatingHttp2ConnectionHandlerTest { when(connection.isServer()).thenReturn(true); handler = new DelegatingHttp2ConnectionHandler(connection, reader, writer, inboundFlow, outboundFlow, observer); - handler.decode(ctx, Unpooled.copiedBuffer("BAD_PREFACE", UTF_8), Collections.emptyList()); + handler.channelRead(ctx, Unpooled.copiedBuffer("BAD_PREFACE", UTF_8)); verify(ctx).close(); } @@ -199,7 +205,7 @@ public class DelegatingHttp2ConnectionHandlerTest { when(connection.isServer()).thenReturn(true); handler = new DelegatingHttp2ConnectionHandler(connection, reader, writer, inboundFlow, outboundFlow, observer); - handler.decode(ctx, connectionPrefaceBuf(), Collections.emptyList()); + handler.channelRead(ctx, connectionPrefaceBuf()); verify(ctx, never()).close(); decode().onSettingsRead(ctx, new Http2Settings()); verify(observer).onSettingsRead(eq(ctx), eq(new Http2Settings())); From c3bd7a8ff10021026b0c223d36f022dbbf4fe397 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 13 May 2014 14:23:23 +0200 Subject: [PATCH 02/26] =?UTF-8?q?Better=20implementation=20of=20AttributeM?= =?UTF-8?q?ap=20and=20also=20add=20hasAttr(...).=20See=C2=A0[#2439]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: The old DefaultAttributeMap impl did more synchronization then needed and also did not expose a efficient way to check if an attribute exists with a specific key. Modifications: * Rewrite DefaultAttributeMap to not use IdentityHashMap and synchronization on the map directly. The new impl uses a combination of AtomicReferenceArray and synchronization per chain (linked-list). Also access the first Attribute per bucket can be done without any synchronization at all and just uses atomic operations. This should fit for most use-cases pretty weel. * Add hasAttr(...) implementation Result: It's now possible to check for the existence of a attribute without create one. Synchronization is per linked-list and the first entry can even be added via atomic operation. --- .../main/java/io/netty/util/AttributeMap.java | 5 + .../io/netty/util/DefaultAttributeMap.java | 145 +++++++++++++++--- .../channel/DefaultChannelHandlerContext.java | 5 + 3 files changed, 130 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/io/netty/util/AttributeMap.java b/common/src/main/java/io/netty/util/AttributeMap.java index 0524b1f287..826e6959a9 100644 --- a/common/src/main/java/io/netty/util/AttributeMap.java +++ b/common/src/main/java/io/netty/util/AttributeMap.java @@ -26,4 +26,9 @@ public interface AttributeMap { * an {@link Attribute} which does not have a value set yet. */ Attribute attr(AttributeKey key); + + /** + * Returns {@code} true if and only if the given {@link Attribute} exists in this {@link AttributeMap}. + */ + boolean hasAttr(AttributeKey key); } diff --git a/common/src/main/java/io/netty/util/DefaultAttributeMap.java b/common/src/main/java/io/netty/util/DefaultAttributeMap.java index 806610a035..a2ec5ea57e 100644 --- a/common/src/main/java/io/netty/util/DefaultAttributeMap.java +++ b/common/src/main/java/io/netty/util/DefaultAttributeMap.java @@ -17,65 +17,147 @@ package io.netty.util; import io.netty.util.internal.PlatformDependent; -import java.util.IdentityHashMap; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** - * Default {@link AttributeMap} implementation which use simple synchronization to keep the memory overhead + * Default {@link AttributeMap} implementation which use simple synchronization per bucket to keep the memory overhead * as low as possible. */ public class DefaultAttributeMap implements AttributeMap { @SuppressWarnings("rawtypes") - private static final AtomicReferenceFieldUpdater updater; + private static final AtomicReferenceFieldUpdater updater; static { @SuppressWarnings("rawtypes") - AtomicReferenceFieldUpdater referenceFieldUpdater = - PlatformDependent.newAtomicReferenceFieldUpdater(DefaultAttributeMap.class, "map"); + AtomicReferenceFieldUpdater referenceFieldUpdater = + PlatformDependent.newAtomicReferenceFieldUpdater(DefaultAttributeMap.class, "attributes"); if (referenceFieldUpdater == null) { - referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, Map.class, "map"); + referenceFieldUpdater = AtomicReferenceFieldUpdater + .newUpdater(DefaultAttributeMap.class, AtomicReferenceArray.class, "attributes"); } updater = referenceFieldUpdater; } + private static final int BUCKET_SIZE = 4; + private static final int MASK = BUCKET_SIZE - 1; + // Initialize lazily to reduce memory consumption; updated by AtomicReferenceFieldUpdater above. @SuppressWarnings("UnusedDeclaration") - private volatile Map, Attribute> map; + private volatile AtomicReferenceArray> attributes; + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Attribute attr(AttributeKey key) { - Map, Attribute> map = this.map; - if (map == null) { + if (key == null) { + throw new NullPointerException("key"); + } + AtomicReferenceArray> attributes = this.attributes; + if (attributes == null) { // Not using ConcurrentHashMap due to high memory consumption. - map = new IdentityHashMap, Attribute>(2); - if (!updater.compareAndSet(this, null, map)) { - map = this.map; + attributes = new AtomicReferenceArray>(BUCKET_SIZE); + + if (!updater.compareAndSet(this, null, attributes)) { + attributes = this.attributes; } } - synchronized (map) { - @SuppressWarnings("unchecked") - Attribute attr = (Attribute) map.get(key); - if (attr == null) { - attr = new DefaultAttribute(map, key); - map.put(key, attr); + int i = index(key); + DefaultAttribute head = attributes.get(i); + if (head == null) { + // No head exists yet which means we may be able to add the attribute without synchronization and just + // use compare and set. At worst we need to fallback to synchronization + head = new DefaultAttribute(key); + if (attributes.compareAndSet(i, null, head)) { + // we were able to add it so return the head right away + return (Attribute) head; + } else { + head = attributes.get(i); + } + } + + synchronized (head) { + DefaultAttribute curr = head; + for (;;) { + if (!curr.removed && curr.key == key) { + return (Attribute) curr; + } + + DefaultAttribute next = curr.next; + if (next == null) { + DefaultAttribute attr = new DefaultAttribute(head, key); + curr.next = attr; + attr.prev = curr; + } } - return attr; } } + @Override + public boolean hasAttr(AttributeKey key) { + if (key == null) { + throw new NullPointerException("key"); + } + AtomicReferenceArray> attributes = this.attributes; + if (attributes == null) { + // no attribute exists + return false; + } + + int i = index(key); + DefaultAttribute head = attributes.get(i); + if (head == null) { + // No attribute exists which point to the bucket in which the head should be located + return false; + } + + // check on the head can be done without synchronization + if (head.key == key && !head.removed) { + return true; + } + + synchronized (head) { + // we need to synchronize on the head + DefaultAttribute curr = head.next; + while (curr != null) { + if (!curr.removed && curr.key == key) { + return true; + } + curr = curr.next; + } + return false; + } + } + + private static int index(AttributeKey key) { + return key.id() & MASK; + } + + @SuppressWarnings("serial") private static final class DefaultAttribute extends AtomicReference implements Attribute { private static final long serialVersionUID = -2661411462200283011L; - private final Map, Attribute> map; + // The head of the linked-list this attribute belongs to, which may be itself + private final DefaultAttribute head; private final AttributeKey key; - DefaultAttribute(Map, Attribute> map, AttributeKey key) { - this.map = map; + // Double-linked list to prev and next node to allow fast removal + private DefaultAttribute prev; + private DefaultAttribute next; + + // Will be set to true one the attribute is removed via getAndRemove() or remove() + private volatile boolean removed; + + DefaultAttribute(DefaultAttribute head, AttributeKey key) { + this.head = head; + this.key = key; + } + + DefaultAttribute(AttributeKey key) { + head = this; this.key = key; } @@ -97,6 +179,7 @@ public class DefaultAttributeMap implements AttributeMap { @Override public T getAndRemove() { + removed = true; T oldValue = getAndSet(null); remove0(); return oldValue; @@ -104,13 +187,25 @@ public class DefaultAttributeMap implements AttributeMap { @Override public void remove() { + removed = true; set(null); remove0(); } private void remove0() { - synchronized (map) { - map.remove(key); + synchronized (head) { + // We only update the linked-list structure if prev != null because if it is null this + // DefaultAttribute acts also as head. The head must never be removed completely and just be + // marked as removed as all synchronization is done on the head itself for each bucket. + // The head itself will be GC'ed once the DefaultAttributeMap is GC'ed. So at most 5 heads will + // be removed lazy as the array size is 5. + if (prev != null) { + prev.next = next; + + if (next != null) { + next.prev = prev; + } + } } } } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java b/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java index bdb459e95b..5c95fb6b54 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java @@ -298,6 +298,11 @@ final class DefaultChannelHandlerContext implements ChannelHandlerContext, Resou return channel.attr(key); } + @Override + public boolean hasAttr(AttributeKey key) { + return channel.hasAttr(key); + } + @Override public ChannelHandlerContext fireChannelRegistered() { DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_REGISTERED); From 942db3aa2355119e4b5817d0d42cecfb7ed2a57c Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Sat, 17 May 2014 20:00:21 +0900 Subject: [PATCH 03/26] Preparation for porting OpenSSL support in 3.10 - Add licenses and dependencies --- NOTICE.txt | 59 ++++++++++++++++++-------------- handler/pom.xml | 5 +++ license/LICENSE.bouncycastle.txt | 23 +++++++++++++ pom.xml | 28 +++++++++++++++ 4 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 license/LICENSE.bouncycastle.txt diff --git a/NOTICE.txt b/NOTICE.txt index 7da06ae4a5..ef5371b9cb 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -50,14 +50,6 @@ WebSocket and HTTP server, which can be obtained at: * HOMEPAGE: * https://github.com/joewalnes/webbit -This product contains a modified portion of 'Caliper', Google's micro- -benchmarking framework, which can be obtained at: - - * LICENSE: - * license/LICENSE.caliper.txt (Apache License 2.0) - * HOMEPAGE: - * http://code.google.com/p/caliper/ - This product contains a modified portion of 'SLF4J', a simple logging facade for Java, which can be obtained at: @@ -72,6 +64,15 @@ Bloch of Google, Inc: * LICENSE: * license/LICENSE.deque.txt (Public Domain) +This product contains a modified version of Roland Kuhn's ASL2 +AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue. +It can be obtained at: + + * LICENSE: + * license/LICENSE.abstractnodequeue.txt (Public Domain) + * HOMEPAGE: + * https://github.com/akka/akka/blob/wip-2.2.3-for-scala-2.11/akka-actor/src/main/java/akka/dispatch/AbstractNodeQueue.java + This product optionally depends on 'JZlib', a re-implementation of zlib in pure Java, which can be obtained at: @@ -88,6 +89,23 @@ interchange format, which can be obtained at: * HOMEPAGE: * http://code.google.com/p/protobuf/ +This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +a temporary self-signed X.509 certificate when the JVM does not provide the +equivalent functionality. It can be obtained at: + + * LICENSE: + * license/LICENSE.bouncycastle.txt (MIT License) + * HOMEPAGE: + * http://www.bouncycastle.org/ + +This product optionally depends on 'Snappy', a compression library produced +by Google Inc, which can be obtained at: + + * LICENSE: + * license/LICENSE.snappy.txt (New BSD License) + * HOMEPAGE: + * http://code.google.com/p/snappy/ + This product optionally depends on 'JBoss Marshalling', an alternative Java serialization API, which can be obtained at: @@ -96,6 +114,14 @@ serialization API, which can be obtained at: * HOMEPAGE: * http://www.jboss.org/jbossmarshalling +This product optionally depends on 'Caliper', Google's micro- +benchmarking framework, which can be obtained at: + + * LICENSE: + * license/LICENSE.caliper.txt (Apache License 2.0) + * HOMEPAGE: + * http://code.google.com/p/caliper/ + This product optionally depends on 'Apache Commons Logging', a logging framework, which can be obtained at: @@ -111,20 +137,3 @@ can be obtained at: * license/LICENSE.log4j.txt (Apache License 2.0) * HOMEPAGE: * http://logging.apache.org/log4j/ - -This product optionally depends on 'Snappy', a compression library produced -by Google Inc, which can be obtained at: - - * LICENSE: - * license/LICENSE.snappy.txt (New BSD License) - * HOMEPAGE: - * http://code.google.com/p/snappy/ - -This product contains a modified version of Roland Kuhn's ASL2 -AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue. -It can be obtained at: - - * LICENSE: - * license/LICENSE.abstractnodequeue.txt (Public Domain) - * HOMEPAGE: - * https://github.com/akka/akka/blob/wip-2.2.3-for-scala-2.11/akka-actor/src/main/java/akka/dispatch/AbstractNodeQueue.java \ No newline at end of file diff --git a/handler/pom.xml b/handler/pom.xml index 933a7e75b5..beb620dbc7 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -44,6 +44,11 @@ netty-codec ${project.version} + + org.bouncycastle + bcpkix-jdk15on + true + diff --git a/license/LICENSE.bouncycastle.txt b/license/LICENSE.bouncycastle.txt new file mode 100644 index 0000000000..dbba1dd782 --- /dev/null +++ b/license/LICENSE.bouncycastle.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. + (http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/pom.xml b/pom.xml index 17cd52210b..7ff7ac1f13 100644 --- a/pom.xml +++ b/pom.xml @@ -246,11 +246,26 @@ 1.1.0.v20120525 + com.google.protobuf protobuf-java 2.5.0 + + + + org.bouncycastle + bcpkix-jdk15on + 1.50 + compile + true + + com.jcraft jzlib @@ -491,6 +506,19 @@ java.nio.channels.MembershipKey java.net.StandardProtocolFamily java.nio.channels.spi.SelectorProvider + + + sun.security.x509.AlgorithmId + sun.security.x509.CertificateAlgorithmId + sun.security.x509.CertificateIssuerName + sun.security.x509.CertificateSerialNumber + sun.security.x509.CertificateSubjectName + sun.security.x509.CertificateValidity + sun.security.x509.CertificateVersion + sun.security.x509.CertificateX509Key + sun.security.x509.X500Name + sun.security.x509.X509CertInfo + sun.security.x509.X509CertImpl From a72230061df5ebee2d1a9f595c9f9ae5ee9868fc Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Sun, 18 May 2014 02:26:01 +0900 Subject: [PATCH 04/26] Add an OpenSslEngine and the universal API for enabling SSL Motivation: Some users already use an SSLEngine implementation in finagle-native. It wraps OpenSSL to get higher SSL performance. However, to take advantage of it, finagle-native must be compiled manually, and it means we cannot pull it in as a dependency and thus we cannot test our SslHandler against the OpenSSL-based SSLEngine. For an instance, we had #2216. Because the construction procedures of JDK SSLEngine and OpenSslEngine are very different from each other, we also need to provide a universal way to enable SSL in a Netty application. Modifications: - Pull netty-tcnative in as an optional dependency. http://netty.io/wiki/forked-tomcat-native.html - Backport NativeLibraryLoader from 4.0 - Move OpenSSL-based SSLEngine implementation into our code base. - Copied from finagle-native; originally written by @jpinner et al. - Overall cleanup by @trustin. - Run all SslHandler tests with both default SSLEngine and OpenSslEngine - Add a unified API for creating an SSL context - SslContext allows you to create a new SSLEngine or a new SslHandler with your PKCS#8 key and X.509 certificate chain. - Add JdkSslContext and its subclasses - Add OpenSslServerContext - Add ApplicationProtocolSelector to ensure the future support for NPN (NextProtoNego) and ALPN (Application Layer Protocol Negotiation) on the client-side. - Add SimpleTrustManagerFactory to help a user write a TrustManagerFactory easily, which should be useful for those who need to write an alternative verification mechanism. For example, we can use it to implement an unsafe TrustManagerFactory that accepts self-signed certificates for testing purposes. - Add InsecureTrustManagerFactory and FingerprintTrustManager for quick and dirty testing - Add SelfSignedCertificate class which generates a self-signed X.509 certificate very easily. - Update all our examples to use SslContext.newClient/ServerContext() - SslHandler now logs the chosen cipher suite when handshake is finished. Result: - Cleaner unified API for configuring an SSL client and an SSL server regardless of its internal implementation. - When native libraries are available, OpenSSL-based SSLEngine implementation is selected automatically to take advantage of its performance benefit. - Examples take advantage of this modification and thus are cleaner. --- .../io/netty/util/internal/EmptyArrays.java | 2 + .../example/http/snoop/HttpSnoopClient.java | 10 +- .../snoop/HttpSnoopClientInitializer.java | 20 +- .../example/http/upload/HttpUploadClient.java | 10 +- .../upload/HttpUploadClientIntializer.java | 18 +- .../example/http/upload/HttpUploadServer.java | 19 +- .../upload/HttpUploadServerInitializer.java | 18 +- .../example/http2/client/Http2Client.java | 12 +- .../http2/client/Http2ClientInitializer.java | 13 +- .../example/http2/server/Http2Server.java | 15 +- .../http2/server/Http2ServerInitializer.java | 13 +- .../PortUnificationServer.java | 15 +- .../PortUnificationServerHandler.java | 22 +- .../example/securechat/SecureChatClient.java | 12 +- .../SecureChatClientInitializer.java | 17 +- .../securechat/SecureChatKeyStore.java | 313 ------- .../example/securechat/SecureChatServer.java | 15 +- .../SecureChatServerInitializer.java | 17 +- .../SecureChatSslContextFactory.java | 108 --- .../SecureChatTrustManagerFactory.java | 77 -- .../netty/example/spdy/client/SpdyClient.java | 10 +- .../spdy/client/SpdyClientInitializer.java | 12 +- .../netty/example/spdy/server/SpdyServer.java | 13 +- .../spdy/server/SpdyServerInitializer.java | 13 +- handler/pom.xml | 6 + .../ssl/ApplicationProtocolSelector.java | 34 + .../handler/ssl/JdkSslClientContext.java | 174 ++++ .../io/netty/handler/ssl/JdkSslContext.java | 178 ++++ .../handler/ssl/JdkSslServerContext.java | 176 ++++ .../java/io/netty/handler/ssl/OpenSsl.java | 84 ++ .../io/netty/handler/ssl/OpenSslEngine.java | 867 ++++++++++++++++++ .../handler/ssl/OpenSslServerContext.java | 349 +++++++ .../handler/ssl/OpenSslSessionStats.java | 122 +++ .../java/io/netty/handler/ssl/PemReader.java | 137 +++ .../java/io/netty/handler/ssl/SslContext.java | 463 ++++++++++ .../java/io/netty/handler/ssl/SslHandler.java | 7 +- .../io/netty/handler/ssl/SslProvider.java | 31 + .../BouncyCastleSelfSignedCertGenerator.java | 61 ++ .../util/FingerprintTrustManagerFactory.java | 207 +++++ .../ssl/util/InsecureTrustManagerFactory.java | 73 ++ .../util/OpenJdkSelfSignedCertGenerator.java | 72 ++ .../ssl/util/SelfSignedCertificate.java | 207 +++++ .../ssl/util/SimpleTrustManagerFactory.java | 132 +++ .../ssl/util/ThreadLocalInsecureRandom.java | 100 ++ .../netty/handler/ssl/util/package-info.java | 20 + pom.xml | 10 + testsuite/pom.xml | 6 + .../transport/socket/SocketSslEchoTest.java | 79 +- .../socket/SocketSslGreetingTest.java | 87 +- .../transport/socket/SocketStartTlsTest.java | 77 +- .../netty/testsuite/util/BogusKeyStore.java | 312 ------- .../util/BogusSslContextFactory.java | 76 -- .../util/BogusTrustManagerFactory.java | 69 -- .../channel/epoll/EpollSocketSslEchoTest.java | 7 +- .../epoll/EpollSocketSslGreetingTest.java | 36 + .../epoll/EpollSocketStartTlsTest.java | 5 + 56 files changed, 3953 insertions(+), 1105 deletions(-) delete mode 100644 example/src/main/java/io/netty/example/securechat/SecureChatKeyStore.java delete mode 100644 example/src/main/java/io/netty/example/securechat/SecureChatSslContextFactory.java delete mode 100644 example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolSelector.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSsl.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/PemReader.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/SslContext.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/SslProvider.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/InsecureTrustManagerFactory.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/ThreadLocalInsecureRandom.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/util/package-info.java delete mode 100644 testsuite/src/test/java/io/netty/testsuite/util/BogusKeyStore.java delete mode 100644 testsuite/src/test/java/io/netty/testsuite/util/BogusSslContextFactory.java delete mode 100644 testsuite/src/test/java/io/netty/testsuite/util/BogusTrustManagerFactory.java create mode 100644 transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java 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/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java index 811beda98d..609a54491f 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java @@ -14,7 +14,6 @@ */ package io.netty.example.http2.client; -import static java.util.concurrent.TimeUnit.SECONDS; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; @@ -28,23 +27,30 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import javax.net.ssl.SSLException; import java.net.InetSocketAddress; import java.util.concurrent.BlockingQueue; +import static java.util.concurrent.TimeUnit.*; + /** * An HTTP2 client that allows you to send HTTP2 frames to a server. Inbound and outbound frames are * logged. */ public class Http2Client { + private final SslContext sslCtx; private final String host; private final int port; private final Http2ClientConnectionHandler http2ConnectionHandler; private Channel channel; private EventLoopGroup workerGroup; - public Http2Client(String host, int port) { + public Http2Client(String host, int port) throws SSLException { + sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); this.host = host; this.port = port; http2ConnectionHandler = new Http2ClientConnectionHandler(); @@ -63,7 +69,7 @@ public class Http2Client { b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); b.remoteAddress(new InetSocketAddress(host, port)); - b.handler(new Http2ClientInitializer(http2ConnectionHandler)); + b.handler(new Http2ClientInitializer(sslCtx, http2ConnectionHandler)); // Start the client. channel = b.connect().syncUninterruptibly().channel(); diff --git a/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java b/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java index 939c22e750..4b20f6c362 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java @@ -17,29 +17,30 @@ package io.netty.example.http2.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.http2.AbstractHttp2ConnectionHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; +import org.eclipse.jetty.npn.NextProtoNego; import javax.net.ssl.SSLEngine; -import org.eclipse.jetty.npn.NextProtoNego; - /** * Configures the client pipeline to support HTTP/2 frames. */ public class Http2ClientInitializer extends ChannelInitializer { + private final SslContext sslCtx; private final AbstractHttp2ConnectionHandler connectionHandler; - public Http2ClientInitializer(AbstractHttp2ConnectionHandler connectionHandler) { + public Http2ClientInitializer(SslContext sslCtx, AbstractHttp2ConnectionHandler connectionHandler) { + this.sslCtx = sslCtx; this.connectionHandler = connectionHandler; } @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 Http2ClientProvider()); NextProtoNego.debug = true; diff --git a/example/src/main/java/io/netty/example/http2/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/server/Http2Server.java index 5bbf6c4d09..42d5146d62 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2Server.java @@ -22,6 +22,9 @@ 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.SslProvider; +import io.netty.handler.ssl.util.SelfSignedCertificate; /** * A HTTP/2 Server that responds to requests with a Hello World. @@ -30,9 +33,11 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; */ public class Http2Server { + private final SslContext sslCtx; private final int port; - public Http2Server(int port) { + public Http2Server(SslContext sslCtx, int port) { + this.sslCtx = sslCtx; this.port = port; } @@ -44,7 +49,7 @@ public class Http2Server { ServerBootstrap b = new ServerBootstrap(); b.option(ChannelOption.SO_BACKLOG, 1024); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) - .childHandler(new Http2ServerInitializer()); + .childHandler(new Http2ServerInitializer(sslCtx)); Channel ch = b.bind(port).sync().channel(); ch.closeFuture().sync(); @@ -65,7 +70,11 @@ public class Http2Server { System.out.println("HTTP2 server started at port " + port + '.'); - new Http2Server(port).run(); + // Configure SSL context. + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey()); + + new Http2Server(sslCtx, port).run(); } public static void checkForNpnSupport() { diff --git a/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java b/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java index 60c3101514..19d2dfb94f 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java @@ -19,7 +19,7 @@ package io.netty.example.http2.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; @@ -29,12 +29,19 @@ import javax.net.ssl.SSLEngine; * Sets up the Netty pipeline */ public class Http2ServerInitializer extends ChannelInitializer { + + private final SslContext sslCtx; + + public Http2ServerInitializer(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 handler = sslCtx.newHandler(ch.alloc()); + SSLEngine engine = handler.engine(); p.addLast("ssl", new SslHandler(engine)); // Setup NextProtoNego with our server provider 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 beb620dbc7..eb25980f2b 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 33920e3419..f5d4124e0e 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -848,7 +848,7 @@ public class SslHandler extends ByteToMessageDecoder { 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()) { @@ -986,6 +986,9 @@ public class SslHandler extends ByteToMessageDecoder { */ private void setHandshakeSuccess() { if (handshakePromise.trySuccess(ctx.channel())) { + if (logger.isDebugEnabled()) { + logger.debug(ctx.channel() + " HANDSHAKEN: " + engine.getSession().getCipherSuite()); + } ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS); } } @@ -1050,7 +1053,7 @@ public class SslHandler extends ByteToMessageDecoder { 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 7ff7ac1f13..debaa5d01e 100644 --- a/pom.xml +++ b/pom.xml @@ -253,6 +253,16 @@ 2.5.0
+ + + ${project.groupId} + netty-tcnative + 1.1.30.Fork1 + ${os.detected.classifier} + compile + true + + @@ -108,222 +105,52 @@ - - - - maven-dependency-plugin - - - copy - generate-resources - - copy - - - - - org.mortbay.jetty.npn - npn-boot - ${npn.version} - jar - false - ${project.build.directory}/npn - - - - - - - - - spdy-server - - - - org.codehaus.mojo - exec-maven-plugin - - ${java.home}/bin/java - - -Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar - -classpath - - io.netty.example.spdy.server.SpdyServer - - runtime - - - - + + io.netty.example.spdy.server.SpdyServer + spdy-client - - - - org.codehaus.mojo - exec-maven-plugin - - ${java.home}/bin/java - - -Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar - -classpath - - io.netty.example.spdy.client.SpdyClient - - runtime - - - - + + io.netty.example.spdy.client.SpdyClient + http2-server - - - - org.codehaus.mojo - exec-maven-plugin - - ${java.home}/bin/java - - -Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar - -classpath - - io.netty.example.http2.server.Http2Server - - runtime - - - - + + io.netty.example.http2.server.Http2Server + http2-client - - - - org.codehaus.mojo - exec-maven-plugin - - ${java.home}/bin/java - - -Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar - -classpath - - io.netty.example.http2.client.Http2Client - - runtime - - - - - - - - - 7u9 - - - java.version - 1.7.0_9 - - - 1.1.3.v20130313 - - - - 7u10 - - - java.version - 1.7.0_10 - - - - 1.1.3.v20130313 - - - - 7u11 - - - java.version - 1.7.0_11 - - - - 1.1.3.v20130313 - - - - 7u13 - - - java.version - 1.7.0_13 - - - - 1.1.4.v20130313 - - - - 7u15 - - - java.version - 1.7.0_15 - - - - 1.1.5.v20130313 - - - - 7u17 - - - java.version - 1.7.0_17 - - - - 1.1.5.v20130313 - - - - 7u21 - - - java.version - 1.7.0_21 - - - - 1.1.5.v20130313 - - - - 7u25 - - - java.version - 1.7.0_25 - - - - 1.1.5.v20130313 + io.netty.example.http2.client.Http2Client + + + + + org.codehaus.mojo + exec-maven-plugin + + ${java.home}/bin/java + + ${argLine.common} + ${argLine.leak} + ${argLine.coverage} + -classpath %classpath + ${exampleClass} + + runtime + + + + 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 a384e3da54..21e2d8ebe5 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 @@ -22,6 +22,7 @@ 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.SslProvider; import io.netty.handler.ssl.util.SelfSignedCertificate; /** @@ -85,7 +86,7 @@ public class SpdyServer { // Configure SSL. SelfSignedCertificate ssc = new SelfSignedCertificate(); - SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); + SslContext sslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey()); new SpdyServer(sslCtx, port).run(); } diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java index 9c5ee4ccdb..9f3779a3cd 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 @@ -41,7 +41,7 @@ public class SpdyServerInitializer extends ChannelInitializer { SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); SSLEngine engine = sslHandler.engine(); - p.addLast("ssl", new SslHandler(engine)); + p.addLast("ssl", sslHandler); // Setup NextProtoNego with our server provider NextProtoNego.put(engine, new SpdyServerProvider()); diff --git a/pom.xml b/pom.xml index c6ab75b957..230307185d 100644 --- a/pom.xml +++ b/pom.xml @@ -71,24 +71,13 @@ leak - - -server - -dsa -da -ea:io.netty... - -XX:+AggressiveOpts - -XX:+TieredCompilation - -XX:+UseBiasedLocking - -XX:+UseFastAccessorMethods - -XX:+OptimizeStringConcat - -XX:+HeapDumpOnOutOfMemoryError - -Dio.netty.leakDetectionLevel=3 - -verbose:gc - + -Dio.netty.leakDetectionLevel=paranoid coverage - ${jacoco.argLine} + ${jacoco.argLine} @@ -179,15 +168,154 @@ + + + + npn-7u9 + + + java.version + 1.7.0_9 + + + + 1.1.3.v20130313 + + + + npn-7u10 + + + java.version + 1.7.0_10 + + + + 1.1.3.v20130313 + + + + npn-7u11 + + + java.version + 1.7.0_11 + + + + 1.1.3.v20130313 + + + + npn-7u13 + + + java.version + 1.7.0_13 + + + + 1.1.4.v20130313 + + + + npn-7u15 + + + java.version + 1.7.0_15 + + + + 1.1.5.v20130313 + + + + npn-7u17 + + + java.version + 1.7.0_17 + + + + 1.1.5.v20130313 + + + + npn-7u21 + + + java.version + 1.7.0_21 + + + + 1.1.5.v20130313 + + + + npn-7u25 + + + java.version + 1.7.0_25 + + + + 1.1.5.v20130313 + + + + npn-7u40 + + + java.version + 1.7.0_40 + + + + 1.1.6.v20130911 + + + + npn-7u45 + + + java.version + 1.7.0_45 + + + + 1.1.6.v20130911 + + + + npn-7u51 + + + java.version + 1.7.0_51 + + + + 1.1.6.v20130911 + + UTF-8 UTF-8 1.3.18.GA - - - -server + 1.1.7.v20140316 + ${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${jetty.npn.version}/npn-boot-${jetty.npn.version}.jar + + -Xbootclasspath/p:${jetty.npn.path} + -server -dsa -da -ea:io.netty... -XX:+AggressiveOpts -XX:+TieredCompilation @@ -196,7 +324,9 @@ -XX:+OptimizeStringConcat -XX:+HeapDumpOnOutOfMemoryError -verbose:gc - + + -D_ + -D_ @@ -239,12 +369,17 @@ true - + org.eclipse.jetty.npn npn-api 1.1.0.v20120525 + + org.mortbay.jetty.npn + npn-boot + ${jetty.npn.version} + @@ -568,6 +703,24 @@ + + + maven-dependency-plugin + + + get-npn-boot + validate + + get + + + org.mortbay.jetty.npn + npn-boot + ${jetty.npn.version} + + + + maven-surefire-plugin @@ -580,7 +733,7 @@ **/TestUtil* random - ${test.jvm.argLine.coverage} ${test.jvm.argLine} + ${argLine.common} ${argLine.leak} ${argLine.coverage} @@ -908,7 +1061,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + 1.3 org.fusesource.hawtjni diff --git a/run-example.sh b/run-example.sh new file mode 100755 index 0000000000..9bf40f64cb --- /dev/null +++ b/run-example.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e +cd "`dirname "$0"`"/example +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + echo >&2 + echo "Available examples:" >&2 + grep -E '^ [-a-z0-9]*' pom.xml | sed -e 's#\(^.*\|.*$\)##g' | sed -e 's#^# #' >&2 + exit 1 +fi + +EXAMPLE_NAME="$1" + +echo "[INFO] Running: $EXAMPLE_NAME" +mvn -X -P "$EXAMPLE_NAME" compile exec:exec + From 59f3d550fec32cc9ddcc04a90679a437738c362d Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 20 May 2014 20:04:41 +0900 Subject: [PATCH 17/26] Fix a problem where all classes are compiled again Motivation: Due to a known problem[1] of maven-compiler-plugin, our build always compiles everything from scratch, which is waste of time. Modifications: Exclude package-info.java from the source list. Result: Much shorter build time. [1]: https://jira.codehaus.org/browse/MCOMPILER-205 --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 230307185d..761cfbad7b 100644 --- a/pom.xml +++ b/pom.xml @@ -625,6 +625,9 @@ --> 256m 1024m + + **/package-info.java + From 66d969b453307b05569880fbbb8ba1af57fab3d3 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 20 May 2014 22:37:55 +0900 Subject: [PATCH 18/26] Fix a build problem with JDK 8 Motivation: Build fails with JDK 8 because npn-boot does not work with JDK 8 Modifications: Do not specify bootclasspath when on JDK 8 Result: Build is green again. --- example/pom.xml | 1 + pom.xml | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/example/pom.xml b/example/pom.xml index c44e9e3b15..833ebbe7de 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -142,6 +142,7 @@ ${java.home}/bin/java ${argLine.common} + ${argLine.bootcp} ${argLine.leak} ${argLine.coverage} -classpath %classpath diff --git a/pom.xml b/pom.xml index 761cfbad7b..14cd9f4464 100644 --- a/pom.xml +++ b/pom.xml @@ -100,14 +100,16 @@ - jdk8 [1.8,) + false + + -D_ @@ -314,7 +316,6 @@ 1.1.7.v20140316 ${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${jetty.npn.version}/npn-boot-${jetty.npn.version}.jar - -Xbootclasspath/p:${jetty.npn.path} -server -dsa -da -ea:io.netty... -XX:+AggressiveOpts @@ -325,6 +326,7 @@ -XX:+HeapDumpOnOutOfMemoryError -verbose:gc + -Xbootclasspath/p:${jetty.npn.path} -D_ -D_ @@ -736,7 +738,7 @@ **/TestUtil* random - ${argLine.common} ${argLine.leak} ${argLine.coverage} + ${argLine.common} ${argLine.bootcp} ${argLine.leak} ${argLine.coverage} From 72ccf83861f30d32ab56173c344ad19fbd30cebb Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 20 May 2014 23:24:34 +0900 Subject: [PATCH 19/26] Clean up the execution mechanism of examples Motivation: - There's no way to pass an argument to an example. - Assigning a Maven profile for each example is an overkill. It makes the pom.xml crowded. Modifications: - Remove example profiles from example/pom.xml - Keep the list of examples in run-example.sh - run-example.sh passes all options to exec-maven-plugin. For example, we can now do this: ./run-example.sh -Dssl -Dport=443 http-server Result: - It's much easier to add a new example and provide an easy way to launch it. - We can still pass an arbitrary argument to the example being launched. (I'll update all examples to make them get their options from system properties rather than from args[]. --- example/pom.xml | 29 +---------------------------- run-example.sh | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/example/pom.xml b/example/pom.xml index 833ebbe7de..d09c478776 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -105,34 +105,6 @@ - - - spdy-server - - io.netty.example.spdy.server.SpdyServer - - - - spdy-client - - io.netty.example.spdy.client.SpdyClient - - - - - http2-server - - io.netty.example.http2.server.Http2Server - - - - http2-client - - io.netty.example.http2.client.Http2Client - - - - @@ -146,6 +118,7 @@ ${argLine.leak} ${argLine.coverage} -classpath %classpath + ${argLine.example} ${exampleClass} runtime diff --git a/run-example.sh b/run-example.sh index 9bf40f64cb..35fb34da7a 100755 --- a/run-example.sh +++ b/run-example.sh @@ -1,15 +1,44 @@ #!/bin/bash -e -cd "`dirname "$0"`"/example -if [[ $# -ne 1 ]]; then - echo "Usage: $0 " >&2 +declare -A EXAMPLE_MAP=( + ['spdy-server']='io.netty.example.spdy.server.SpdyServer' + ['spdy-client']='io.netty.example.spdy.client.SpdyClient' + ['http2-server']='io.netty.example.http2.server.Http2Server' + ['http2-client']='io.netty.example.http2.client.Http2Client' +) + +EXAMPLE='' +EXAMPLE_CLASS='' +EXAMPLE_ARGS='' +I=0 + +while [[ $# -gt 0 ]]; do + ARG="$1" + shift + if [[ "$ARG" =~ (^-.+) ]]; then + if [[ -z "$EXAMPLE_ARGS" ]]; then + EXAMPLE_ARGS="$ARG" + else + EXAMPLE_ARGS="$EXAMPLE_ARGS $ARG" + fi + else + EXAMPLE="$ARG" + EXAMPLE_CLASS="${EXAMPLE_MAP["$EXAMPLE"]}" + break + fi +done + +if [[ -z "$EXAMPLE" ]] || [[ -z "$EXAMPLE_CLASS" ]] || [[ $# -ne 0 ]]; then + echo " Usage: $0 [-D[=] ...] " >&2 + echo "Example: $0 -Dport=8443 -Dssl http-server" >&2 echo >&2 echo "Available examples:" >&2 - grep -E '^ [-a-z0-9]*' pom.xml | sed -e 's#\(^.*\|.*$\)##g' | sed -e 's#^# #' >&2 + for E in "${!EXAMPLE_MAP[@]}"; do + echo " $E" + done | sort >&2 exit 1 fi -EXAMPLE_NAME="$1" - -echo "[INFO] Running: $EXAMPLE_NAME" -mvn -X -P "$EXAMPLE_NAME" compile exec:exec +cd "`dirname "$0"`"/example +echo "[INFO] Running: $EXAMPLE ($EXAMPLE_CLASS $EXAMPLE_ARGS)" +exec mvn -nsu compile exec:exec -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS" From 50ae950203545622bd214c9f2bfd5e88956abad9 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 20 May 2014 23:38:53 +0900 Subject: [PATCH 20/26] Improve run-example.sh - More usage example - Newlines for prettier output --- run-example.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/run-example.sh b/run-example.sh index 35fb34da7a..82c529296c 100755 --- a/run-example.sh +++ b/run-example.sh @@ -30,11 +30,14 @@ done if [[ -z "$EXAMPLE" ]] || [[ -z "$EXAMPLE_CLASS" ]] || [[ $# -ne 0 ]]; then echo " Usage: $0 [-D[=] ...] " >&2 echo "Example: $0 -Dport=8443 -Dssl http-server" >&2 + echo " $0 -Dhost=127.0.0.1 -Dport=8009 echo-client" >&2 echo >&2 echo "Available examples:" >&2 + echo >&2 for E in "${!EXAMPLE_MAP[@]}"; do echo " $E" done | sort >&2 + echo >&2 exit 1 fi From 579973e35a4c39b4347ce60fd0b0159ad67d1125 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 12:54:36 +0900 Subject: [PATCH 21/26] Fix resource leak in DefaultHttp2FrameIOTest --- .../codec/http2/DefaultHttp2FrameIOTest.java | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java index 9912716a04..639ccc2cb1 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java @@ -15,12 +15,6 @@ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_SHORT; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isNull; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; @@ -28,7 +22,6 @@ import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.util.CharsetUtil; - import io.netty.util.ReferenceCountUtil; import org.junit.Before; import org.junit.Test; @@ -36,6 +29,11 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static io.netty.handler.codec.http2.Http2CodecUtil.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.*; + /** * Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}. */ @@ -70,51 +68,63 @@ public class DefaultHttp2FrameIOTest { public void emptyDataShouldRoundtrip() throws Exception { ByteBuf data = Unpooled.EMPTY_BUFFER; writer.writeData(ctx, promise, 1000, data, 0, false, false, false); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false)); + frame.release(); } @Test public void dataShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); writer.writeData(ctx, promise, 1000, data.retain().duplicate(), 0, false, false, false); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onDataRead(eq(ctx), eq(1000), eq(data), eq(0), eq(false), eq(false), eq(false)); + frame.release(); } @Test public void dataWithPaddingShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); writer.writeData(ctx, promise, 1, data.retain().duplicate(), 256, true, true, true); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onDataRead(eq(ctx), eq(1), eq(data), eq(256), eq(true), eq(true), eq(true)); + frame.release(); } @Test public void priorityShouldRoundtrip() throws Exception { writer.writePriority(ctx, promise, 1, 2, (short) 255, true); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPriorityRead(eq(ctx), eq(1), eq(2), eq((short) 255), eq(true)); + frame.release(); } @Test public void rstStreamShouldRoundtrip() throws Exception { writer.writeRstStream(ctx, promise, 1, MAX_UNSIGNED_INT); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onRstStreamRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT)); + frame.release(); } @Test public void emptySettingsShouldRoundtrip() throws Exception { writer.writeSettings(ctx, promise, new Http2Settings()); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onSettingsRead(eq(ctx), eq(new Http2Settings())); + frame.release(); } @Test @@ -127,35 +137,43 @@ public class DefaultHttp2FrameIOTest { settings.allowCompressedData(false); writer.writeSettings(ctx, promise, settings); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onSettingsRead(eq(ctx), eq(settings)); + frame.release(); } @Test public void settingsAckShouldRoundtrip() throws Exception { writer.writeSettingsAck(ctx, promise); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onSettingsAckRead(eq(ctx)); + frame.release(); } @Test public void pingShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); writer.writePing(ctx, promise, false, data.retain().duplicate()); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPingRead(eq(ctx), eq(data)); + frame.release(); } @Test public void pingAckShouldRoundtrip() throws Exception { ByteBuf data = dummyData(); writer.writePing(ctx, promise, true, data.retain().duplicate()); + ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPingAckRead(eq(ctx), eq(data)); + frame.release(); } @Test @@ -165,6 +183,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(data)); + frame.release(); } @Test @@ -173,16 +192,17 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onWindowUpdateRead(eq(ctx), eq(1), eq(Integer.MAX_VALUE)); + frame.release(); } @Test public void altSvcShouldRoundtrip() throws Exception { - writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", - "origin"); + writer.writeAltSvc(ctx, promise, 1, MAX_UNSIGNED_INT, MAX_UNSIGNED_SHORT, dummyData(), "host", "origin"); ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT), eq(dummyData()), eq("host"), eq("origin")); + frame.release(); } @Test @@ -192,6 +212,7 @@ public class DefaultHttp2FrameIOTest { reader.readFrame(ctx, frame, observer); verify(observer).onAltSvcRead(eq(ctx), eq(1), eq(MAX_UNSIGNED_INT), eq(MAX_UNSIGNED_SHORT), eq(dummyData()), eq("host"), isNull(String.class)); + frame.release(); } @Test @@ -200,6 +221,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onBlockedRead(eq(ctx), eq(1)); + frame.release(); } @Test @@ -209,6 +231,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true), eq(true)); + frame.release(); } @Test @@ -218,6 +241,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true)); + frame.release(); } @Test @@ -227,6 +251,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(0), eq(true), eq(true)); + frame.release(); } @Test @@ -236,6 +261,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(256), eq(true), eq(true)); + frame.release(); } @Test @@ -246,6 +272,7 @@ public class DefaultHttp2FrameIOTest { reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), eq(true), eq(true)); + frame.release(); } @Test @@ -256,6 +283,7 @@ public class DefaultHttp2FrameIOTest { reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256), eq(true), eq(true)); + frame.release(); } @Test @@ -266,6 +294,7 @@ public class DefaultHttp2FrameIOTest { reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(0), eq(true), eq(true)); + frame.release(); } @Test @@ -276,6 +305,7 @@ public class DefaultHttp2FrameIOTest { reader.readFrame(ctx, frame, observer); verify(observer).onHeadersRead(eq(ctx), eq(1), eq(headers), eq(2), eq((short) 3), eq(true), eq(256), eq(true), eq(true)); + frame.release(); } @Test @@ -285,6 +315,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0)); + frame.release(); } @Test @@ -294,6 +325,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0)); + frame.release(); } @Test @@ -303,6 +335,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256)); + frame.release(); } @Test @@ -312,6 +345,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(0)); + frame.release(); } @Test @@ -321,6 +355,7 @@ public class DefaultHttp2FrameIOTest { ByteBuf frame = captureWrite(); reader.readFrame(ctx, frame, observer); verify(observer).onPushPromiseRead(eq(ctx), eq(1), eq(2), eq(headers), eq(256)); + frame.release(); } private ByteBuf captureWrite() { From 3051c1ee0f3d61584f857e4e20b64d4918397753 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 13:35:28 +0900 Subject: [PATCH 22/26] Bash 3 compatibility Motivation: Mac OS X ships Bash 3, and it does not have an associative array (declare -A). Modifications: Do not use an associative array. Result: Can run examples on Mac OS X using run-example.sh --- run-example.sh | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/run-example.sh b/run-example.sh index 82c529296c..f8ed1857f5 100755 --- a/run-example.sh +++ b/run-example.sh @@ -1,28 +1,31 @@ #!/bin/bash -e -declare -A EXAMPLE_MAP=( - ['spdy-server']='io.netty.example.spdy.server.SpdyServer' - ['spdy-client']='io.netty.example.spdy.client.SpdyClient' - ['http2-server']='io.netty.example.http2.server.Http2Server' - ['http2-client']='io.netty.example.http2.client.Http2Client' +EXAMPLE_MAP=( + 'spdy-server:io.netty.example.spdy.server.SpdyServer' + 'spdy-client:io.netty.example.spdy.client.SpdyClient' + 'http2-server:io.netty.example.http2.server.Http2Server' + 'http2-client:io.netty.example.http2.client.Http2Client' ) EXAMPLE='' EXAMPLE_CLASS='' -EXAMPLE_ARGS='' +EXAMPLE_ARGS='-D_' I=0 while [[ $# -gt 0 ]]; do ARG="$1" shift if [[ "$ARG" =~ (^-.+) ]]; then - if [[ -z "$EXAMPLE_ARGS" ]]; then - EXAMPLE_ARGS="$ARG" - else - EXAMPLE_ARGS="$EXAMPLE_ARGS $ARG" - fi + EXAMPLE_ARGS="$EXAMPLE_ARGS $ARG" else EXAMPLE="$ARG" - EXAMPLE_CLASS="${EXAMPLE_MAP["$EXAMPLE"]}" + for E in "${EXAMPLE_MAP[@]}"; do + KEY="${E%%:*}" + VAL="${E##*:}" + if [[ "$EXAMPLE" == "$KEY" ]]; then + EXAMPLE_CLASS="$VAL" + break + fi + done break fi done @@ -34,8 +37,8 @@ if [[ -z "$EXAMPLE" ]] || [[ -z "$EXAMPLE_CLASS" ]] || [[ $# -ne 0 ]]; then echo >&2 echo "Available examples:" >&2 echo >&2 - for E in "${!EXAMPLE_MAP[@]}"; do - echo " $E" + for E in "${EXAMPLE_MAP[@]}"; do + echo " ${E%%:*}" done | sort >&2 echo >&2 exit 1 @@ -43,5 +46,5 @@ fi cd "`dirname "$0"`"/example echo "[INFO] Running: $EXAMPLE ($EXAMPLE_CLASS $EXAMPLE_ARGS)" -exec mvn -nsu compile exec:exec -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS" +exec mvn -X -nsu compile exec:exec -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS" From e167ec51eb29036cae9fa1551f319e166c10eba8 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 17:21:18 +0900 Subject: [PATCH 23/26] Add unified NextProtoNego extension support to SslContext Motivation: - OpenSslEngine and JDK SSLEngine (+ Jetty NPN) have different APIs to support NextProtoNego extension. - It is impossible to configure NPN with SslContext when the provider type is JDK. Modification: - Implement NextProtoNego extension by overriding the behavior of SSLSession.getProtocol() for both OpenSSLEngine and JDK SSLEngine. - SSLEngine.getProtocol() returns a string delimited by a colon (':') where the first component is the transport protosol (e.g. TLSv1.2) and the second component is the name of the application protocol - Remove the direct reference of Jetty NPN classes from the examples - Add SslContext.newApplicationProtocolSelector Result: - A user can now use both JDK SSLEngine and OpenSslEngine for NPN-based protocols such as HTTP2 and SPDY --- .../example/http2/client/Http2Client.java | 11 +- .../http2/client/Http2ClientInitializer.java | 15 +- .../http2/client/Http2ClientProvider.java | 58 ---- .../http2/server/Http2OrHttpHandler.java | 7 +- .../example/http2/server/Http2Server.java | 26 +- .../http2/server/Http2ServerInitializer.java | 18 +- .../http2/server/Http2ServerProvider.java | 66 ---- .../netty/example/spdy/client/SpdyClient.java | 10 +- .../spdy/client/SpdyClientInitializer.java | 12 +- .../spdy/client/SpdyClientProvider.java | 58 ---- .../spdy/server/SpdyOrHttpHandler.java | 5 +- .../netty/example/spdy/server/SpdyServer.java | 27 +- .../spdy/server/SpdyServerHandler.java | 3 + .../spdy/server/SpdyServerInitializer.java | 14 +- .../spdy/server/SpdyServerProvider.java | 63 ---- handler/pom.xml | 11 + .../handler/ssl/JdkSslClientContext.java | 17 +- .../io/netty/handler/ssl/JdkSslContext.java | 6 +- .../handler/ssl/JdkSslServerContext.java | 29 +- .../netty/handler/ssl/JettyNpnSslEngine.java | 286 ++++++++++++++++++ .../netty/handler/ssl/JettyNpnSslSession.java | 167 ++++++++++ .../io/netty/handler/ssl/OpenSslEngine.java | 20 +- .../handler/ssl/OpenSslServerContext.java | 6 +- .../java/io/netty/handler/ssl/SslContext.java | 78 +++++ pom.xml | 3 + 25 files changed, 658 insertions(+), 358 deletions(-) delete mode 100644 example/src/main/java/io/netty/example/http2/client/Http2ClientProvider.java delete mode 100644 example/src/main/java/io/netty/example/http2/server/Http2ServerProvider.java delete mode 100644 example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java delete mode 100644 example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java create mode 100644 handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java diff --git a/example/src/main/java/io/netty/example/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java index 609a54491f..8560bbe9a4 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java @@ -22,11 +22,11 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.example.http2.server.Http2Server; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -50,7 +50,13 @@ public class Http2Client { private EventLoopGroup workerGroup; public Http2Client(String host, int port) throws SSLException { - sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); + sslCtx = SslContext.newClientContext( + null, InsecureTrustManagerFactory.INSTANCE, null, + SslContext.newApplicationProtocolSelector( + SelectedProtocol.HTTP_2.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); + this.host = host; this.port = port; http2ConnectionHandler = new Http2ClientConnectionHandler(); @@ -108,7 +114,6 @@ public class Http2Client { } public static void main(String[] args) throws Exception { - Http2Server.checkForNpnSupport(); int port; if (args.length > 0) { port = Integer.parseInt(args[0]); diff --git a/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java b/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java index 4b20f6c362..d189aabb95 100644 --- a/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java +++ b/example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java @@ -15,14 +15,9 @@ package io.netty.example.http2.client; import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http2.AbstractHttp2ConnectionHandler; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; /** * Configures the client pipeline to support HTTP/2 frames. @@ -39,14 +34,6 @@ public class Http2ClientInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { - SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = sslHandler.engine(); - NextProtoNego.put(engine, new Http2ClientProvider()); - NextProtoNego.debug = true; - - ChannelPipeline pipeline = ch.pipeline(); - - pipeline.addLast("ssl", new SslHandler(engine)); - pipeline.addLast("http2ConnectionHandler", connectionHandler); + ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), connectionHandler); } } diff --git a/example/src/main/java/io/netty/example/http2/client/Http2ClientProvider.java b/example/src/main/java/io/netty/example/http2/client/Http2ClientProvider.java deleted file mode 100644 index c9150de520..0000000000 --- a/example/src/main/java/io/netty/example/http2/client/Http2ClientProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.example.http2.client; - -import org.eclipse.jetty.npn.NextProtoNego; - -import java.util.List; - -import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.*; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next Protocol - * Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which protocol to use - * over the secure connection. - *

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

- * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The - * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from Maven - * at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. - * - * @see Jetty documentation - */ -public class Http2ClientProvider implements NextProtoNego.ClientProvider { - - private String selectedProtocol; - - @Override - public String selectProtocol(List protocols) { - if (protocols.contains(HTTP_2.protocolName())) { - selectedProtocol = HTTP_2.protocolName(); - } - return selectedProtocol; - } - - @Override - public boolean supports() { - return true; - } - - @Override - public void unsupported() { - selectedProtocol = HTTP_1_1.protocolName(); - } -} diff --git a/example/src/main/java/io/netty/example/http2/server/Http2OrHttpHandler.java b/example/src/main/java/io/netty/example/http2/server/Http2OrHttpHandler.java index 1be6270e59..13160c44b2 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2OrHttpHandler.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2OrHttpHandler.java @@ -17,10 +17,7 @@ package io.netty.example.http2.server; import io.netty.channel.ChannelHandler; import io.netty.handler.codec.http2.Http2OrHttpChooser; -import org.eclipse.jetty.npn.NextProtoNego; - import javax.net.ssl.SSLEngine; - import java.util.logging.Logger; /** @@ -41,8 +38,8 @@ public class Http2OrHttpHandler extends Http2OrHttpChooser { @Override protected SelectedProtocol getProtocol(SSLEngine engine) { - Http2ServerProvider provider = (Http2ServerProvider) NextProtoNego.get(engine); - SelectedProtocol selectedProtocol = provider.getSelectedProtocol(); + String[] protocol = engine.getSession().getProtocol().split(":"); + SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]); logger.info("Selected Protocol is " + selectedProtocol); return selectedProtocol; diff --git a/example/src/main/java/io/netty/example/http2/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/server/Http2Server.java index 42d5146d62..0604d190e5 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2Server.java @@ -22,10 +22,12 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.SelfSignedCertificate; +import java.util.Arrays; + /** * A HTTP/2 Server that responds to requests with a Hello World. *

@@ -60,7 +62,6 @@ public class Http2Server { } public static void main(String[] args) throws Exception { - checkForNpnSupport(); int port; if (args.length > 0) { port = Integer.parseInt(args[0]); @@ -72,22 +73,13 @@ public class Http2Server { // Configure SSL context. SelfSignedCertificate ssc = new SelfSignedCertificate(); - SslContext sslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey()); + SslContext sslCtx = SslContext.newServerContext( + ssc.certificate(), ssc.privateKey(), null, null, + Arrays.asList( + SelectedProtocol.HTTP_2.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); new Http2Server(sslCtx, port).run(); } - - public static void checkForNpnSupport() { - try { - Class.forName("sun.security.ssl.NextProtoNegoExtension"); - } catch (ClassNotFoundException ignored) { - System.err.println(); - System.err.println("Could not locate Next Protocol Negotiation (NPN) implementation."); - System.err.println("The NPN jar should have been made available when building the examples with maven."); - System.err.println("Please check that your JDK is among those supported by Jetty-NPN:"); - System.err.println("http://wiki.eclipse.org/Jetty/Feature/NPN#Versions"); - System.err.println(); - throw new IllegalStateException("Could not locate NPN implementation. See console err for details."); - } - } } diff --git a/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java b/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java index 19d2dfb94f..00f3f59529 100644 --- a/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java +++ b/example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java @@ -17,13 +17,8 @@ package io.netty.example.http2.server; import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; /** * Sets up the Netty pipeline @@ -38,17 +33,6 @@ public class Http2ServerInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { - ChannelPipeline p = ch.pipeline(); - - SslHandler handler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = handler.engine(); - p.addLast("ssl", new SslHandler(engine)); - - // Setup NextProtoNego with our server provider - NextProtoNego.put(engine, new Http2ServerProvider()); - NextProtoNego.debug = true; - - // Negotiates with the browser if HTTP2 or HTTP is going to be used - p.addLast("handler", new Http2OrHttpHandler()); + ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new Http2OrHttpHandler()); } } diff --git a/example/src/main/java/io/netty/example/http2/server/Http2ServerProvider.java b/example/src/main/java/io/netty/example/http2/server/Http2ServerProvider.java deleted file mode 100644 index 9000ae4638..0000000000 --- a/example/src/main/java/io/netty/example/http2/server/Http2ServerProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.example.http2.server; - -import io.netty.handler.codec.http2.Http2OrHttpChooser; - -import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; - -import java.util.Arrays; -import java.util.List; - -import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.*; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next - * Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which - * protocol to use over the secure connection. - *

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

- * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The - * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from - * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. - * - * @see Jetty documentation - */ -public class Http2ServerProvider implements ServerProvider { - - private String selectedProtocol; - - @Override - public void unsupported() { - // if unsupported, default to http/1.1 - selectedProtocol = HTTP_1_1.protocolName(); - } - - @Override - public List protocols() { - return Arrays.asList(HTTP_2.protocolName(), HTTP_1_1.protocolName()); - } - - @Override - public void protocolSelected(String protocol) { - selectedProtocol = protocol; - } - - public Http2OrHttpChooser.SelectedProtocol getSelectedProtocol() { - if (selectedProtocol == null) { - return UNKNOWN; - } - return protocol(selectedProtocol); - } -} diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java index f5fa16f513..fbae5d12f1 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java @@ -27,8 +27,8 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import javax.net.ssl.SSLException; @@ -60,7 +60,13 @@ public class SpdyClient { private EventLoopGroup workerGroup; public SpdyClient(String host, int port) throws SSLException { - sslCtx = SslContext.newClientContext(SslProvider.JDK, InsecureTrustManagerFactory.INSTANCE); + sslCtx = SslContext.newClientContext( + null, InsecureTrustManagerFactory.INSTANCE, null, + SslContext.newApplicationProtocolSelector( + SelectedProtocol.SPDY_3_1.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); + this.host = host; this.port = port; httpResponseHandler = new HttpResponseClientHandler(); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java index 63400c472d..f8fc08bce4 100644 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClientInitializer.java @@ -23,10 +23,6 @@ import io.netty.handler.codec.spdy.SpdyHttpDecoder; import io.netty.handler.codec.spdy.SpdyHttpEncoder; import io.netty.handler.codec.spdy.SpdySessionHandler; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; import static io.netty.handler.codec.spdy.SpdyVersion.*; import static io.netty.util.internal.logging.InternalLogLevel.*; @@ -45,14 +41,8 @@ public class SpdyClientInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { - SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = sslHandler.engine(); - NextProtoNego.put(engine, new SpdyClientProvider()); - NextProtoNego.debug = true; - ChannelPipeline pipeline = ch.pipeline(); - - pipeline.addLast("ssl", sslHandler); + pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc())); pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(SPDY_3_1)); pipeline.addLast("spdyFrameLogger", new SpdyFrameLogger(INFO)); pipeline.addLast("spdySessionHandler", new SpdySessionHandler(SPDY_3_1, false)); diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java deleted file mode 100644 index c4adedf39b..0000000000 --- a/example/src/main/java/io/netty/example/spdy/client/SpdyClientProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.example.spdy.client; - -import org.eclipse.jetty.npn.NextProtoNego.ClientProvider; - -import java.util.List; - -import static io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol.*; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next Protocol - * Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which protocol to use - * over the secure connection. - *

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

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

@@ -72,7 +74,6 @@ public class SpdyServer { } public static void main(String[] args) throws Exception { - checkForNpnSupport(); int port; if (args.length > 0) { port = Integer.parseInt(args[0]); @@ -86,21 +87,13 @@ public class SpdyServer { // Configure SSL. SelfSignedCertificate ssc = new SelfSignedCertificate(); - SslContext sslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey()); + SslContext sslCtx = SslContext.newServerContext( + ssc.certificate(), ssc.privateKey(), null, null, + Arrays.asList( + SelectedProtocol.SPDY_3_1.protocolName(), + SelectedProtocol.HTTP_1_1.protocolName()), + 0, 0); + new SpdyServer(sslCtx, port).run(); } - - private static void checkForNpnSupport() { - try { - Class.forName("sun.security.ssl.NextProtoNegoExtension"); - } catch (ClassNotFoundException ignored) { - System.err.println(); - System.err.println("Could not locate Next Protocol Negotiation (NPN) implementation."); - System.err.println("The NPN jar should have been made available when building the examples with maven."); - System.err.println("Please check that your JDK is among those supported by Jetty-NPN:"); - System.err.println("http://wiki.eclipse.org/Jetty/Feature/NPN#Versions"); - System.err.println(); - throw new IllegalStateException("Could not locate NPN implementation. See console err for details."); - } - } } diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerHandler.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerHandler.java index b1af42ebc9..6d04fc1a8a 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerHandler.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServerHandler.java @@ -23,6 +23,7 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.ssl.SslHandler; import io.netty.util.CharsetUtil; import java.util.Date; @@ -43,6 +44,8 @@ public class SpdyServerHandler extends SimpleChannelInboundHandler { @Override public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { + SslHandler h = ctx.pipeline().get(SslHandler.class); + System.err.println(h.engine().getSession().getProtocol()); if (msg instanceof HttpRequest) { HttpRequest req = (HttpRequest) msg; diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java index 9f3779a3cd..e59954b002 100644 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java +++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServerInitializer.java @@ -19,10 +19,6 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import org.eclipse.jetty.npn.NextProtoNego; - -import javax.net.ssl.SSLEngine; /** * Sets up the Netty pipeline @@ -38,15 +34,7 @@ public class SpdyServerInitializer extends ChannelInitializer { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); - - SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); - SSLEngine engine = sslHandler.engine(); - p.addLast("ssl", sslHandler); - - // Setup NextProtoNego with our server provider - NextProtoNego.put(engine, new SpdyServerProvider()); - NextProtoNego.debug = true; - + p.addLast("ssl", sslCtx.newHandler(ch.alloc())); // Negotiates with the browser if SPDY or HTTP is going to be used p.addLast("handler", new SpdyOrHttpHandler()); } diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java deleted file mode 100644 index e8f5ff2eba..0000000000 --- a/example/src/main/java/io/netty/example/spdy/server/SpdyServerProvider.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.example.spdy.server; - -import io.netty.handler.codec.spdy.SpdyOrHttpChooser; -import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; - -import java.util.Arrays; -import java.util.List; - -/** - * The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next - * Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which - * protocol to use over the secure connection. - *

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

- * To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p: ...}. The - * "path_to_npn_boot_jar" is the path on the file system for the NPN Boot Jar file which can be downloaded from - * Maven at coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. - * - * @see Jetty documentation - */ -public class SpdyServerProvider implements ServerProvider { - - private String selectedProtocol; - - @Override - public void unsupported() { - // if unsupported, default to http/1.1 - selectedProtocol = "http/1.1"; - } - - @Override - public List protocols() { - return Arrays.asList("spdy/3.1", "http/1.1"); - } - - @Override - public void protocolSelected(String protocol) { - selectedProtocol = protocol; - } - - public SpdyOrHttpChooser.SelectedProtocol getSelectedProtocol() { - if (selectedProtocol == null) { - return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN; - } - return SpdyOrHttpChooser.SelectedProtocol.protocol(selectedProtocol); - } -} diff --git a/handler/pom.xml b/handler/pom.xml index eb25980f2b..aa06a88553 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -55,6 +55,17 @@ bcpkix-jdk15on true + + org.eclipse.jetty.npn + npn-api + true + + + org.mortbay.jetty.npn + npn-boot + provided + true + diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java index 05296723b1..05e13fdf8f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java @@ -20,6 +20,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManager; @@ -38,6 +39,7 @@ import java.util.List; public final class JdkSslClientContext extends JdkSslContext { private final SSLContext ctx; + private final ApplicationProtocolSelector nextProtocolSelector; /** * Creates a new instance. @@ -105,10 +107,12 @@ public final class JdkSslClientContext extends JdkSslContext { super(ciphers); - if (nextProtocolSelector != null) { + if (nextProtocolSelector != null && !JettyNpnSslEngine.isAvailable()) { throw new SSLException("NPN/ALPN unsupported: " + nextProtocolSelector); } + this.nextProtocolSelector = nextProtocolSelector; + try { if (certChainFile == null) { ctx = SSLContext.getInstance(PROTOCOL); @@ -166,7 +170,7 @@ public final class JdkSslClientContext extends JdkSslContext { @Override public ApplicationProtocolSelector nextProtocolSelector() { - return null; + return nextProtocolSelector; } @Override @@ -178,4 +182,13 @@ public final class JdkSslClientContext extends JdkSslContext { public SSLContext context() { return ctx; } + + @Override + SSLEngine wrapEngine(SSLEngine engine) { + if (nextProtocolSelector == null) { + return engine; + } else { + return new JettyNpnSslEngine(engine, nextProtocolSelector); + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java index d543f2dc80..c2d408ae5b 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java @@ -153,7 +153,7 @@ public abstract class JdkSslContext extends SslContext { engine.setEnabledCipherSuites(cipherSuites); engine.setEnabledProtocols(PROTOCOLS); engine.setUseClientMode(isClient()); - return engine; + return wrapEngine(engine); } @Override @@ -162,9 +162,11 @@ public abstract class JdkSslContext extends SslContext { engine.setEnabledCipherSuites(cipherSuites); engine.setEnabledProtocols(PROTOCOLS); engine.setUseClientMode(isClient()); - return engine; + return wrapEngine(engine); } + abstract SSLEngine wrapEngine(SSLEngine engine); + private static String[] toCipherSuiteArray(Iterable ciphers) { if (ciphers == null) { return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]); diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java index 30f836385f..4363f72f19 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java @@ -21,6 +21,7 @@ import io.netty.buffer.ByteBufInputStream; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; import java.io.File; @@ -42,6 +43,7 @@ import java.util.List; public final class JdkSslServerContext extends JdkSslContext { private final SSLContext ctx; + private final List nextProtocols; /** * Creates a new instance. @@ -100,7 +102,21 @@ public final class JdkSslServerContext extends JdkSslContext { } if (nextProtocols != null && nextProtocols.iterator().hasNext()) { - throw new SSLException("NPN/ALPN unsupported: " + nextProtocols); + if (!JettyNpnSslEngine.isAvailable()) { + throw new SSLException("NPN/ALPN unsupported: " + nextProtocols); + } + + List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + this.nextProtocols = Collections.unmodifiableList(list); + } else { + this.nextProtocols = Collections.emptyList(); } String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); @@ -173,11 +189,20 @@ public final class JdkSslServerContext extends JdkSslContext { @Override public List nextProtocols() { - return Collections.emptyList(); + return nextProtocols; } @Override public SSLContext context() { return ctx; } + + @Override + SSLEngine wrapEngine(SSLEngine engine) { + if (nextProtocols.isEmpty()) { + return engine; + } else { + return new JettyNpnSslEngine(engine, nextProtocols); + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java new file mode 100644 index 0000000000..04a0d2535a --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslEngine.java @@ -0,0 +1,286 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.eclipse.jetty.npn.NextProtoNego; +import org.eclipse.jetty.npn.NextProtoNego.ClientProvider; +import org.eclipse.jetty.npn.NextProtoNego.ServerProvider; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import java.nio.ByteBuffer; +import java.util.List; + +final class JettyNpnSslEngine extends SSLEngine { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(JettyNpnSslEngine.class); + + private static boolean available; + + static boolean isAvailable() { + updateAvailability(); + return available; + } + + private static void updateAvailability() { + if (available) { + return; + } + try { + // Try to get the bootstrap class loader. + ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent(); + if (bootloader == null) { + // If failed, use the system class loader, + // although it's not perfect to tell if NPN extension has been loaded. + bootloader = ClassLoader.getSystemClassLoader(); + } + Class.forName("sun.security.ssl.NextProtoNegoExtension", true, bootloader); + available = true; + } catch (Exception ignore) { + // npn-boot was not loaded. + } + } + + private final SSLEngine engine; + private final JettyNpnSslSession session; + + JettyNpnSslEngine(SSLEngine engine, final List nextProtocols) { + assert !nextProtocols.isEmpty(); + + this.engine = engine; + session = new JettyNpnSslSession(engine); + + NextProtoNego.put(engine, new ServerProvider() { + @Override + public void unsupported() { + getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1)); + } + + @Override + public List protocols() { + return nextProtocols; + } + + @Override + public void protocolSelected(String protocol) { + getSession().setApplicationProtocol(protocol); + } + }); + } + + JettyNpnSslEngine(SSLEngine engine, final ApplicationProtocolSelector nextProtocolSelector) { + this.engine = engine; + session = new JettyNpnSslSession(engine); + + NextProtoNego.put(engine, new ClientProvider() { + @Override + public boolean supports() { + return true; + } + + @Override + public void unsupported() { + session.setApplicationProtocol(null); + } + + @Override + public String selectProtocol(List protocols) { + String p = null; + try { + p = nextProtocolSelector.selectProtocol(protocols); + } catch (Exception e) { + logger.warn("Failed to select the next protocol:", e); + } + session.setApplicationProtocol(p); + return p; + } + }); + } + + @Override + public JettyNpnSslSession getSession() { + return session; + } + + @Override + public void closeInbound() throws SSLException { + NextProtoNego.remove(engine); + engine.closeInbound(); + } + + @Override + public void closeOutbound() { + NextProtoNego.remove(engine); + engine.closeOutbound(); + } + + @Override + public String getPeerHost() { + return engine.getPeerHost(); + } + + @Override + public int getPeerPort() { + return engine.getPeerPort(); + } + + @Override + public SSLEngineResult wrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException { + return engine.wrap(byteBuffer, byteBuffer2); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] byteBuffers, ByteBuffer byteBuffer) throws SSLException { + return engine.wrap(byteBuffers, byteBuffer); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i2, ByteBuffer byteBuffer) throws SSLException { + return engine.wrap(byteBuffers, i, i2, byteBuffer); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffer2); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffers); + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i2) throws SSLException { + return engine.unwrap(byteBuffer, byteBuffers, i, i2); + } + + @Override + public Runnable getDelegatedTask() { + return engine.getDelegatedTask(); + } + + @Override + public boolean isInboundDone() { + return engine.isInboundDone(); + } + + @Override + public boolean isOutboundDone() { + return engine.isOutboundDone(); + } + + @Override + public String[] getSupportedCipherSuites() { + return engine.getSupportedCipherSuites(); + } + + @Override + public String[] getEnabledCipherSuites() { + return engine.getEnabledCipherSuites(); + } + + @Override + public void setEnabledCipherSuites(String[] strings) { + engine.setEnabledCipherSuites(strings); + } + + @Override + public String[] getSupportedProtocols() { + return engine.getSupportedProtocols(); + } + + @Override + public String[] getEnabledProtocols() { + return engine.getEnabledProtocols(); + } + + @Override + public void setEnabledProtocols(String[] strings) { + engine.setEnabledProtocols(strings); + } + + @Override + public SSLSession getHandshakeSession() { + return engine.getHandshakeSession(); + } + + @Override + public void beginHandshake() throws SSLException { + engine.beginHandshake(); + } + + @Override + public HandshakeStatus getHandshakeStatus() { + return engine.getHandshakeStatus(); + } + + @Override + public void setUseClientMode(boolean b) { + engine.setUseClientMode(b); + } + + @Override + public boolean getUseClientMode() { + return engine.getUseClientMode(); + } + + @Override + public void setNeedClientAuth(boolean b) { + engine.setNeedClientAuth(b); + } + + @Override + public boolean getNeedClientAuth() { + return engine.getNeedClientAuth(); + } + + @Override + public void setWantClientAuth(boolean b) { + engine.setWantClientAuth(b); + } + + @Override + public boolean getWantClientAuth() { + return engine.getWantClientAuth(); + } + + @Override + public void setEnableSessionCreation(boolean b) { + engine.setEnableSessionCreation(b); + } + + @Override + public boolean getEnableSessionCreation() { + return engine.getEnableSessionCreation(); + } + + @Override + public SSLParameters getSSLParameters() { + return engine.getSSLParameters(); + } + + @Override + public void setSSLParameters(SSLParameters sslParameters) { + engine.setSSLParameters(sslParameters); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java new file mode 100644 index 0000000000..b05866f174 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.ssl; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.X509Certificate; +import java.security.Principal; +import java.security.cert.Certificate; + +final class JettyNpnSslSession implements SSLSession { + + private final SSLEngine engine; + private volatile String applicationProtocol; + + JettyNpnSslSession(SSLEngine engine) { + this.engine = engine; + } + + void setApplicationProtocol(String applicationProtocol) { + this.applicationProtocol = applicationProtocol; + } + + @Override + public String getProtocol() { + final String protocol = unwrap().getProtocol(); + final String applicationProtocol = this.applicationProtocol; + + if (applicationProtocol == null) { + if (protocol != null) { + return protocol.replace(':', '_'); + } else { + return null; + } + } + + final StringBuilder buf = new StringBuilder(32); + if (protocol != null) { + buf.append(protocol.replace(':', '_')); + buf.append(':'); + } else { + buf.append("null:"); + } + buf.append(applicationProtocol); + return buf.toString(); + } + + private SSLSession unwrap() { + return engine.getSession(); + } + + @Override + public byte[] getId() { + return unwrap().getId(); + } + + @Override + public SSLSessionContext getSessionContext() { + return unwrap().getSessionContext(); + } + + @Override + public long getCreationTime() { + return unwrap().getCreationTime(); + } + + @Override + public long getLastAccessedTime() { + return unwrap().getLastAccessedTime(); + } + + @Override + public void invalidate() { + unwrap().invalidate(); + } + + @Override + public boolean isValid() { + return unwrap().isValid(); + } + + @Override + public void putValue(String s, Object o) { + unwrap().putValue(s, o); + } + + @Override + public Object getValue(String s) { + return unwrap().getValue(s); + } + + @Override + public void removeValue(String s) { + unwrap().removeValue(s); + } + + @Override + public String[] getValueNames() { + return unwrap().getValueNames(); + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return unwrap().getPeerCertificates(); + } + + @Override + public Certificate[] getLocalCertificates() { + return unwrap().getLocalCertificates(); + } + + @Override + public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + return unwrap().getPeerCertificateChain(); + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return unwrap().getPeerPrincipal(); + } + + @Override + public Principal getLocalPrincipal() { + return unwrap().getLocalPrincipal(); + } + + @Override + public String getCipherSuite() { + return unwrap().getCipherSuite(); + } + + @Override + public String getPeerHost() { + return unwrap().getPeerHost(); + } + + @Override + public int getPeerPort() { + return unwrap().getPeerPort(); + } + + @Override + public int getPacketBufferSize() { + return unwrap().getPacketBufferSize(); + } + + @Override + public int getApplicationBufferSize() { + return unwrap().getApplicationBufferSize(); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 0fc8c4a904..8d27bfa042 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -85,7 +85,7 @@ public final class OpenSslEngine extends SSLEngine { private volatile int destroyed; private String cipher; - private String protocol; + private volatile String applicationProtocol; // SSL Engine status variables private boolean isInboundDone; @@ -95,6 +95,7 @@ public final class OpenSslEngine extends SSLEngine { private int lastPrimingReadResult; private final ByteBufAllocator alloc; + private final String fallbackApplicationProtocol; private SSLSession session; /** @@ -103,7 +104,7 @@ public final class OpenSslEngine extends SSLEngine { * @param sslCtx an OpenSSL {@code SSL_CTX} object * @param alloc the {@link ByteBufAllocator} that will be used by this engine */ - public OpenSslEngine(long sslCtx, ByteBufAllocator alloc) { + public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { throw new NullPointerException("sslContext"); @@ -115,6 +116,7 @@ public final class OpenSslEngine extends SSLEngine { this.alloc = alloc; ssl = SSL.newSSL(sslCtx, true); networkBIO = SSL.makeNetworkBIO(ssl); + this.fallbackApplicationProtocol = fallbackApplicationProtocol; } /** @@ -708,7 +710,13 @@ public final class OpenSslEngine extends SSLEngine { @Override public String getProtocol() { - return protocol; + // TODO: Figure out how to get the current protocol. + String applicationProtocol = OpenSslEngine.this.applicationProtocol; + if (applicationProtocol == null) { + return "unknown"; + } else { + return "unknown:" + applicationProtocol; + } } @Override @@ -796,7 +804,11 @@ public final class OpenSslEngine extends SSLEngine { if (SSL.isInInit(ssl) == 0) { handshakeFinished = true; cipher = SSL.getCipherForSSL(ssl); - protocol = SSL.getNextProtoNegotiated(ssl); + String applicationProtocol = SSL.getNextProtoNegotiated(ssl); + if (applicationProtocol == null) { + applicationProtocol = fallbackApplicationProtocol; + } + this.applicationProtocol = applicationProtocol; return FINISHED; } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java index 04142440b6..cbea241297 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -310,7 +310,11 @@ public final class OpenSslServerContext extends SslContext { */ @Override public SSLEngine newEngine(ByteBufAllocator alloc) { - return new OpenSslEngine(ctx, alloc); + if (unmodifiableNextProtocols.isEmpty()) { + return new OpenSslEngine(ctx, alloc, null); + } else { + return new OpenSslEngine(ctx, alloc, unmodifiableNextProtocols.get(unmodifiableNextProtocols.size() - 1)); + } } @Override diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java index 8da2a14757..f3a3f33b71 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java @@ -26,6 +26,7 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.File; +import java.util.ArrayList; import java.util.List; /** @@ -373,6 +374,83 @@ public abstract class SslContext { ciphers, nextProtocolSelector, sessionCacheSize, sessionTimeout); } + /** + * Creates a simple client-side {@link ApplicationProtocolSelector} that selects the most preferred protocol + * among the application protocols sent by the server. If there is no match, it chooses the least preferred one. + * + * @param nextProtocols the list of the supported client-side application protocols, in the order of preference + * @return the new {@link ApplicationProtocolSelector}. + * {@code null} if the specified {@code nextProtocols} does not contain any elements. + * + */ + public static ApplicationProtocolSelector newApplicationProtocolSelector(String... nextProtocols) { + if (nextProtocols == null) { + throw new NullPointerException("nextProtocols"); + } + + final List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + if (list.isEmpty()) { + return null; + } + + return newApplicationProtocolSelector(list); + } + + private static ApplicationProtocolSelector newApplicationProtocolSelector(final List list) { + return new ApplicationProtocolSelector() { + @Override + public String selectProtocol(List protocols) throws Exception { + for (String p: list) { + if (protocols.contains(p)) { + return p; + } + } + return list.get(list.size() - 1); + } + + @Override + public String toString() { + return "ApplicationProtocolSelector(" + list + ')'; + } + }; + } + + /** + * Creates a simple client-side {@link ApplicationProtocolSelector} that selects the most preferred protocol + * among the application protocols sent by the server. If there is no match, it chooses the least preferred one. + * + * @param nextProtocols the list of the supported client-side application protocols, in the order of preference + * @return the new {@link ApplicationProtocolSelector}. + * {@code null} if the specified {@code nextProtocols} does not contain any elements. + * + */ + public static ApplicationProtocolSelector newApplicationProtocolSelector(Iterable nextProtocols) { + if (nextProtocols == null) { + throw new NullPointerException("nextProtocols"); + } + + final List list = new ArrayList(); + for (String p: nextProtocols) { + if (p == null) { + break; + } + list.add(p); + } + + if (list.isEmpty()) { + return null; + } + + return newApplicationProtocolSelector(list); + } + SslContext() { } /** diff --git a/pom.xml b/pom.xml index 14cd9f4464..07618aaeac 100644 --- a/pom.xml +++ b/pom.xml @@ -669,6 +669,9 @@ sun.security.x509.X500Name sun.security.x509.X509CertInfo sun.security.x509.X509CertImpl + + + javax.net.ssl.SSLEngine From ddb59fbc0177a66d6373b5b35e6bce3e2309167a Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 17:29:18 +0900 Subject: [PATCH 24/26] Update the instruction for running SPDY examples --- .../java/io/netty/example/spdy/client/SpdyClient.java | 4 ++-- .../java/io/netty/example/spdy/client/package-info.java | 8 +++++--- .../java/io/netty/example/spdy/server/SpdyServer.java | 4 ++-- .../java/io/netty/example/spdy/server/package-info.java | 4 ++-- run-example.sh | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) 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 fbae5d12f1..821005f862 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 @@ -45,9 +45,9 @@ import static java.util.concurrent.TimeUnit.*; * coordinates org.mortbay.jetty.npn:npn-boot. Different versions applies to different OpenJDK versions. See * Jetty docs for more information. *

- * You may also use maven to start the client from the command line: + * You may also use the {@code run-example.sh} script to start the client from the command line: *

- *     mvn exec:exec -Pspdy-client
+ *     ./run-example spdy-client
  * 
*/ public class SpdyClient { diff --git a/example/src/main/java/io/netty/example/spdy/client/package-info.java b/example/src/main/java/io/netty/example/spdy/client/package-info.java index c9e8d44fec..de3f6d40ed 100644 --- a/example/src/main/java/io/netty/example/spdy/client/package-info.java +++ b/example/src/main/java/io/netty/example/spdy/client/package-info.java @@ -33,11 +33,13 @@ * After that, you can run {@link io.netty.example.spdy.client.SpdyClient}, also settings the JVM parameter * mentioned above. *

- * You may also use maven to start the server and the client from the command line: + * You may also use the {@code run-example.sh} script to start the server and the client from the command line: *

- *     mvn exec:exec -Pspdy-server
+ *     ./run-example spdy-server
  * 
* Then start the client in a different terminal window: - * mvn exec:exec -Pspdy-client + *
+ *     ./run-example spdy-client
+ * 
*/ package io.netty.example.spdy.client; 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 a281dd5ddb..f602051f8d 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 @@ -36,9 +36,9 @@ import java.util.Arrays; * See Jetty docs for more * information. *

- * You may also use maven to start the server from the command line: + * You may also use the {@code run-example.sh} script to start the server from the command line: *

- *     mvn exec:exec -Pspdy-server
+ *     ./run-example spdy-server
  * 
*

* Once started, you can test the server with your diff --git a/example/src/main/java/io/netty/example/spdy/server/package-info.java b/example/src/main/java/io/netty/example/spdy/server/package-info.java index ece1e7c9c7..1a61e7a58b 100644 --- a/example/src/main/java/io/netty/example/spdy/server/package-info.java +++ b/example/src/main/java/io/netty/example/spdy/server/package-info.java @@ -28,9 +28,9 @@ * See Jetty docs for more * information. *

- * You may also use maven to start the server from the command line: + * You may also use the {@code run-example.sh} script to start the server from the command line: *

- *     mvn exec:exec -Pspdy-server
+ *     ./run-example spdy-server
  * 
*

* Once started, you can test the server with your diff --git a/run-example.sh b/run-example.sh index f8ed1857f5..1670f5bc69 100755 --- a/run-example.sh +++ b/run-example.sh @@ -46,5 +46,5 @@ fi cd "`dirname "$0"`"/example echo "[INFO] Running: $EXAMPLE ($EXAMPLE_CLASS $EXAMPLE_ARGS)" -exec mvn -X -nsu compile exec:exec -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS" +exec mvn -nsu compile exec:exec -DargLine.example="$EXAMPLE_ARGS" -DexampleClass="$EXAMPLE_CLASS" From cf3e4e704336a6caa2c0a817d97ebd1ec839e931 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 17:44:07 +0900 Subject: [PATCH 25/26] Escape a colon in protocol names --- .../src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java | 3 +++ handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java index b05866f174..f4da3da1d5 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java +++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java @@ -34,6 +34,9 @@ final class JettyNpnSslSession implements SSLSession { } void setApplicationProtocol(String applicationProtocol) { + if (applicationProtocol != null) { + applicationProtocol = applicationProtocol.replace(':', '_'); + } this.applicationProtocol = applicationProtocol; } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 8d27bfa042..677df6c9d2 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -808,7 +808,7 @@ public final class OpenSslEngine extends SSLEngine { if (applicationProtocol == null) { applicationProtocol = fallbackApplicationProtocol; } - this.applicationProtocol = applicationProtocol; + this.applicationProtocol = applicationProtocol.replace(':', '_'); return FINISHED; } From 46ed3d4c97a505693a7a43ac7b70a28fcecf03ae Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 21 May 2014 20:02:15 +0900 Subject: [PATCH 26/26] Fix NPE in OpenSslEngine --- .../src/main/java/io/netty/handler/ssl/OpenSslEngine.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 677df6c9d2..a753c0ba6b 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -808,7 +808,11 @@ public final class OpenSslEngine extends SSLEngine { if (applicationProtocol == null) { applicationProtocol = fallbackApplicationProtocol; } - this.applicationProtocol = applicationProtocol.replace(':', '_'); + if (applicationProtocol != null) { + this.applicationProtocol = applicationProtocol.replace(':', '_'); + } else { + this.applicationProtocol = null; + } return FINISHED; }