diff --git a/.gitignore b/.gitignore
index b9e7986b1a..8a80d67d0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,28 +1,32 @@
-#Eclipse project files
+# Eclipse project files
.project
.classpath
.settings
-#IntelliJ IDEA project files and directories
+# IntelliJ IDEA project files and directories
*.iml
*.ipr
*.iws
.idea/
-#Geany project file
+# Geany project file
.geany
-#KDevelop project file and directory
+# KDevelop project file and directory
.kdev4/
*.kdev4
-#Build targets
+# Build targets
/target
*/target
-#Report directories
+# Report directories
/reports
*/reports
-#Mac-specific directory that no other operating system needs.
-.DS_Store
\ No newline at end of file
+# Mac-specific directory that no other operating system needs.
+.DS_Store
+
+# JVM crash logs
+hs_err_pid*.log
+
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/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() {
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()));
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/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/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
index 9c4ca1cf72..effed4c2c5 100644
--- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
@@ -42,6 +42,12 @@ final class PlatformDependent0 {
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
private static final long ADDRESS_FIELD_OFFSET;
+ /**
+ * Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling
+ * during a large copy.
+ */
+ private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
+
/**
* {@code true} if and only if the platform supports unaligned access.
*
@@ -155,11 +161,9 @@ final class PlatformDependent0 {
}
try {
Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
- if (cleaner == null) {
- throw new IllegalArgumentException(
- "attempted to deallocate the buffer which was allocated via JNIEnv->NewDirectByteBuffer()");
+ if (cleaner != null) {
+ cleaner.clean();
}
- cleaner.clean();
} catch (Throwable t) {
// Nothing we can do here.
}
@@ -308,11 +312,25 @@ final class PlatformDependent0 {
}
static void copyMemory(long srcAddr, long dstAddr, long length) {
- UNSAFE.copyMemory(srcAddr, dstAddr, length);
+ //UNSAFE.copyMemory(srcAddr, dstAddr, length);
+ while (length > 0) {
+ long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
+ UNSAFE.copyMemory(srcAddr, dstAddr, size);
+ length -= size;
+ srcAddr += size;
+ dstAddr += size;
+ }
}
static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) {
- UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
+ //UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
+ while (length > 0) {
+ long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
+ UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size);
+ length -= size;
+ srcOffset += size;
+ dstOffset += size;
+ }
}
static AtomicReferenceFieldUpdater newAtomicReferenceFieldUpdater(
diff --git a/example/pom.xml b/example/pom.xml
index 2861109307..d09c478776 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -28,9 +28,6 @@
Netty/Example
-
- 1.1.6.v20130911
- ${project.groupId}
@@ -71,6 +68,15 @@
com.google.protobufprotobuf-java
+
+ ${project.groupId}
+ netty-tcnative
+ ${os.detected.classifier}
+
+
+ org.eclipse.jetty.npn
+ npn-api
+ com.jcraftjzlib
@@ -81,10 +87,6 @@
javassistruntime
-
- org.eclipse.jetty.npn
- npn-api
-
@@ -106,219 +108,23 @@
- maven-dependency-plugin
-
-
- copy
- generate-resources
-
- copy
-
-
-
-
- org.mortbay.jetty.npn
- npn-boot
- ${npn.version}
- jar
- false
- ${project.build.directory}/npn
-
-
-
-
-
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ ${java.home}/bin/java
+
+ ${argLine.common}
+ ${argLine.bootcp}
+ ${argLine.leak}
+ ${argLine.coverage}
+ -classpath %classpath
+ ${argLine.example}
+ ${exampleClass}
+
+ runtime
+
-
-
-
- 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
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
- 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
-
-
-
-
-
-
- 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
-
-
-
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/Http2ExampleUtil.java b/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java
index e86224709a..08519f8c02 100644
--- a/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java
+++ b/example/src/main/java/io/netty/example/http2/Http2ExampleUtil.java
@@ -91,31 +91,9 @@ public final class Http2ExampleUtil {
}
}
- // If SSL was selected, verify that NPN is supported.
- if (ssl) {
- checkForNpnSupport();
- }
-
return new EndpointConfig(ssl, host, port);
}
- /**
- * Checks for NPN support. If not supported, prints an error message throws an exception.
- */
- 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.");
- }
- }
-
private Http2ExampleUtil() {
}
}
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 be07e90c98..204a8aac6e 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
@@ -28,9 +28,14 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.example.http2.Http2ExampleUtil.EndpointConfig;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.net.InetSocketAddress;
+import javax.net.ssl.SSLException;
+
/**
* An HTTP2 client that allows you to send HTTP2 frames to a server. Inbound and outbound frames are
* logged. When run from the command-line, sends a single HEADERS frame to the server and gets back
@@ -42,13 +47,24 @@ import java.net.InetSocketAddress;
*/
public class Http2Client {
+ private final SslContext sslCtx;
private final EndpointConfig config;
private Http2ClientConnectionHandler http2ConnectionHandler;
private Channel channel;
private EventLoopGroup workerGroup;
- public Http2Client(EndpointConfig config) {
+ public Http2Client(EndpointConfig config) throws SSLException {
this.config = config;
+ if (config.isSsl()) {
+ sslCtx = SslContext.newClientContext(
+ null, InsecureTrustManagerFactory.INSTANCE, null,
+ SslContext.newApplicationProtocolSelector(
+ SelectedProtocol.HTTP_2.protocolName(),
+ SelectedProtocol.HTTP_1_1.protocolName()),
+ 0, 0);
+ } else {
+ sslCtx = null;
+ }
}
/**
@@ -67,7 +83,7 @@ public class Http2Client {
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress(new InetSocketAddress(config.host(), config.port()));
- Http2ClientInitializer initializer = new Http2ClientInitializer(config.isSsl());
+ Http2ClientInitializer initializer = new Http2ClientInitializer(sslCtx);
b.handler(initializer);
// Start the client.
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 c11de637d7..e6f0d87191 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,37 +17,31 @@ package io.netty.example.http2.client;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
-import io.netty.handler.ssl.SslHandler;
-
-import javax.net.ssl.SSLEngine;
-
-import org.eclipse.jetty.npn.NextProtoNego;
+import io.netty.handler.ssl.SslContext;
/**
* Configures the client pipeline to support HTTP/2 frames.
*/
public class Http2ClientInitializer extends ChannelInitializer {
+ private final SslContext sslCtx;
private Http2ClientConnectionHandler connectionHandler;
- private final boolean ssl;
- public Http2ClientInitializer(boolean ssl) {
- this.ssl = ssl;
+ public Http2ClientInitializer(SslContext sslCtx) {
+ this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
connectionHandler = new Http2ClientConnectionHandler(ch.newPromise(), ch.newPromise());
- if (ssl) {
+ if (sslCtx != null) {
configureSsl(ch);
} else {
configureClearText(ch);
@@ -62,15 +56,7 @@ public class Http2ClientInitializer extends ChannelInitializer {
* Configure the pipeline for TLS NPN negotiation to HTTP/2.
*/
private void configureSsl(SocketChannel ch) {
- SSLEngine engine = SecureChatSslContextFactory.getClientContext().createSSLEngine();
- engine.setUseClientMode(true);
- 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 27484a7cf4..fcb586bf77 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
@@ -24,6 +24,11 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.example.http2.Http2ExampleUtil.EndpointConfig;
+import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
+
+import java.util.Arrays;
/**
* A HTTP/2 Server that responds to requests with a Hello World. Once started, you can test the
@@ -47,8 +52,21 @@ public class Http2Server {
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
+
+ // If SSL was selected, configure the SSL context.
+ SslContext sslCtx = null;
+ if (config.isSsl()) {
+ SelfSignedCertificate ssc = new SelfSignedCertificate();
+ sslCtx = SslContext.newServerContext(
+ ssc.certificate(), ssc.privateKey(), null, null,
+ Arrays.asList(
+ SelectedProtocol.HTTP_2.protocolName(),
+ SelectedProtocol.HTTP_1_1.protocolName()),
+ 0, 0);
+ }
+
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .childHandler(new Http2ServerInitializer(config.isSsl()));
+ .childHandler(new Http2ServerInitializer(sslCtx));
Channel ch = b.bind(config.port()).sync().channel();
ch.closeFuture().sync();
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 224b237be6..46bbbdb8f2 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
@@ -22,34 +22,29 @@ import io.netty.channel.ChannelHandlerContext;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerUpgradeHandler;
import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
-import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslContext;
import java.util.Arrays;
-import javax.net.ssl.SSLEngine;
-
-import org.eclipse.jetty.npn.NextProtoNego;
-
/**
* Sets up the Netty pipeline for the example server. Depending on the endpoint config, sets up the
* pipeline for NPN or cleartext HTTP upgrade to HTTP/2.
*/
public class Http2ServerInitializer extends ChannelInitializer {
- private final boolean ssl;
+ private final SslContext sslCtx;
- public Http2ServerInitializer(boolean ssl) {
- this.ssl = ssl;
+ public Http2ServerInitializer(SslContext sslCtx) {
+ this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
- if (ssl) {
+ if (sslCtx != null) {
configureSsl(ch);
} else {
configureClearText(ch);
@@ -60,18 +55,7 @@ public class Http2ServerInitializer extends ChannelInitializer {
* Configure the pipeline for TLS NPN negotiation to HTTP/2.
*/
private void configureSsl(SocketChannel ch) {
- ChannelPipeline p = ch.pipeline();
-
- SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
- engine.setUseClientMode(false);
- 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.
- *
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..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
@@ -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.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
+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;
@@ -41,20 +45,28 @@ 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:
*
*/
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(
+ 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();
@@ -73,7 +85,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..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
@@ -18,24 +18,22 @@ 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.SslHandler;
-import org.eclipse.jetty.npn.NextProtoNego;
-
-import javax.net.ssl.SSLEngine;
+import io.netty.handler.ssl.SslContext;
import static io.netty.handler.codec.spdy.SpdyVersion.*;
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 +41,8 @@ public class SpdyClientInitializer extends ChannelInitializer {
@Override
public void initChannel(SocketChannel ch) throws Exception {
- SSLEngine engine = SecureChatSslContextFactory.getClientContext().createSSLEngine();
- engine.setUseClientMode(true);
- NextProtoNego.put(engine, new SpdyClientProvider());
- NextProtoNego.debug = true;
-
ChannelPipeline pipeline = ch.pipeline();
-
- pipeline.addLast("ssl", new SslHandler(engine));
+ 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/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:
*
* 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/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 8e6c177ad7..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
@@ -21,6 +21,11 @@ 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.util.SelfSignedCertificate;
+
+import java.util.Arrays;
/**
* A SPDY Server that responds to a GET request with a Hello World.
@@ -31,9 +36,9 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
* 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:
*
* Once started, you can test the server with your
@@ -42,9 +47,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 +63,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();
@@ -67,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]);
@@ -79,20 +85,15 @@ 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(), null, null,
+ Arrays.asList(
+ SelectedProtocol.SPDY_3_1.protocolName(),
+ SelectedProtocol.HTTP_1_1.protocolName()),
+ 0, 0);
- 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.");
- }
+ new SpdyServer(sslCtx, port).run();
}
}
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
+
+ ${project.groupId}
+ netty-tcnative
+ ${os.detected.classifier}
+ true
+
+
+ org.bouncycastle
+ 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/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..05e13fdf8f
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
@@ -0,0 +1,194 @@
+/*
+ * 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.SSLEngine;
+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;
+ private final ApplicationProtocolSelector nextProtocolSelector;
+
+ /**
+ * 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 && !JettyNpnSslEngine.isAvailable()) {
+ throw new SSLException("NPN/ALPN unsupported: " + nextProtocolSelector);
+ }
+
+ this.nextProtocolSelector = 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");
+
+ ByteBuf[] certs = PemReader.readCertificates(certChainFile);
+ try {
+ for (ByteBuf buf: certs) {
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
+ X500Principal principal = cert.getSubjectX500Principal();
+ ks.setCertificateEntry(principal.getName("RFC2253"), cert);
+ }
+ } finally {
+ for (ByteBuf buf: certs) {
+ buf.release();
+ }
+ }
+
+ // 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 nextProtocolSelector;
+ }
+
+ @Override
+ public List nextProtocols() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ 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
new file mode 100644
index 0000000000..c2d408ae5b
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ // GCM (Galois/Counter Mode) requires JDK 8.
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ // AES256 requires JCE unlimited strength jurisdiction policy files.
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
+ // GCM (Galois/Counter Mode) requires JDK 8.
+ "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ "SSL_RSA_WITH_RC4_128_SHA",
+ "SSL_RSA_WITH_RC4_128_MD5",
+ "TLS_RSA_WITH_AES_128_CBC_SHA",
+ // AES256 requires JCE unlimited strength jurisdiction policy files.
+ "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 wrapEngine(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 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()]);
+ } 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..4363f72f19
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
@@ -0,0 +1,208 @@
+/*
+ * 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.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSessionContext;
+import java.io.File;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
+ */
+public final class JdkSslServerContext extends JdkSslContext {
+
+ private final SSLContext ctx;
+ private final List nextProtocols;
+
+ /**
+ * 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()) {
+ 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");
+ 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).release();
+ PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(encodedKey);
+
+ PrivateKey key;
+ try {
+ key = rsaKF.generatePrivate(encodedKeySpec);
+ } catch (InvalidKeySpecException ignore) {
+ key = dsaKF.generatePrivate(encodedKeySpec);
+ }
+
+ List certChain = new ArrayList();
+ ByteBuf[] certs = PemReader.readCertificates(certChainFile);
+ try {
+ for (ByteBuf buf: certs) {
+ certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
+ }
+ } finally {
+ for (ByteBuf buf: certs) {
+ buf.release();
+ }
+ }
+
+ 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 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..f4da3da1d5
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyNpnSslSession.java
@@ -0,0 +1,170 @@
+/*
+ * 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) {
+ if (applicationProtocol != null) {
+ applicationProtocol = applicationProtocol.replace(':', '_');
+ }
+ 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/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..a753c0ba6b
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
@@ -0,0 +1,885 @@
+/*
+ * 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;
+
+ static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH;
+
+ 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 volatile String applicationProtocol;
+
+ // SSL Engine status variables
+ private boolean isInboundDone;
+ private boolean isOutboundDone;
+ private boolean engineClosed;
+
+ private int lastPrimingReadResult;
+
+ private final ByteBufAllocator alloc;
+ private final String fallbackApplicationProtocol;
+ 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, String fallbackApplicationProtocol) {
+ 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);
+ this.fallbackApplicationProtocol = fallbackApplicationProtocol;
+ }
+
+ /**
+ * 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() {
+ // TODO: Figure out how to get the current protocol.
+ String applicationProtocol = OpenSslEngine.this.applicationProtocol;
+ if (applicationProtocol == null) {
+ return "unknown";
+ } else {
+ return "unknown:" + applicationProtocol;
+ }
+ }
+
+ @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);
+ String applicationProtocol = SSL.getNextProtoNegotiated(ssl);
+ if (applicationProtocol == null) {
+ applicationProtocol = fallbackApplicationProtocol;
+ }
+ if (applicationProtocol != null) {
+ this.applicationProtocol = applicationProtocol.replace(':', '_');
+ } else {
+ this.applicationProtocol = null;
+ }
+ 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..cbea241297
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
@@ -0,0 +1,353 @@
+/*
+ * 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) {
+ if (unmodifiableNextProtocols.isEmpty()) {
+ return new OpenSslEngine(ctx, alloc, null);
+ } else {
+ return new OpenSslEngine(ctx, alloc, unmodifiableNextProtocols.get(unmodifiableNextProtocols.size() - 1));
+ }
+ }
+
+ @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..ee606000bf
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/PemReader.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+ }
+
+ ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
+ ByteBuf der = Base64.decode(base64);
+ base64.release();
+ certs.add(der);
+
+ 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);
+ }
+
+ ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
+ ByteBuf der = Base64.decode(base64);
+ base64.release();
+ return der;
+ }
+
+ 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..f3a3f33b71
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
@@ -0,0 +1,541 @@
+/*
+ * 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.ArrayList;
+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}.
+ *
+ *
+ */
+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);
+ }
+
+ /**
+ * 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() { }
+
+ /**
+ * 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..b9277da171 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
@@ -16,6 +16,7 @@
package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
@@ -48,7 +49,6 @@ import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayDeque;
-import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
@@ -175,6 +175,29 @@ public class SslHandler extends ByteToMessageDecoder {
private final SSLEngine engine;
private final int maxPacketBufferSize;
+ // BEGIN Platform-dependent flags
+
+ /**
+ * {@code trus} if and only if {@link SSLEngine} expects a direct buffer.
+ */
+ private final boolean wantsDirectBuffer;
+ /**
+ * {@code true} if and only if {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} requires the output buffer
+ * to be always as large as {@link #maxPacketBufferSize} even if the input buffer contains small amount of data.
+ *
+ * If this flag is {@code false}, we allocate a smaller output buffer.
+ *
+ */
+ private final boolean wantsLargeOutboundNetworkBuffer;
+ /**
+ * {@code true} if and only if {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} expects a heap buffer rather than
+ * a direct buffer. For an unknown reason, JDK8 SSLEngine causes JVM to crash when its cipher suite uses Galois
+ * Counter Mode (GCM).
+ */
+ private boolean wantsInboundHeapBuffer;
+
+ // END Platform-dependent flags
+
private final boolean startTls;
private boolean sentFirstMessage;
private boolean flushedBeforeHandshakeDone;
@@ -189,7 +212,6 @@ public class SslHandler extends ByteToMessageDecoder {
private boolean needsFlush;
private int packetLength;
- private ByteBuf decodeOut;
private volatile long handshakeTimeoutMillis = 10000;
private volatile long closeNotifyTimeoutMillis = 3000;
@@ -217,6 +239,9 @@ public class SslHandler extends ByteToMessageDecoder {
this.engine = engine;
this.startTls = startTls;
maxPacketBufferSize = engine.getSession().getPacketBufferSize();
+
+ wantsDirectBuffer = engine instanceof OpenSslEngine;
+ wantsLargeOutboundNetworkBuffer = !(engine instanceof OpenSslEngine);
}
public long getHandshakeTimeoutMillis() {
@@ -318,10 +343,6 @@ public class SslHandler extends ByteToMessageDecoder {
@Override
public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
- if (decodeOut != null) {
- decodeOut.release();
- decodeOut = null;
- }
for (;;) {
PendingWrite write = pendingUnencryptedWrites.poll();
if (write == null) {
@@ -383,16 +404,18 @@ public class SslHandler extends ByteToMessageDecoder {
if (pending == null) {
break;
}
- if (out == null) {
- out = ctx.alloc().buffer(maxPacketBufferSize);
- }
if (!(pending.msg() instanceof ByteBuf)) {
ctx.write(pending.msg(), (ChannelPromise) pending.recycleAndGet());
pendingUnencryptedWrites.remove();
continue;
}
+
ByteBuf buf = (ByteBuf) pending.msg();
+ if (out == null) {
+ out = allocateOutNetBuf(ctx, buf.readableBytes());
+ }
+
SSLEngineResult result = wrap(engine, buf, out);
if (!buf.isReadable()) {
@@ -470,7 +493,7 @@ public class SslHandler extends ByteToMessageDecoder {
try {
for (;;) {
if (out == null) {
- out = ctx.alloc().buffer(maxPacketBufferSize);
+ out = allocateOutNetBuf(ctx, 0);
}
SSLEngineResult result = wrap(engine, Unpooled.EMPTY_BUFFER, out);
@@ -491,7 +514,7 @@ public class SslHandler extends ByteToMessageDecoder {
break;
case NEED_UNWRAP:
if (!inUnwrap) {
- unwrapNonApp(ctx);
+ unwrapNonAppData(ctx);
}
break;
case NEED_WRAP:
@@ -501,7 +524,7 @@ public class SslHandler extends ByteToMessageDecoder {
// Workaround for TLS False Start problem reported at:
// https://github.com/netty/netty/issues/1108#issuecomment-14266970
if (!inUnwrap) {
- unwrapNonApp(ctx);
+ unwrapNonAppData(ctx);
}
break;
default:
@@ -524,6 +547,12 @@ public class SslHandler extends ByteToMessageDecoder {
private SSLEngineResult wrap(SSLEngine engine, ByteBuf in, ByteBuf out) throws SSLException {
ByteBuffer in0 = in.nioBuffer();
+ if (!in0.isDirect()) {
+ ByteBuffer newIn0 = ByteBuffer.allocateDirect(in0.remaining());
+ newIn0.put(in0).flip();
+ in0 = newIn0;
+ }
+
for (;;) {
ByteBuffer out0 = out.nioBuffer(out.writerIndex(), out.writableBytes());
SSLEngineResult result = engine.wrap(in0, out0);
@@ -729,31 +758,25 @@ public class SslHandler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List
+ */
+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..d1559eb70d
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/util/OpenJdkSelfSignedCertGenerator.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.security.cert.CertificateException;
+
+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)));
+ try {
+ info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
+ } catch (CertificateException ignore) {
+ info.set(X509CertInfo.SUBJECT, owner);
+ }
+ try {
+ info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
+ } catch (CertificateException ignore) {
+ info.set(X509CertInfo.ISSUER, 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/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..07618aaeac 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=paranoidcoverage
- ${jacoco.argLine}
+ ${jacoco.argLine}
@@ -111,14 +100,16 @@
-
jdk8[1.8,)
+
false
+
+ -D_
@@ -179,15 +170,153 @@
+
+
+
+ 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-8UTF-81.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
+
+ -server
-dsa -da -ea:io.netty...
-XX:+AggressiveOpts
-XX:+TieredCompilation
@@ -196,7 +325,10 @@
-XX:+OptimizeStringConcat
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-
+
+ -Xbootclasspath/p:${jetty.npn.path}
+ -D_
+ -D_
@@ -239,18 +371,48 @@
true
-
+
org.eclipse.jetty.npnnpn-api1.1.0.v20120525
+
+ org.mortbay.jetty.npn
+ npn-boot
+ ${jetty.npn.version}
+
+
com.google.protobufprotobuf-java2.5.0
+
+
+
+ ${project.groupId}
+ netty-tcnative
+ 1.1.30.Fork1
+ ${os.detected.classifier}
+ compile
+ true
+
+
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.50
+ compile
+ true
+
+
com.jcraftjzlib
@@ -414,7 +576,7 @@
kr.motd.mavenos-maven-plugin
- 1.1.2
+ 1.2.2.Final
@@ -465,6 +627,9 @@
-->
256m1024m
+
+ **/package-info.java
+
@@ -491,6 +656,22 @@
java.nio.channels.MembershipKeyjava.net.StandardProtocolFamilyjava.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
+
+
+ javax.net.ssl.SSLEngine
@@ -530,6 +711,24 @@
+
+
+ maven-dependency-plugin
+
+
+ get-npn-boot
+ validate
+
+ get
+
+
+ org.mortbay.jetty.npn
+ npn-boot
+ ${jetty.npn.version}
+
+
+
+ maven-surefire-plugin
@@ -542,7 +741,7 @@
**/TestUtil*random
- ${test.jvm.argLine.coverage} ${test.jvm.argLine}
+ ${argLine.common} ${argLine.bootcp} ${argLine.leak} ${argLine.coverage}
@@ -870,7 +1069,7 @@
org.codehaus.mojoexec-maven-plugin
- 1.2.1
+ 1.3org.fusesource.hawtjni
diff --git a/run-example.sh b/run-example.sh
new file mode 100755
index 0000000000..1670f5bc69
--- /dev/null
+++ b/run-example.sh
@@ -0,0 +1,50 @@
+#!/bin/bash -e
+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='-D_'
+I=0
+
+while [[ $# -gt 0 ]]; do
+ ARG="$1"
+ shift
+ if [[ "$ARG" =~ (^-.+) ]]; then
+ EXAMPLE_ARGS="$EXAMPLE_ARGS $ARG"
+ else
+ EXAMPLE="$ARG"
+ for E in "${EXAMPLE_MAP[@]}"; do
+ KEY="${E%%:*}"
+ VAL="${E##*:}"
+ if [[ "$EXAMPLE" == "$KEY" ]]; then
+ EXAMPLE_CLASS="$VAL"
+ break
+ fi
+ done
+ 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 " $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
+
+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"
+
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 829ead4dba..273250d1af 100644
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -69,6 +69,12 @@
netty-transport-udt${project.version}
+
+ ${project.groupId}
+ netty-tcnative
+ ${os.detected.classifier}
+ true
+
diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java
index f1f12718c7..69f33d94cc 100644
--- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java
+++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java
@@ -25,17 +25,25 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.ssl.JdkSslClientContext;
+import io.netty.handler.ssl.JdkSslServerContext;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.OpenSslServerContext;
+import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;
-import io.netty.testsuite.util.BogusSslContextFactory;
import io.netty.util.concurrent.Future;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import javax.net.ssl.SSLEngine;
+import java.io.File;
import java.io.IOException;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -48,29 +56,67 @@ import static org.junit.Assert.*;
@RunWith(Parameterized.class)
public class SocketSslEchoTest extends AbstractSocketTest {
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslEchoTest.class);
+
private static final int FIRST_MESSAGE_SIZE = 16384;
private static final Random random = new Random();
+ private static final File CERT_FILE;
+ private static final File KEY_FILE;
static final byte[] data = new byte[1048576];
static {
random.nextBytes(data);
+
+ SelfSignedCertificate ssc;
+ try {
+ ssc = new SelfSignedCertificate();
+ } catch (CertificateException e) {
+ throw new Error(e);
+ }
+ CERT_FILE = ssc.certificate();
+ KEY_FILE = ssc.privateKey();
}
- @Parameters(name = "{index}: useChunkedWriteHandler = {0}, useCompositeByteBuf = {1}")
- public static Collection data() {
- List params = new ArrayList();
- for (int i = 0; i < 4; i ++) {
- params.add(new Object[] {
- (i & 2) != 0, (i & 1) != 0
- });
+ @Parameters(name =
+ "{index}: serverEngine = {0}, clientEngine = {1}, useChunkedWriteHandler = {2}, useCompositeByteBuf = {3}")
+ public static Collection data() throws Exception {
+ List serverContexts = new ArrayList();
+ serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
+
+ List clientContexts = new ArrayList();
+ clientContexts.add(new JdkSslClientContext(CERT_FILE));
+
+ boolean hasOpenSsl = OpenSsl.isAvailable();
+ if (hasOpenSsl) {
+ serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
+
+ // TODO: Client mode is not supported yet.
+ // clientContexts.add(new OpenSslContext(CERT_FILE));
+ } else {
+ logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
}
+
+ List params = new ArrayList();
+ for (SslContext sc: serverContexts) {
+ for (SslContext cc: clientContexts) {
+ for (int i = 0; i < 4; i ++) {
+ params.add(new Object[] { sc, cc, (i & 2) != 0, (i & 1) != 0 });
+ }
+ }
+ }
+
return params;
}
+ private final SslContext serverCtx;
+ private final SslContext clientCtx;
private final boolean useChunkedWriteHandler;
private final boolean useCompositeByteBuf;
- public SocketSslEchoTest(boolean useChunkedWriteHandler, boolean useCompositeByteBuf) {
+ public SocketSslEchoTest(
+ SslContext serverCtx, SslContext clientCtx, boolean useChunkedWriteHandler, boolean useCompositeByteBuf) {
+ this.serverCtx = serverCtx;
+ this.clientCtx = clientCtx;
this.useChunkedWriteHandler = useChunkedWriteHandler;
this.useCompositeByteBuf = useCompositeByteBuf;
}
@@ -97,16 +143,10 @@ public class SocketSslEchoTest extends AbstractSocketTest {
final EchoHandler sh = new EchoHandler(true, useCompositeByteBuf, autoRead);
final EchoHandler ch = new EchoHandler(false, useCompositeByteBuf, autoRead);
- final SSLEngine sse = BogusSslContextFactory.getServerContext().createSSLEngine();
- final SSLEngine cse = BogusSslContextFactory.getClientContext().createSSLEngine();
- sse.setUseClientMode(false);
- cse.setUseClientMode(true);
-
sb.childHandler(new ChannelInitializer() {
@Override
- @SuppressWarnings("deprecation")
public void initChannel(SocketChannel sch) throws Exception {
- sch.pipeline().addFirst("ssl", new SslHandler(sse));
+ sch.pipeline().addLast("ssl", serverCtx.newHandler(sch.alloc()));
if (useChunkedWriteHandler) {
sch.pipeline().addLast(new ChunkedWriteHandler());
}
@@ -116,9 +156,8 @@ public class SocketSslEchoTest extends AbstractSocketTest {
cb.handler(new ChannelInitializer() {
@Override
- @SuppressWarnings("deprecation")
public void initChannel(SocketChannel sch) throws Exception {
- sch.pipeline().addFirst("ssl", new SslHandler(cse));
+ sch.pipeline().addLast("ssl", clientCtx.newHandler(sch.alloc()));
if (useChunkedWriteHandler) {
sch.pipeline().addLast(new ChunkedWriteHandler());
}
@@ -195,7 +234,7 @@ public class SocketSslEchoTest extends AbstractSocketTest {
}
}
- private class EchoHandler extends SimpleChannelInboundHandler {
+ private static class EchoHandler extends SimpleChannelInboundHandler {
volatile Channel channel;
final AtomicReference exception = new AtomicReference();
volatile int counter;
diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java
index 5fb7fd343b..70b55c6513 100644
--- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java
+++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java
@@ -27,24 +27,87 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
-import io.netty.handler.ssl.SslHandler;
-import io.netty.testsuite.util.BogusSslContextFactory;
+import io.netty.handler.ssl.JdkSslClientContext;
+import io.netty.handler.ssl.JdkSslServerContext;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.OpenSslServerContext;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.ReferenceCountUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-import javax.net.ssl.SSLEngine;
+import java.io.File;
import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
-import static org.junit.Assert.assertEquals;
-
+import static org.junit.Assert.*;
+@RunWith(Parameterized.class)
public class SocketSslGreetingTest extends AbstractSocketTest {
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslGreetingTest.class);
+
private static final LogLevel LOG_LEVEL = LogLevel.TRACE;
+ private static final File CERT_FILE;
+ private static final File KEY_FILE;
private final ByteBuf greeting = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeByte('a'));
+ static {
+ SelfSignedCertificate ssc;
+ try {
+ ssc = new SelfSignedCertificate();
+ } catch (CertificateException e) {
+ throw new Error(e);
+ }
+ CERT_FILE = ssc.certificate();
+ KEY_FILE = ssc.privateKey();
+ }
+
+ @Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}")
+ public static Collection data() throws Exception {
+ List serverContexts = new ArrayList();
+ serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
+
+ List clientContexts = new ArrayList();
+ clientContexts.add(new JdkSslClientContext(CERT_FILE));
+
+ boolean hasOpenSsl = OpenSsl.isAvailable();
+ if (hasOpenSsl) {
+ serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
+
+ // TODO: Client mode is not supported yet.
+ // clientContexts.add(new OpenSslContext(CERT_FILE));
+ } else {
+ logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
+ }
+
+ List params = new ArrayList();
+ for (SslContext sc: serverContexts) {
+ for (SslContext cc: clientContexts) {
+ params.add(new Object[] { sc, cc });
+ }
+ }
+ return params;
+ }
+
+ private final SslContext serverCtx;
+ private final SslContext clientCtx;
+
+ public SocketSslGreetingTest(SslContext serverCtx, SslContext clientCtx) {
+ this.serverCtx = serverCtx;
+ this.clientCtx = clientCtx;
+ }
+
// Test for https://github.com/netty/netty/pull/2437
@Test(timeout = 30000)
public void testSslGreeting() throws Throwable {
@@ -58,12 +121,9 @@ public class SocketSslGreetingTest extends AbstractSocketTest {
sb.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel sch) throws Exception {
- final SSLEngine sse = BogusSslContextFactory.getServerContext().createSSLEngine();
- sse.setUseClientMode(false);
-
ChannelPipeline p = sch.pipeline();
- p.addLast(new SslHandler(sse));
- p.addLast("logger", new LoggingHandler(LOG_LEVEL));
+ p.addLast(serverCtx.newHandler(sch.alloc()));
+ p.addLast(new LoggingHandler(LOG_LEVEL));
p.addLast(sh);
}
});
@@ -71,12 +131,9 @@ public class SocketSslGreetingTest extends AbstractSocketTest {
cb.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel sch) throws Exception {
- final SSLEngine cse = BogusSslContextFactory.getClientContext().createSSLEngine();
- cse.setUseClientMode(true);
-
ChannelPipeline p = sch.pipeline();
- p.addLast(new SslHandler(cse));
- p.addLast("logger", new LoggingHandler(LOG_LEVEL));
+ p.addLast(clientCtx.newHandler(sch.alloc()));
+ p.addLast(new LoggingHandler(LOG_LEVEL));
p.addLast(ch);
}
});
diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java
index 123e6acc47..29f4ddf6df 100644
--- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java
+++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java
@@ -17,6 +17,7 @@ package io.netty.testsuite.transport.socket;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
@@ -28,26 +29,84 @@ import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.JdkSslClientContext;
+import io.netty.handler.ssl.JdkSslServerContext;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.OpenSslServerContext;
+import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
-import io.netty.testsuite.util.BogusSslContextFactory;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import javax.net.ssl.SSLEngine;
+import java.io.File;
import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
+@RunWith(Parameterized.class)
public class SocketStartTlsTest extends AbstractSocketTest {
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketStartTlsTest.class);
+
private static final LogLevel LOG_LEVEL = LogLevel.TRACE;
+ private static final File CERT_FILE;
+ private static final File KEY_FILE;
private static EventExecutorGroup executor;
+ static {
+ SelfSignedCertificate ssc;
+ try {
+ ssc = new SelfSignedCertificate();
+ } catch (CertificateException e) {
+ throw new Error(e);
+ }
+ CERT_FILE = ssc.certificate();
+ KEY_FILE = ssc.privateKey();
+ }
+
+ @Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}")
+ public static Collection data() throws Exception {
+ List serverContexts = new ArrayList();
+ serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
+
+ List clientContexts = new ArrayList();
+ clientContexts.add(new JdkSslClientContext(CERT_FILE));
+
+ boolean hasOpenSsl = OpenSsl.isAvailable();
+ if (hasOpenSsl) {
+ serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
+
+ // TODO: Client mode is not supported yet.
+ // clientContexts.add(new OpenSslContext(CERT_FILE));
+ } else {
+ logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
+ }
+
+ List params = new ArrayList();
+ for (SslContext sc: serverContexts) {
+ for (SslContext cc: clientContexts) {
+ params.add(new Object[] { sc, cc });
+ }
+ }
+ return params;
+ }
+
@BeforeClass
public static void createExecutor() {
executor = new DefaultEventExecutorGroup(2);
@@ -58,6 +117,14 @@ public class SocketStartTlsTest extends AbstractSocketTest {
executor.shutdownGracefully().sync();
}
+ private final SslContext serverCtx;
+ private final SslContext clientCtx;
+
+ public SocketStartTlsTest(SslContext serverCtx, SslContext clientCtx) {
+ this.serverCtx = serverCtx;
+ this.clientCtx = clientCtx;
+ }
+
@Test(timeout = 30000)
public void testStartTls() throws Throwable {
run();
@@ -78,8 +145,8 @@ public class SocketStartTlsTest extends AbstractSocketTest {
private void testStartTls(ServerBootstrap sb, Bootstrap cb, boolean autoRead) throws Throwable {
final EventExecutorGroup executor = SocketStartTlsTest.executor;
- final SSLEngine sse = BogusSslContextFactory.getServerContext().createSSLEngine();
- final SSLEngine cse = BogusSslContextFactory.getClientContext().createSSLEngine();
+ SSLEngine sse = serverCtx.newEngine(PooledByteBufAllocator.DEFAULT);
+ SSLEngine cse = clientCtx.newEngine(PooledByteBufAllocator.DEFAULT);
final StartTlsServerHandler sh = new StartTlsServerHandler(sse, autoRead);
final StartTlsClientHandler ch = new StartTlsClientHandler(cse, autoRead);
@@ -155,7 +222,7 @@ public class SocketStartTlsTest extends AbstractSocketTest {
}
}
- private class StartTlsClientHandler extends SimpleChannelInboundHandler {
+ private static class StartTlsClientHandler extends SimpleChannelInboundHandler {
private final SslHandler sslHandler;
private final boolean autoRead;
private Future handshakeFuture;
@@ -207,7 +274,7 @@ public class SocketStartTlsTest extends AbstractSocketTest {
}
}
- private class StartTlsServerHandler extends SimpleChannelInboundHandler {
+ private static class StartTlsServerHandler extends SimpleChannelInboundHandler {
private final SslHandler sslHandler;
private final boolean autoRead;
volatile Channel channel;
diff --git a/testsuite/src/test/java/io/netty/testsuite/util/BogusKeyStore.java b/testsuite/src/test/java/io/netty/testsuite/util/BogusKeyStore.java
deleted file mode 100644
index c20bf56626..0000000000
--- a/testsuite/src/test/java/io/netty/testsuite/util/BogusKeyStore.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright 2013 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.testsuite.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-/**
- * A bogus key store which provides all the required information to
- * create an example SSL connection.
- *
- * To generate a bogus key store:
- *