Merge branch 'master' of https://github.com/netty/netty into http2

Conflicts:
	example/src/main/java/io/netty/example/http2/client/Http2Client.java
	example/src/main/java/io/netty/example/http2/client/Http2ClientInitializer.java
	example/src/main/java/io/netty/example/http2/server/Http2Server.java
	example/src/main/java/io/netty/example/http2/server/Http2ServerInitializer.java
This commit is contained in:
nmittler 2014-05-21 07:44:18 -07:00
commit 054e4c5233
79 changed files with 5318 additions and 1835 deletions

4
.gitignore vendored
View File

@ -26,3 +26,7 @@
# Mac-specific directory that no other operating system needs.
.DS_Store
# JVM crash logs
hs_err_pid*.log

View File

@ -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

View File

@ -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() {

View File

@ -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()));

View File

@ -26,4 +26,9 @@ public interface AttributeMap {
* an {@link Attribute} which does not have a value set yet.
*/
<T> Attribute<T> attr(AttributeKey<T> key);
/**
* Returns {@code} true if and only if the given {@link Attribute} exists in this {@link AttributeMap}.
*/
<T> boolean hasAttr(AttributeKey<T> key);
}

View File

@ -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<DefaultAttributeMap, Map> updater;
private static final AtomicReferenceFieldUpdater<DefaultAttributeMap, AtomicReferenceArray> updater;
static {
@SuppressWarnings("rawtypes")
AtomicReferenceFieldUpdater<DefaultAttributeMap, Map> referenceFieldUpdater =
PlatformDependent.newAtomicReferenceFieldUpdater(DefaultAttributeMap.class, "map");
AtomicReferenceFieldUpdater<DefaultAttributeMap, AtomicReferenceArray> 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<AttributeKey<?>, Attribute<?>> map;
private volatile AtomicReferenceArray<DefaultAttribute<?>> attributes;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public <T> Attribute<T> attr(AttributeKey<T> key) {
Map<AttributeKey<?>, Attribute<?>> map = this.map;
if (map == null) {
if (key == null) {
throw new NullPointerException("key");
}
AtomicReferenceArray<DefaultAttribute<?>> attributes = this.attributes;
if (attributes == null) {
// Not using ConcurrentHashMap due to high memory consumption.
map = new IdentityHashMap<AttributeKey<?>, Attribute<?>>(2);
if (!updater.compareAndSet(this, null, map)) {
map = this.map;
attributes = new AtomicReferenceArray<DefaultAttribute<?>>(BUCKET_SIZE);
if (!updater.compareAndSet(this, null, attributes)) {
attributes = this.attributes;
}
}
synchronized (map) {
@SuppressWarnings("unchecked")
Attribute<T> attr = (Attribute<T>) map.get(key);
if (attr == null) {
attr = new DefaultAttribute<T>(map, key);
map.put(key, attr);
}
return 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<T>) head;
} else {
head = attributes.get(i);
}
}
synchronized (head) {
DefaultAttribute<?> curr = head;
for (;;) {
if (!curr.removed && curr.key == key) {
return (Attribute<T>) curr;
}
DefaultAttribute<?> next = curr.next;
if (next == null) {
DefaultAttribute<T> attr = new DefaultAttribute<T>(head, key);
curr.next = attr;
attr.prev = curr;
}
}
}
}
@Override
public <T> boolean hasAttr(AttributeKey<T> key) {
if (key == null) {
throw new NullPointerException("key");
}
AtomicReferenceArray<DefaultAttribute<?>> 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<T> extends AtomicReference<T> implements Attribute<T> {
private static final long serialVersionUID = -2661411462200283011L;
private final Map<AttributeKey<?>, Attribute<?>> map;
// The head of the linked-list this attribute belongs to, which may be itself
private final DefaultAttribute<?> head;
private final AttributeKey<T> key;
DefaultAttribute(Map<AttributeKey<?>, Attribute<?>> map, AttributeKey<T> 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<T> key) {
this.head = head;
this.key = key;
}
DefaultAttribute(AttributeKey<T> 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;
}
}
}
}
}

View File

@ -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() { }
}

View File

@ -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();
}
} 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 <U, W> AtomicReferenceFieldUpdater<U, W> newAtomicReferenceFieldUpdater(

View File

@ -28,9 +28,6 @@
<name>Netty/Example</name>
<properties>
<npn.version>1.1.6.v20130911</npn.version>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
@ -71,6 +68,15 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-tcnative</artifactId>
<classifier>${os.detected.classifier}</classifier>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
@ -81,10 +87,6 @@
<artifactId>javassist</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
</dependency>
<!-- see https://github.com/netty/netty/issues/874 -->
<dependency>
@ -103,38 +105,6 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<version>${npn.version}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/npn</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>spdy-server</id>
<build>
<plugins>
<plugin>
@ -142,183 +112,19 @@
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argument>
<argument>-classpath</argument>
<classpath/>
<argument>io.netty.example.spdy.server.SpdyServer</argument>
</arguments>
<commandlineArgs>
${argLine.common}
${argLine.bootcp}
${argLine.leak}
${argLine.coverage}
-classpath %classpath
${argLine.example}
${exampleClass}
</commandlineArgs>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>spdy-client</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argument>
<argument>-classpath</argument>
<classpath/>
<argument>io.netty.example.spdy.client.SpdyClient</argument>
</arguments>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>http2-server</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argument>
<argument>-classpath</argument>
<classpath/>
<argument>io.netty.example.http2.server.Http2Server</argument>
</arguments>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>http2-client</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argument>
<argument>-classpath</argument>
<classpath/>
<argument>io.netty.example.http2.client.Http2Client</argument>
</arguments>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!--
profiles for the various npn vs OpenJDK version as found on:
http://www.eclipse.org/jetty/documentation/current/npn-chapter.html#npn-versions
-->
<profile>
<id>7u9</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_9</value>
</property>
</activation>
<properties>
<npn.version>1.1.3.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u10</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_10</value>
</property>
</activation>
<properties>
<npn.version>1.1.3.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u11</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_11</value>
</property>
</activation>
<properties>
<npn.version>1.1.3.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u13</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_13</value>
</property>
</activation>
<properties>
<npn.version>1.1.4.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u15</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_15</value>
</property>
</activation>
<properties>
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u17</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_17</value>
</property>
</activation>
<properties>
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u21</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_21</value>
</property>
</activation>
<properties>
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u25</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_25</value>
</property>
</activation>
<properties>
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
</profiles>
</project>

View File

@ -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();

View File

@ -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<SocketChannel> {
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<SocketChannel
ChannelPipeline p = ch.pipeline();
p.addLast("log", new LoggingHandler(LogLevel.INFO));
// Enable HTTPS if necessary.
if (ssl) {
SSLEngine engine =
SecureChatSslContextFactory.getClientContext().createSSLEngine();
engine.setUseClientMode(true);
p.addLast("ssl", new SslHandler(engine));
// Enable HTTPS if necessary.
if (sslCtx != null) {
p.addLast("ssl", sslCtx.newHandler(ch.alloc()));
}
p.addLast("codec", new HttpClientCodec());

View File

@ -37,6 +37,8 @@ import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder.ErrorDataEncoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.BufferedWriter;
import java.io.File;
@ -97,7 +99,13 @@ public class HttpUploadClient {
return;
}
final SslContext sslCtx;
boolean ssl = "https".equalsIgnoreCase(scheme);
if (ssl) {
sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE);
} else {
sslCtx = null;
}
URI uriFile;
try {
@ -125,7 +133,7 @@ public class HttpUploadClient {
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).handler(new HttpUploadClientIntializer(ssl));
b.group(group).channel(NioSocketChannel.class).handler(new HttpUploadClientIntializer(sslCtx));
// Simple Get form: no factory used (not usable)
List<Entry<String, String>> headers = formGet(b, host, port, get, uriSimple);

View File

@ -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<SocketChannel> {
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<SocketChannel
// Create a default pipeline implementation.
ChannelPipeline pipeline = ch.pipeline();
if (ssl) {
SSLEngine engine = SecureChatSslContextFactory.getClientContext().createSSLEngine();
engine.setUseClientMode(true);
pipeline.addLast("ssl", new SslHandler(engine));
if (sslCtx != null) {
pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
}
pipeline.addLast("codec", new HttpClientCodec());

View File

@ -20,16 +20,20 @@ import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* A HTTP server showing how to use the HTTP multipart package for file uploads and decoding post data.
*/
public class HttpUploadServer {
private final SslContext sslCtx;
private final int port;
public static boolean isSSL;
public HttpUploadServer(int port) {
public HttpUploadServer(SslContext sslCtx, int port) {
this.sslCtx = sslCtx;
this.port = port;
}
@ -39,7 +43,7 @@ public class HttpUploadServer {
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new HttpUploadServerInitializer());
.childHandler(new HttpUploadServerInitializer(sslCtx));
Channel ch = b.bind(port).sync().channel();
System.out.println("HTTP Upload Server at port " + port + '.');
@ -62,6 +66,15 @@ public class HttpUploadServer {
if (args.length > 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();
}
}

View File

@ -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<SocketChannel> {
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());

View File

@ -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() {
}
}

View File

@ -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.

View File

@ -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<SocketChannel> {
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<SocketChannel> {
* 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);
}
/**

View File

@ -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.
* <p>
* This NPN service provider negotiates using HTTP2.
* <p>
* To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. 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 <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty documentation</a>
*/
public class Http2ClientProvider implements NextProtoNego.ClientProvider {
private String selectedProtocol;
@Override
public String selectProtocol(List<String> 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();
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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<SocketChannel> {
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<SocketChannel> {
* 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());
}
/**

View File

@ -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.
* <p>
* This NPN service provider negotiates using HTTP_2.
* <p>
* To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. 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 <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty documentation</a>
*/
public class Http2ServerProvider implements ServerProvider {
private String selectedProtocol;
@Override
public void unsupported() {
// if unsupported, default to http/1.1
selectedProtocol = HTTP_1_1.protocolName();
}
@Override
public List<String> protocols() {
return Arrays.asList(HTTP_2.protocolName(), HTTP_1_1.protocolName());
}
@Override
public void protocolSelected(String protocol) {
selectedProtocol = protocol;
}
public Http2OrHttpChooser.SelectedProtocol getSelectedProtocol() {
if (selectedProtocol == null) {
return UNKNOWN;
}
return protocol(selectedProtocol);
}
}

View File

@ -21,6 +21,8 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* Serves two protocols (HTTP and Factorial) using only one port, enabling
@ -31,9 +33,11 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
*/
public class PortUnificationServer {
private final SslContext sslCtx;
private final int port;
public PortUnificationServer(int port) {
public PortUnificationServer(SslContext sslCtx, int port) {
this.sslCtx = sslCtx;
this.port = port;
}
@ -47,7 +51,7 @@ public class PortUnificationServer {
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new PortUnificationServerHandler());
ch.pipeline().addLast(new PortUnificationServerHandler(sslCtx));
}
});
@ -66,6 +70,11 @@ public class PortUnificationServer {
} else {
port = 8080;
}
new PortUnificationServer(port).run();
// Configure SSL.
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
new PortUnificationServer(sslCtx, port).run();
}
}

View File

@ -22,16 +22,15 @@ import io.netty.example.factorial.BigIntegerDecoder;
import io.netty.example.factorial.FactorialServerHandler;
import io.netty.example.factorial.NumberEncoder;
import io.netty.example.http.snoop.HttpSnoopServerHandler;
import io.netty.example.securechat.SecureChatSslContextFactory;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine;
import java.util.List;
/**
@ -40,14 +39,16 @@ import java.util.List;
*/
public class PortUnificationServerHandler extends ByteToMessageDecoder {
private final SslContext sslCtx;
private final boolean detectSsl;
private final boolean detectGzip;
public PortUnificationServerHandler() {
this(true, true);
public PortUnificationServerHandler(SslContext sslCtx) {
this(sslCtx, true, true);
}
private PortUnificationServerHandler(boolean detectSsl, boolean detectGzip) {
private PortUnificationServerHandler(SslContext sslCtx, boolean detectSsl, boolean detectGzip) {
this.sslCtx = sslCtx;
this.detectSsl = detectSsl;
this.detectGzip = detectGzip;
}
@ -111,13 +112,8 @@ public class PortUnificationServerHandler extends ByteToMessageDecoder {
private void enableSsl(ChannelHandlerContext ctx) {
ChannelPipeline p = ctx.pipeline();
SSLEngine engine =
SecureChatSslContextFactory.getServerContext().createSSLEngine();
engine.setUseClientMode(false);
p.addLast("ssl", new SslHandler(engine));
p.addLast("unificationA", new PortUnificationServerHandler(false, detectGzip));
p.addLast("ssl", sslCtx.newHandler(ctx.alloc()));
p.addLast("unificationA", new PortUnificationServerHandler(sslCtx, false, detectGzip));
p.remove(this);
}
@ -125,7 +121,7 @@ public class PortUnificationServerHandler extends ByteToMessageDecoder {
ChannelPipeline p = ctx.pipeline();
p.addLast("gzipdeflater", ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
p.addLast("gzipinflater", ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
p.addLast("unificationB", new PortUnificationServerHandler(detectSsl, false));
p.addLast("unificationB", new PortUnificationServerHandler(sslCtx, detectSsl, false));
p.remove(this);
}

View File

@ -22,6 +22,8 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.example.telnet.TelnetClient;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
@ -31,10 +33,12 @@ import java.io.InputStreamReader;
*/
public class SecureChatClient {
private final SslContext sslCtx;
private final String host;
private final int port;
public SecureChatClient(String host, int port) {
public SecureChatClient(SslContext sslCtx, String host, int port) {
this.sslCtx = sslCtx;
this.host = host;
this.port = port;
}
@ -45,7 +49,7 @@ public class SecureChatClient {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new SecureChatClientInitializer());
.handler(new SecureChatClientInitializer(sslCtx));
// Start the connection attempt.
Channel ch = b.connect(host, port).sync().channel();
@ -93,6 +97,8 @@ public class SecureChatClient {
String host = args[0];
int port = Integer.parseInt(args[1]);
new SecureChatClient(host, port).run();
// Configure SSL.
SslContext sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE);
new SecureChatClient(sslCtx, host, port).run();
}
}

View File

@ -22,15 +22,19 @@ import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine;
import io.netty.handler.ssl.SslContext;
/**
* Creates a newly configured {@link ChannelPipeline} for a new channel.
*/
public class SecureChatClientInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public SecureChatClientInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
@ -40,12 +44,7 @@ public class SecureChatClientInitializer extends ChannelInitializer<SocketChanne
// and accept any invalid certificates in the client side.
// You will need something more complicated to identify both
// and server in the real world.
SSLEngine engine =
SecureChatSslContextFactory.getClientContext().createSSLEngine();
engine.setUseClientMode(true);
pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
// On top of the SSL handler, add the text line codec.
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(

View File

@ -1,313 +0,0 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.example.securechat;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* A bogus key store which provides all the required information to
* create an example SSL connection.
*
* To generate a bogus key store:
* <pre>
* keytool -genkey -alias securechat -keysize 2048 -validity 36500
* -keyalg RSA -dname "CN=securechat"
* -keypass secret -storepass secret
* -keystore cert.jks
* </pre>
*/
public final class SecureChatKeyStore {
private static final short[] DATA = {
0xfe, 0xed, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x00, 0x00, 0x01, 0x1a, 0x9f, 0x57, 0xa5,
0x27, 0x00, 0x00, 0x01, 0x9a, 0x30, 0x82, 0x01,
0x96, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01,
0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05,
0x00, 0x04, 0x82, 0x01, 0x82, 0x48, 0x6d, 0xcf,
0x16, 0xb5, 0x50, 0x95, 0x36, 0xbf, 0x47, 0x27,
0x50, 0x58, 0x0d, 0xa2, 0x52, 0x7e, 0x25, 0xab,
0x14, 0x1a, 0x26, 0x5e, 0x2d, 0x8a, 0x23, 0x90,
0x60, 0x7f, 0x12, 0x20, 0x56, 0xd1, 0x43, 0xa2,
0x6b, 0x47, 0x5d, 0xed, 0x9d, 0xd4, 0xe5, 0x83,
0x28, 0x89, 0xc2, 0x16, 0x4c, 0x76, 0x06, 0xad,
0x8e, 0x8c, 0x29, 0x1a, 0x9b, 0x0f, 0xdd, 0x60,
0x4b, 0xb4, 0x62, 0x82, 0x9e, 0x4a, 0x63, 0x83,
0x2e, 0xd2, 0x43, 0x78, 0xc2, 0x32, 0x1f, 0x60,
0xa9, 0x8a, 0x7f, 0x0f, 0x7c, 0xa6, 0x1d, 0xe6,
0x92, 0x9e, 0x52, 0xc7, 0x7d, 0xbb, 0x35, 0x3b,
0xaa, 0x89, 0x73, 0x4c, 0xfb, 0x99, 0x54, 0x97,
0x99, 0x28, 0x6e, 0x66, 0x5b, 0xf7, 0x9b, 0x7e,
0x6d, 0x8a, 0x2f, 0xfa, 0xc3, 0x1e, 0x71, 0xb9,
0xbd, 0x8f, 0xc5, 0x63, 0x25, 0x31, 0x20, 0x02,
0xff, 0x02, 0xf0, 0xc9, 0x2c, 0xdd, 0x3a, 0x10,
0x30, 0xab, 0xe5, 0xad, 0x3d, 0x1a, 0x82, 0x77,
0x46, 0xed, 0x03, 0x38, 0xa4, 0x73, 0x6d, 0x36,
0x36, 0x33, 0x70, 0xb2, 0x63, 0x20, 0xca, 0x03,
0xbf, 0x5a, 0xf4, 0x7c, 0x35, 0xf0, 0x63, 0x1a,
0x12, 0x33, 0x12, 0x58, 0xd9, 0xa2, 0x63, 0x6b,
0x63, 0x82, 0x41, 0x65, 0x70, 0x37, 0x4b, 0x99,
0x04, 0x9f, 0xdd, 0x5e, 0x07, 0x01, 0x95, 0x9f,
0x36, 0xe8, 0xc3, 0x66, 0x2a, 0x21, 0x69, 0x68,
0x40, 0xe6, 0xbc, 0xbb, 0x85, 0x81, 0x21, 0x13,
0xe6, 0xa4, 0xcf, 0xd3, 0x67, 0xe3, 0xfd, 0x75,
0xf0, 0xdf, 0x83, 0xe0, 0xc5, 0x36, 0x09, 0xac,
0x1b, 0xd4, 0xf7, 0x2a, 0x23, 0x57, 0x1c, 0x5c,
0x0f, 0xf4, 0xcf, 0xa2, 0xcf, 0xf5, 0xbd, 0x9c,
0x69, 0x98, 0x78, 0x3a, 0x25, 0xe4, 0xfd, 0x85,
0x11, 0xcc, 0x7d, 0xef, 0xeb, 0x74, 0x60, 0xb1,
0xb7, 0xfb, 0x1f, 0x0e, 0x62, 0xff, 0xfe, 0x09,
0x0a, 0xc3, 0x80, 0x2f, 0x10, 0x49, 0x89, 0x78,
0xd2, 0x08, 0xfa, 0x89, 0x22, 0x45, 0x91, 0x21,
0xbc, 0x90, 0x3e, 0xad, 0xb3, 0x0a, 0xb4, 0x0e,
0x1c, 0xa1, 0x93, 0x92, 0xd8, 0x72, 0x07, 0x54,
0x60, 0xe7, 0x91, 0xfc, 0xd9, 0x3c, 0xe1, 0x6f,
0x08, 0xe4, 0x56, 0xf6, 0x0b, 0xb0, 0x3c, 0x39,
0x8a, 0x2d, 0x48, 0x44, 0x28, 0x13, 0xca, 0xe9,
0xf7, 0xa3, 0xb6, 0x8a, 0x5f, 0x31, 0xa9, 0x72,
0xf2, 0xde, 0x96, 0xf2, 0xb1, 0x53, 0xb1, 0x3e,
0x24, 0x57, 0xfd, 0x18, 0x45, 0x1f, 0xc5, 0x33,
0x1b, 0xa4, 0xe8, 0x21, 0xfa, 0x0e, 0xb2, 0xb9,
0xcb, 0xc7, 0x07, 0x41, 0xdd, 0x2f, 0xb6, 0x6a,
0x23, 0x18, 0xed, 0xc1, 0xef, 0xe2, 0x4b, 0xec,
0xc9, 0xba, 0xfb, 0x46, 0x43, 0x90, 0xd7, 0xb5,
0x68, 0x28, 0x31, 0x2b, 0x8d, 0xa8, 0x51, 0x63,
0xf7, 0x53, 0x99, 0x19, 0x68, 0x85, 0x66, 0x00,
0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35,
0x30, 0x39, 0x00, 0x00, 0x02, 0x3a, 0x30, 0x82,
0x02, 0x36, 0x30, 0x82, 0x01, 0xe0, 0xa0, 0x03,
0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, 0xf1,
0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
0x30, 0x81, 0xa0, 0x31, 0x0b, 0x30, 0x09, 0x06,
0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 0x52,
0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67,
0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, 0x30,
0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b,
0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d,
0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06,
0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, 0x68,
0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20,
0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31,
0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b,
0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55,
0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e,
0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65,
0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e,
0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30,
0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34,
0x31, 0x33, 0x38, 0x5a, 0x18, 0x0f, 0x32, 0x31,
0x38, 0x37, 0x31, 0x31, 0x32, 0x34, 0x30, 0x35,
0x34, 0x31, 0x33, 0x38, 0x5a, 0x30, 0x81, 0xa0,
0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
0x06, 0x13, 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30,
0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a,
0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d,
0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03,
0x55, 0x04, 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f,
0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69,
0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e,
0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f,
0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16,
0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x41,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x31, 0x30,
0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74,
0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79,
0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74,
0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41,
0x00, 0xc3, 0xe3, 0x5e, 0x41, 0xa7, 0x87, 0x11,
0x00, 0x42, 0x2a, 0xb0, 0x4b, 0xed, 0xb2, 0xe0,
0x23, 0xdb, 0xb1, 0x3d, 0x58, 0x97, 0x35, 0x60,
0x0b, 0x82, 0x59, 0xd3, 0x00, 0xea, 0xd4, 0x61,
0xb8, 0x79, 0x3f, 0xb6, 0x3c, 0x12, 0x05, 0x93,
0x2e, 0x9a, 0x59, 0x68, 0x14, 0x77, 0x3a, 0xc8,
0x50, 0x25, 0x57, 0xa4, 0x49, 0x18, 0x63, 0x41,
0xf0, 0x2d, 0x28, 0xec, 0x06, 0xfb, 0xb4, 0x9f,
0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d,
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00,
0x65, 0x6c, 0x30, 0x01, 0xc2, 0x8e, 0x3e, 0xcb,
0xb3, 0x77, 0x48, 0xe9, 0x66, 0x61, 0x9a, 0x40,
0x86, 0xaf, 0xf6, 0x03, 0xeb, 0xba, 0x6a, 0xf2,
0xfd, 0xe2, 0xaf, 0x36, 0x5e, 0x7b, 0xaa, 0x22,
0x04, 0xdd, 0x2c, 0x20, 0xc4, 0xfc, 0xdd, 0xd0,
0x82, 0x20, 0x1c, 0x3d, 0xd7, 0x9e, 0x5e, 0x5c,
0x92, 0x5a, 0x76, 0x71, 0x28, 0xf5, 0x07, 0x7d,
0xa2, 0x81, 0xba, 0x77, 0x9f, 0x2a, 0xd9, 0x44,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x6d, 0x79,
0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x1a, 0x9f,
0x5b, 0x56, 0xa0, 0x00, 0x00, 0x01, 0x99, 0x30,
0x82, 0x01, 0x95, 0x30, 0x0e, 0x06, 0x0a, 0x2b,
0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01,
0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x81, 0x29,
0xa8, 0xb6, 0x08, 0x0c, 0x85, 0x75, 0x3e, 0xdd,
0xb5, 0xe5, 0x1a, 0x87, 0x68, 0xd1, 0x90, 0x4b,
0x29, 0x31, 0xee, 0x90, 0xbc, 0x9d, 0x73, 0xa0,
0x3f, 0xe9, 0x0b, 0xa4, 0xef, 0x30, 0x9b, 0x36,
0x9a, 0xb2, 0x54, 0x77, 0x81, 0x07, 0x4b, 0xaa,
0xa5, 0x77, 0x98, 0xe1, 0xeb, 0xb5, 0x7c, 0x4e,
0x48, 0xd5, 0x08, 0xfc, 0x2c, 0x36, 0xe2, 0x65,
0x03, 0xac, 0xe5, 0xf3, 0x96, 0xb7, 0xd0, 0xb5,
0x3b, 0x92, 0xe4, 0x14, 0x05, 0x7a, 0x6a, 0x92,
0x56, 0xfe, 0x4e, 0xab, 0xd3, 0x0e, 0x32, 0x04,
0x22, 0x22, 0x74, 0x47, 0x7d, 0xec, 0x21, 0x99,
0x30, 0x31, 0x64, 0x46, 0x64, 0x9b, 0xc7, 0x13,
0xbf, 0xbe, 0xd0, 0x31, 0x49, 0xe7, 0x3c, 0xbf,
0xba, 0xb1, 0x20, 0xf9, 0x42, 0xf4, 0xa9, 0xa9,
0xe5, 0x13, 0x65, 0x32, 0xbf, 0x7c, 0xcc, 0x91,
0xd3, 0xfd, 0x24, 0x47, 0x0b, 0xe5, 0x53, 0xad,
0x50, 0x30, 0x56, 0xd1, 0xfa, 0x9c, 0x37, 0xa8,
0xc1, 0xce, 0xf6, 0x0b, 0x18, 0xaa, 0x7c, 0xab,
0xbd, 0x1f, 0xdf, 0xe4, 0x80, 0xb8, 0xa7, 0xe0,
0xad, 0x7d, 0x50, 0x74, 0xf1, 0x98, 0x78, 0xbc,
0x58, 0xb9, 0xc2, 0x52, 0xbe, 0xd2, 0x5b, 0x81,
0x94, 0x83, 0x8f, 0xb9, 0x4c, 0xee, 0x01, 0x2b,
0x5e, 0xc9, 0x6e, 0x9b, 0xf5, 0x63, 0x69, 0xe4,
0xd8, 0x0b, 0x47, 0xd8, 0xfd, 0xd8, 0xe0, 0xed,
0xa8, 0x27, 0x03, 0x74, 0x1e, 0x5d, 0x32, 0xe6,
0x5c, 0x63, 0xc2, 0xfb, 0x3f, 0xee, 0xb4, 0x13,
0xc6, 0x0e, 0x6e, 0x74, 0xe0, 0x22, 0xac, 0xce,
0x79, 0xf9, 0x43, 0x68, 0xc1, 0x03, 0x74, 0x2b,
0xe1, 0x18, 0xf8, 0x7f, 0x76, 0x9a, 0xea, 0x82,
0x3f, 0xc2, 0xa6, 0xa7, 0x4c, 0xfe, 0xae, 0x29,
0x3b, 0xc1, 0x10, 0x7c, 0xd5, 0x77, 0x17, 0x79,
0x5f, 0xcb, 0xad, 0x1f, 0xd8, 0xa1, 0xfd, 0x90,
0xe1, 0x6b, 0xb2, 0xef, 0xb9, 0x41, 0x26, 0xa4,
0x0b, 0x4f, 0xc6, 0x83, 0x05, 0x6f, 0xf0, 0x64,
0x40, 0xe1, 0x44, 0xc4, 0xf9, 0x40, 0x2b, 0x3b,
0x40, 0xdb, 0xaf, 0x35, 0xa4, 0x9b, 0x9f, 0xc4,
0x74, 0x07, 0xe5, 0x18, 0x60, 0xc5, 0xfe, 0x15,
0x0e, 0x3a, 0x25, 0x2a, 0x11, 0xee, 0x78, 0x2f,
0xb8, 0xd1, 0x6e, 0x4e, 0x3c, 0x0a, 0xb5, 0xb9,
0x40, 0x86, 0x27, 0x6d, 0x8f, 0x53, 0xb7, 0x77,
0x36, 0xec, 0x5d, 0xed, 0x32, 0x40, 0x43, 0x82,
0xc3, 0x52, 0x58, 0xc4, 0x26, 0x39, 0xf3, 0xb3,
0xad, 0x58, 0xab, 0xb7, 0xf7, 0x8e, 0x0e, 0xba,
0x8e, 0x78, 0x9d, 0xbf, 0x58, 0x34, 0xbd, 0x77,
0x73, 0xa6, 0x50, 0x55, 0x00, 0x60, 0x26, 0xbf,
0x6d, 0xb4, 0x98, 0x8a, 0x18, 0x83, 0x89, 0xf8,
0xcd, 0x0d, 0x49, 0x06, 0xae, 0x51, 0x6e, 0xaf,
0xbd, 0xe2, 0x07, 0x13, 0xd8, 0x64, 0xcc, 0xbf,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e,
0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x34, 0x30,
0x82, 0x02, 0x30, 0x30, 0x82, 0x01, 0xda, 0xa0,
0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59,
0xf2, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
0x00, 0x30, 0x81, 0x9d, 0x31, 0x0b, 0x30, 0x09,
0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b,
0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e,
0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14,
0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61,
0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18,
0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54,
0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79,
0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04,
0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x31,
0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03,
0x13, 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74,
0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d,
0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65,
0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, 0x38, 0x30,
0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x35, 0x34,
0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37,
0x31, 0x31, 0x32, 0x33, 0x30, 0x35, 0x34, 0x35,
0x34, 0x30, 0x5a, 0x30, 0x81, 0x9d, 0x31, 0x0b,
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06,
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79,
0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f,
0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04,
0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67,
0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a,
0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74,
0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65,
0x63, 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03,
0x55, 0x04, 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72,
0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55,
0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e,
0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65,
0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e,
0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06,
0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30,
0x48, 0x02, 0x41, 0x00, 0x95, 0xb3, 0x47, 0x17,
0x95, 0x0f, 0x57, 0xcf, 0x66, 0x72, 0x0a, 0x7e,
0x5b, 0x54, 0xea, 0x8c, 0x6f, 0x79, 0xde, 0x94,
0xac, 0x0b, 0x5a, 0xd4, 0xd6, 0x1b, 0x58, 0x12,
0x1a, 0x16, 0x3d, 0xfe, 0xdf, 0xa5, 0x2b, 0x86,
0xbc, 0x64, 0xd4, 0x80, 0x1e, 0x3f, 0xf9, 0xe2,
0x04, 0x03, 0x79, 0x9b, 0xc1, 0x5c, 0xf0, 0xf1,
0xf3, 0xf1, 0xe3, 0xbf, 0x3f, 0xc0, 0x1f, 0xdd,
0xdb, 0xc0, 0x5b, 0x21, 0x02, 0x03, 0x01, 0x00,
0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
0x03, 0x41, 0x00, 0x02, 0xd7, 0xdd, 0xbd, 0x0c,
0x8e, 0x21, 0x20, 0xef, 0x9e, 0x4f, 0x1f, 0xf5,
0x49, 0xf1, 0xae, 0x58, 0x9b, 0x94, 0x3a, 0x1f,
0x70, 0x33, 0xf0, 0x9b, 0xbb, 0xe9, 0xc0, 0xf3,
0x72, 0xcb, 0xde, 0xb6, 0x56, 0x72, 0xcc, 0x1c,
0xf0, 0xd6, 0x5a, 0x2a, 0xbc, 0xa1, 0x7e, 0x23,
0x83, 0xe9, 0xe7, 0xcf, 0x9e, 0xa5, 0xf9, 0xcc,
0xc2, 0x61, 0xf4, 0xdb, 0x40, 0x93, 0x1d, 0x63,
0x8a, 0x50, 0x4c, 0x11, 0x39, 0xb1, 0x91, 0xc1,
0xe6, 0x9d, 0xd9, 0x1a, 0x62, 0x1b, 0xb8, 0xd3,
0xd6, 0x9a, 0x6d, 0xb9, 0x8e, 0x15, 0x51 };
public static InputStream asInputStream() {
byte[] data = new byte[DATA.length];
for (int i = 0; i < data.length; i ++) {
data[i] = (byte) DATA[i];
}
return new ByteArrayInputStream(data);
}
public static char[] getCertificatePassword() {
return "secret".toCharArray();
}
public static char[] getKeyStorePassword() {
return "secret".toCharArray();
}
private SecureChatKeyStore() {
// Unused
}
}

View File

@ -20,15 +20,19 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.example.telnet.TelnetServer;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* Simple SSL chat server modified from {@link TelnetServer}.
*/
public class SecureChatServer {
private final SslContext sslCtx;
private final int port;
public SecureChatServer(int port) {
public SecureChatServer(SslContext sslCtx, int port) {
this.sslCtx = sslCtx;
this.port = port;
}
@ -39,7 +43,7 @@ public class SecureChatServer {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new SecureChatServerInitializer());
.childHandler(new SecureChatServerInitializer(sslCtx));
b.bind(port).sync().channel().closeFuture().sync();
} finally {
@ -55,6 +59,11 @@ public class SecureChatServer {
} else {
port = 8443;
}
new SecureChatServer(port).run();
// Configure SSL context.
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
new SecureChatServer(sslCtx, port).run();
}
}

View File

@ -22,15 +22,19 @@ import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine;
import io.netty.handler.ssl.SslContext;
/**
* Creates a newly configured {@link ChannelPipeline} for a new channel.
*/
public class SecureChatServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public SecureChatServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
@ -43,12 +47,7 @@ public class SecureChatServerInitializer extends ChannelInitializer<SocketChanne
//
// Read SecureChatSslContextFactory
// if you need client certificate authentication.
SSLEngine engine =
SecureChatSslContextFactory.getServerContext().createSSLEngine();
engine.setUseClientMode(false);
pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
// On top of the SSL handler, add the text line codec.
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(

View File

@ -1,108 +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 io.netty.handler.ssl.SslHandler;
import io.netty.util.internal.SystemPropertyUtil;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
/**
* Creates a bogus {@link SSLContext}. A client-side context created by this
* factory accepts any certificate even if it is invalid. A server-side context
* created by this factory sends a bogus certificate defined in {@link SecureChatKeyStore}.
* <p>
* You will have to create your context differently in a real world application.
*
* <h3>Client Certificate Authentication</h3>
*
* To enable client certificate authentication:
* <ul>
* <li>Enable client authentication on the server side by calling
* {@link SSLEngine#setNeedClientAuth(boolean)} before creating
* {@link SslHandler}.</li>
* <li>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)}.</li>
* <li>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.</li>
* </ul>
*/
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
}
}

View File

@ -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
}
}

View File

@ -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
* <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more information.
* <p>
* 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:
* <pre>
* mvn exec:exec -Pspdy-client
* ./run-example spdy-client
* </pre>
*/
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();

View File

@ -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<SocketChannel> {
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<SocketChannel> {
@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));

View File

@ -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.
* <p>
* This NPN service provider negotiates using SPDY.
* <p>
* To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. 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 <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty documentation</a>
*/
public class SpdyClientProvider implements ClientProvider {
private String selectedProtocol;
@Override
public String selectProtocol(List<String> 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();
}
}

View File

@ -33,11 +33,13 @@
* After that, you can run {@link io.netty.example.spdy.client.SpdyClient}, also settings the JVM parameter
* mentioned above.
* <p>
* 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:
* <pre>
* mvn exec:exec -Pspdy-server
* ./run-example spdy-server
* </pre>
* Then start the client in a different terminal window:
* mvn exec:exec -Pspdy-client
* <pre>
* ./run-example spdy-client
* </pre>
*/
package io.netty.example.spdy.client;

View File

@ -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;

View File

@ -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 <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more
* information.
* <p>
* 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:
* <pre>
* mvn exec:exec -Pspdy-server
* ./run-example spdy-server
* </pre>
* <p>
* 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();
}
}

View File

@ -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<Object> {
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
SslHandler h = ctx.pipeline().get(SslHandler.class);
System.err.println(h.engine().getSession().getProtocol());
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;

View File

@ -18,28 +18,23 @@ package io.netty.example.spdy.server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.example.securechat.SecureChatSslContextFactory;
import io.netty.handler.ssl.SslHandler;
import org.eclipse.jetty.npn.NextProtoNego;
import javax.net.ssl.SSLEngine;
import io.netty.handler.ssl.SslContext;
/**
* Sets up the Netty pipeline
*/
public class SpdyServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public SpdyServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();
engine.setUseClientMode(false);
p.addLast("ssl", new SslHandler(engine));
// Setup NextProtoNego with our server provider
NextProtoNego.put(engine, new SpdyServerProvider());
NextProtoNego.debug = true;
p.addLast("ssl", sslCtx.newHandler(ch.alloc()));
// Negotiates with the browser if SPDY or HTTP is going to be used
p.addLast("handler", new SpdyOrHttpHandler());
}

View File

@ -1,63 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.example.spdy.server;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser;
import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
import java.util.Arrays;
import java.util.List;
/**
* The Jetty project provides an implementation of the Transport Layer Security (TLS) extension for Next
* Protocol Negotiation (NPN) for OpenJDK 7 or greater. NPN allows the application layer to negotiate which
* protocol to use over the secure connection.
* <p>
* This NPN service provider negotiates using SPDY.
* <p>
* To enable NPN support, start the JVM with: {@code java -Xbootclasspath/p:<path_to_npn_boot_jar> ...}. 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 <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty documentation</a>
*/
public class SpdyServerProvider implements ServerProvider {
private String selectedProtocol;
@Override
public void unsupported() {
// if unsupported, default to http/1.1
selectedProtocol = "http/1.1";
}
@Override
public List<String> protocols() {
return Arrays.asList("spdy/3.1", "http/1.1");
}
@Override
public void protocolSelected(String protocol) {
selectedProtocol = protocol;
}
public SpdyOrHttpChooser.SelectedProtocol getSelectedProtocol() {
if (selectedProtocol == null) {
return SpdyOrHttpChooser.SelectedProtocol.UNKNOWN;
}
return SpdyOrHttpChooser.SelectedProtocol.protocol(selectedProtocol);
}
}

View File

@ -28,9 +28,9 @@
* See <a href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty docs</a> for more
* information.
* <p>
* 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:
* <pre>
* mvn exec:exec -Pspdy-server
* ./run-example spdy-server
* </pre>
* <p>
* Once started, you can test the server with your

View File

@ -44,6 +44,28 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-tcnative</artifactId>
<classifier>${os.detected.classifier}</classifier>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -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 <a href="http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04">NPN
* (Next Protocol Negotiation)</a> or <a href="https://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-05">ALPN
* (Application Layer Protocol Negotiation)</a>.
*/
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<String> protocols) throws Exception;
}

View File

@ -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<String> 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<String> 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);
}
}
}

View File

@ -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<String> 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<String> protocols = new ArrayList<String>();
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<String> ciphers = new ArrayList<String>();
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<String> 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<String> unmodifiableCipherSuites;
JdkSslContext(Iterable<String> 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<String> 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<String> ciphers) {
if (ciphers == null) {
return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]);
} else {
List<String> newCiphers = new ArrayList<String>();
for (String c: ciphers) {
if (c == null) {
break;
}
newCiphers.add(c);
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}
}

View File

@ -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<String> 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<String> ciphers, Iterable<String> 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<String> list = new ArrayList<String>();
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<Certificate> certChain = new ArrayList<Certificate>();
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<String> 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);
}
}
}

View File

@ -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<String> 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<String> 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<String> 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);
}
}

View File

@ -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();
}
}

View File

@ -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 <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> 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
* <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
* are available.
*/
public static boolean isAvailable() {
return UNAVAILABILITY_CAUSE == null;
}
/**
* Ensure that <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> 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
* <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support.
*
* @return the cause if unavailable. {@code null} if available.
*/
public static Throwable unavailabilityCause() {
return UNAVAILABILITY_CAUSE;
}
private OpenSsl() { }
}

View File

@ -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
* <a href="https://www.openssl.org/docs/crypto/BIO_s_bio.html#EXAMPLE">OpenSSL BIO abstractions</a>.
*/
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<OpenSslEngine> 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;
}
}

View File

@ -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<String> DEFAULT_CIPHERS;
static {
List<String> ciphers = new ArrayList<String>();
// 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<String> ciphers = new ArrayList<String>();
private final List<String> unmodifiableCiphers = Collections.unmodifiableList(ciphers);
private final long sessionCacheSize;
private final long sessionTimeout;
private final List<String> nextProtocols = new ArrayList<String>();
private final List<String> 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<String> ciphers, Iterable<String> 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<String> cipherSuites() {
return unmodifiableCiphers;
}
@Override
public long sessionCacheSize() {
return sessionCacheSize;
}
@Override
public long sessionTimeout() {
return sessionTimeout;
}
@Override
public ApplicationProtocolSelector nextProtocolSelector() {
return null;
}
@Override
public List<String> 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);
}
}
}

View File

@ -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 <a href="https://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html"><code>SSL_CTX_sess_number</code></a>
*/
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);
}
}

View File

@ -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<ByteBuf> certs = new ArrayList<ByteBuf>();
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() { }
}

View File

@ -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}.
*
* <h3>Making your server support SSL/TLS</h3>
* <pre>
* // In your {@link ChannelInitializer}:
* {@link ChannelPipeline} p = channel.pipeline();
* {@link SslContext} sslCtx = {@link #newServerContext(File, File) SslContext.newServerContext(...)};
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator) sslCtx.newEngine(channel.alloc())});
* ...
* </pre>
*
* <h3>Making your client support SSL/TLS</h3>
* <pre>
* // In your {@link ChannelInitializer}:
* {@link ChannelPipeline} p = channel.pipeline();
* {@link SslContext} sslCtx = {@link #newClientContext(File) SslContext.newClientContext(...)};
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator, String, int) sslCtx.newEngine(channel.alloc(), host, port)});
* ...
* </pre>
*/
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<String> ciphers, Iterable<String> 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<String> ciphers, Iterable<String> 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<String> 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<String> 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<String> list = new ArrayList<String>();
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<String> list) {
return new ApplicationProtocolSelector() {
@Override
public String selectProtocol(List<String> 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<String> nextProtocols) {
if (nextProtocols == null) {
throw new NullPointerException("nextProtocols");
}
final List<String> list = new ArrayList<String>();
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<String> 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<String> 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);
}
}

View File

@ -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.
* <p>
* If this flag is {@code false}, we allocate a smaller output buffer.
* </p>
*/
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<Object> out) throws SSLException {
// Keeps the list of the length of every SSL record in the input buffer.
int[] recordLengths = null;
int nRecords = 0;
final int startOffset = in.readerIndex();
final int endOffset = in.writerIndex();
int offset = startOffset;
int totalLength = 0;
// If we calculated the length of the current SSL record before, use that information.
if (packetLength > 0) {
if (endOffset - startOffset < packetLength) {
return;
} else {
recordLengths = new int[4];
recordLengths[0] = packetLength;
nRecords = 1;
offset += packetLength;
totalLength = packetLength;
packetLength = 0;
}
}
boolean nonSslRecord = false;
for (;;) {
while (totalLength < OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
final int readableBytes = endOffset - offset;
if (readableBytes < 5) {
break;
@ -773,21 +796,18 @@ public class SslHandler extends ByteToMessageDecoder {
break;
}
// We have a whole packet.
// Remember the length of the current packet.
if (recordLengths == null) {
recordLengths = new int[4];
int newTotalLength = totalLength + packetLength;
if (newTotalLength > OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
// Don't read too much.
break;
}
if (nRecords == recordLengths.length) {
recordLengths = Arrays.copyOf(recordLengths, recordLengths.length << 1);
}
recordLengths[nRecords ++] = packetLength;
// We have a whole packet.
// Increment the offset to handle the next packet.
offset += packetLength;
totalLength = newTotalLength;
}
final int totalLength = offset - startOffset;
if (totalLength > 0) {
// The buffer contains one or more full SSL records.
// Slice out the whole packet so unwrap will only be called with complete packets.
@ -799,9 +819,11 @@ public class SslHandler extends ByteToMessageDecoder {
// 4) unwrapLater(...) calls decode(...)
//
// See https://github.com/netty/netty/issues/1534
in.skipBytes(totalLength);
ByteBuffer buffer = in.nioBuffer(startOffset, totalLength);
unwrapMultiple(ctx, buffer, totalLength, recordLengths, nRecords, out);
final ByteBuffer inNetBuf = in.nioBuffer(startOffset, totalLength);
unwrap(ctx, inNetBuf, totalLength);
assert !inNetBuf.hasRemaining() || engine.isInboundDone();
}
if (nonSslRecord) {
@ -826,52 +848,34 @@ public class SslHandler extends ByteToMessageDecoder {
/**
* Calls {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} with an empty buffer to handle handshakes, etc.
*/
private void unwrapNonApp(ChannelHandlerContext ctx) throws SSLException {
try {
unwrapSingle(ctx, Unpooled.EMPTY_BUFFER.nioBuffer(), 0);
} finally {
ByteBuf decodeOut = this.decodeOut;
if (decodeOut != null && decodeOut.isReadable()) {
this.decodeOut = null;
ctx.fireChannelRead(decodeOut);
}
}
private void unwrapNonAppData(ChannelHandlerContext ctx) throws SSLException {
unwrap(ctx, Unpooled.EMPTY_BUFFER.nioBuffer(), 0);
}
/**
* Unwraps multiple inbound SSL records.
* Unwraps inbound SSL records.
*/
private void unwrapMultiple(
ChannelHandlerContext ctx, ByteBuffer packet, int totalLength,
int[] recordLengths, int nRecords, List<Object> out) throws SSLException {
for (int i = 0; i < nRecords; i ++) {
packet.limit(packet.position() + recordLengths[i]);
try {
unwrapSingle(ctx, packet, totalLength);
assert !packet.hasRemaining();
} finally {
ByteBuf decodeOut = this.decodeOut;
if (decodeOut != null && decodeOut.isReadable()) {
this.decodeOut = null;
out.add(decodeOut);
}
}
}
}
/**
* Unwraps a single SSL record.
*/
private void unwrapSingle(
private void unwrap(
ChannelHandlerContext ctx, ByteBuffer packet, int initialOutAppBufCapacity) throws SSLException {
boolean wrapLater = false;
try {
for (;;) {
if (decodeOut == null) {
decodeOut = ctx.alloc().buffer(initialOutAppBufCapacity);
// If SSLEngine expects a heap buffer for unwrapping, do the conversion.
final ByteBuffer oldPacket;
final ByteBuf newPacket;
final int oldPos = packet.position();
if (wantsInboundHeapBuffer && packet.isDirect()) {
newPacket = ctx.alloc().heapBuffer(packet.limit() - oldPos);
newPacket.writeBytes(packet);
oldPacket = packet;
packet = newPacket.nioBuffer();
} else {
oldPacket = null;
newPacket = null;
}
boolean wrapLater = false;
ByteBuf decodeOut = allocate(ctx, initialOutAppBufCapacity);
try {
for (;;) {
final SSLEngineResult result = unwrap(engine, packet, decodeOut);
final Status status = result.getStatus();
final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
@ -926,6 +930,19 @@ public class SslHandler extends ByteToMessageDecoder {
} catch (SSLException e) {
setHandshakeFailure(e);
throw e;
} finally {
// If we converted packet into a heap buffer at the beginning of this method,
// we should synchronize the position of the original buffer.
if (newPacket != null) {
oldPacket.position(oldPos + packet.position());
newPacket.release();
}
if (decodeOut.isReadable()) {
ctx.fireChannelRead(decodeOut);
} else {
decodeOut.release();
}
}
}
@ -985,7 +1002,16 @@ public class SslHandler extends ByteToMessageDecoder {
* Notify all the handshake futures about the successfully handshake
*/
private void setHandshakeSuccess() {
// Work around the JVM crash which occurs when a cipher suite with GCM enabled.
final String cipherSuite = String.valueOf(engine.getSession().getCipherSuite());
if (!wantsDirectBuffer && (cipherSuite.contains("_GCM_") || cipherSuite.contains("-GCM-"))) {
wantsInboundHeapBuffer = true;
}
if (handshakePromise.trySuccess(ctx.channel())) {
if (logger.isDebugEnabled()) {
logger.debug(ctx.channel() + " HANDSHAKEN: " + engine.getSession().getCipherSuite());
}
ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS);
}
}
@ -1050,7 +1076,7 @@ public class SslHandler extends ByteToMessageDecoder {
public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
if (ctx.channel().isActive()) {
if (ctx.channel().isActive() && engine.getUseClientMode()) {
// channelActive() event has been fired already, which means this.channelActive() will
// not be invoked. We have to initialize here instead.
handshake();
@ -1114,6 +1140,7 @@ public class SslHandler extends ByteToMessageDecoder {
}
ctx.fireChannelActive();
}
private void safeClose(
final ChannelHandlerContext ctx, ChannelFuture flushFuture,
final ChannelPromise promise) {
@ -1153,6 +1180,33 @@ public class SslHandler extends ByteToMessageDecoder {
});
}
/**
* Always prefer a direct buffer when it's pooled, so that we reduce the number of memory copies
* in {@link OpenSslEngine}.
*/
private ByteBuf allocate(ChannelHandlerContext ctx, int capacity) {
ByteBufAllocator alloc = ctx.alloc();
if (wantsDirectBuffer) {
return alloc.directBuffer(capacity);
} else {
return alloc.buffer(capacity);
}
}
/**
* Allocates an outbound network buffer for {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} which can encrypt
* the specified amount of pending bytes.
*/
private ByteBuf allocateOutNetBuf(ChannelHandlerContext ctx, int pendingBytes) {
if (wantsLargeOutboundNetworkBuffer) {
return allocate(ctx, maxPacketBufferSize);
} else {
return allocate(ctx, Math.min(
pendingBytes + OpenSslEngine.MAX_ENCRYPTION_OVERHEAD_LENGTH,
maxPacketBufferSize));
}
}
private final class LazyChannelPromise extends DefaultPromise<Channel> {
@Override

View File

@ -0,0 +1,31 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
/**
* An enumeration of SSL/TLS protocol providers.
*/
public enum SslProvider {
/**
* JDK's default implementation.
*/
JDK,
/**
* OpenSSL-based implementation.
*/
OPENSSL
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl.util;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import static io.netty.handler.ssl.util.SelfSignedCertificate.*;
/**
* Generates a self-signed certificate using <a href="http://www.bouncycastle.org/">Bouncy Castle</a>.
*/
final class BouncyCastleSelfSignedCertGenerator {
private static final Provider PROVIDER = new BouncyCastleProvider();
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random) throws Exception {
PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate.
X500Name owner = new X500Name("CN=" + fqdn);
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
owner, new BigInteger(64, random), NOT_BEFORE, NOT_AFTER, owner, keypair.getPublic());
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(key);
X509CertificateHolder certHolder = builder.build(signer);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder);
cert.verify(keypair.getPublic());
return newSelfSignedCertificate(fqdn, key, cert);
}
private BouncyCastleSelfSignedCertGenerator() { }
}

View File

@ -0,0 +1,207 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl.util;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.EmptyArrays;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* An {@link TrustManagerFactory} that trusts an X.509 certificate whose SHA1 checksum matches.
* <p>
* <strong>NOTE:</strong>
* Never use this {@link TrustManagerFactory} in production unless you are not sure what you are exactly doing with it.
* </p><p>
* The SHA1 checksum of an X.509 certificate is calculated from its DER encoded format. You can get the fingerprint of
* an X.509 certificate using the {@code openssl} command. For example:
* <pre>
* $ openssl x509 -fingerprint -sha1 -in my_certificate.crt
* SHA1 Fingerprint=4E:85:10:55:BC:7B:12:08:D1:EA:0A:12:C9:72:EE:F3:AA:B2:C7:CB
* -----BEGIN CERTIFICATE-----
* MIIBqjCCAROgAwIBAgIJALiT3Nvp0kvmMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
* BAMTC2V4YW1wbGUuY29tMCAXDTcwMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5
* WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
* gYkCgYEAnadvODG0QCiHhaFZlLHtr5gLIkDQS8ErZ//KfqeCHTC/KJsl3xYFk0zG
* aCv2FcmkOlokm77qV8qOW2DZdND7WuYzX6nLVuLb+GYxZ7b45iMAbAajvGh8jc9U
* o07fUIahGqTDAIAGCWsoLUOQ9nMzO/8GRHcXJAeQ2MGY2VpCcv0CAwEAATANBgkq
* hkiG9w0BAQUFAAOBgQBpRCnmjmNM0D7yrpkUJpBTNiqinhKLbeOvPWm+YmdInUUs
* LoMu0mZ1IANemLwqbwJJ76fknngeB+YuVAj46SurvVCV6ekwHcbgpW1u063IRwKk
* tQhOBO0HQxldUS4+4MYv/kuvnKkbjfgh5qfWw89Kx4kD+cycpP4yPtgDGk8ZMA==
* -----END CERTIFICATE-----
* </pre>
* </p>
*/
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<MessageDigest> tlmd = new ThreadLocal<MessageDigest>() {
@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<String> 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<byte[]> list = new ArrayList<byte[]>();
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<String> fingerprints) {
if (fingerprints == null) {
throw new NullPointerException("fingerprints");
}
List<byte[]> list = new ArrayList<byte[]>();
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 };
}
}

View File

@ -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.
* <p>
* <strong>NOTE:</strong>
* Never use this {@link javax.net.ssl.TrustManagerFactory} in production.
* It is purely for testing purposes, and thus it is very insecure.
* </p>
*/
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 };
}
}

View File

@ -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() { }
}

View File

@ -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.
* <p>
* <strong>NOTE:</strong>
* 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.
* </p><p>
* 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()}.
* </p><p>
* 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 <a href="http://www.bouncycastle.org/">Bouncy Castle</a> as a fallback.
* </p>
*/
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);
}
}
}

View File

@ -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<SimpleTrustManagerFactorySpi> CURRENT_SPI =
new ThreadLocal<SimpleTrustManagerFactorySpi>() {
@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();
}
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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.

241
pom.xml
View File

@ -71,24 +71,13 @@
<profile>
<id>leak</id>
<properties>
<test.jvm.argLine>
-server
-dsa -da -ea:io.netty...
-XX:+AggressiveOpts
-XX:+TieredCompilation
-XX:+UseBiasedLocking
-XX:+UseFastAccessorMethods
-XX:+OptimizeStringConcat
-XX:+HeapDumpOnOutOfMemoryError
-Dio.netty.leakDetectionLevel=3
-verbose:gc
</test.jvm.argLine>
<argLine.leak>-Dio.netty.leakDetectionLevel=paranoid</argLine.leak>
</properties>
</profile>
<profile>
<id>coverage</id>
<properties>
<test.jvm.argLine.coverage>${jacoco.argLine}</test.jvm.argLine.coverage>
<argLine.coverage>${jacoco.argLine}</argLine.coverage>
</properties>
<build>
<plugins>
@ -111,14 +100,16 @@
</plugins>
</build>
</profile>
<!-- Our Javadoc has poor enough quality to fail the build thanks to JDK8 javadoc which got more strict. -->
<profile>
<id>jdk8</id>
<activation>
<jdk>[1.8,)</jdk>
</activation>
<properties>
<!-- Our Javadoc has poor enough quality to fail the build thanks to JDK8 javadoc which got more strict. -->
<maven.javadoc.failOnError>false</maven.javadoc.failOnError>
<!-- npn-boot does not work with JDK 8 -->
<argLine.bootcp>-D_</argLine.bootcp>
</properties>
</profile>
<profile>
@ -179,14 +170,152 @@
</plugins>
</build>
</profile>
<!--
Profiles that assigns proper Jetty npn-boot version.
See: http://www.eclipse.org/jetty/documentation/current/npn-chapter.html#npn-versions
-->
<profile>
<id>npn-7u9</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_9</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.3.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u10</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_10</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.3.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u11</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_11</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.3.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u13</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_13</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.4.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u15</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_15</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.5.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u17</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_17</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.5.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u21</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_21</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.5.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u25</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_25</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.5.v20130313</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u40</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_40</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u45</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_45</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
</properties>
</profile>
<profile>
<id>npn-7u51</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_51</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
</properties>
</profile>
</profiles>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jboss.marshalling.version>1.3.18.GA</jboss.marshalling.version>
<test.jvm.argLine.coverage></test.jvm.argLine.coverage> <!-- Set when 'coverage' profile is active -->
<test.jvm.argLine>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.npn.path>${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${jetty.npn.version}/npn-boot-${jetty.npn.version}.jar</jetty.npn.path>
<argLine.common>
-server
-dsa -da -ea:io.netty...
-XX:+AggressiveOpts
@ -196,7 +325,10 @@
-XX:+OptimizeStringConcat
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
</test.jvm.argLine>
</argLine.common>
<argLine.bootcp>-Xbootclasspath/p:${jetty.npn.path}</argLine.bootcp>
<argLine.leak>-D_</argLine.leak> <!-- Set when 'leak' profile is active -->
<argLine.coverage>-D_</argLine.coverage> <!-- Set when 'coverage' profile is active -->
</properties>
<modules>
@ -239,18 +371,48 @@
<optional>true</optional>
</dependency>
<!-- SPDY Example - completely optional -->
<!-- SPDY and HTTP/2 - completely optional -->
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
<version>1.1.0.v20120525</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<version>${jetty.npn.version}</version>
</dependency>
<!-- Google Protocol Buffers - completely optional -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<!-- Our own Tomcat Native fork - completely optional, used for acclerating SSL with OpenSSL. -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.30.Fork1</version>
<classifier>${os.detected.classifier}</classifier>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!--
Bouncy Castle - completely optional, only needed when:
- you generate a temporary self-signed certificate using SelfSignedCertificate, and
- you don't use the JDK which doesn't provide sun.security.x509 package.
-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.50</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
@ -414,7 +576,7 @@
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.1.2</version>
<version>1.2.2.Final</version>
</extension>
</extensions>
@ -465,6 +627,9 @@
-->
<meminitial>256m</meminitial>
<maxmem>1024m</maxmem>
<excludes>
<exclude>**/package-info.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
@ -491,6 +656,22 @@
<ignore>java.nio.channels.MembershipKey</ignore>
<ignore>java.net.StandardProtocolFamily</ignore>
<ignore>java.nio.channels.spi.SelectorProvider</ignore>
<!-- Self-signed certificate generation -->
<ignore>sun.security.x509.AlgorithmId</ignore>
<ignore>sun.security.x509.CertificateAlgorithmId</ignore>
<ignore>sun.security.x509.CertificateIssuerName</ignore>
<ignore>sun.security.x509.CertificateSerialNumber</ignore>
<ignore>sun.security.x509.CertificateSubjectName</ignore>
<ignore>sun.security.x509.CertificateValidity</ignore>
<ignore>sun.security.x509.CertificateVersion</ignore>
<ignore>sun.security.x509.CertificateX509Key</ignore>
<ignore>sun.security.x509.X500Name</ignore>
<ignore>sun.security.x509.X509CertInfo</ignore>
<ignore>sun.security.x509.X509CertImpl</ignore>
<!-- SSLSession implelementation -->
<ignore>javax.net.ssl.SSLEngine</ignore>
</ignores>
</configuration>
<executions>
@ -530,6 +711,24 @@
</dependency>
</dependencies>
</plugin>
<!-- Download the npn-boot.jar in advance to add it to the boot classpath. -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>get-npn-boot</id>
<phase>validate</phase>
<goals>
<goal>get</goal>
</goals>
<configuration>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<version>${jetty.npn.version}</version>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
@ -542,7 +741,7 @@
<exclude>**/TestUtil*</exclude>
</excludes>
<runOrder>random</runOrder>
<argLine>${test.jvm.argLine.coverage} ${test.jvm.argLine}</argLine>
<argLine>${argLine.common} ${argLine.bootcp} ${argLine.leak} ${argLine.coverage}</argLine>
</configuration>
</plugin>
<!-- always produce osgi bundles -->
@ -870,7 +1069,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<version>1.3</version>
</plugin>
<plugin>
<groupId>org.fusesource.hawtjni</groupId>

50
run-example.sh Executable file
View File

@ -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<name>[=<value>] ...] <example-name>" >&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"

View File

@ -69,6 +69,12 @@
<artifactId>netty-transport-udt</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-tcnative</artifactId>
<classifier>${os.detected.classifier}</classifier>
<optional>true</optional>
</dependency>
</dependencies>
<properties>

View File

@ -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<Object[]> data() {
List<Object[]> params = new ArrayList<Object[]>();
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<Object[]> data() throws Exception {
List<SslContext> serverContexts = new ArrayList<SslContext>();
serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
List<SslContext> clientContexts = new ArrayList<SslContext>();
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<Object[]> params = new ArrayList<Object[]>();
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<SocketChannel>() {
@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<SocketChannel>() {
@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<ByteBuf> {
private static class EchoHandler extends SimpleChannelInboundHandler<ByteBuf> {
volatile Channel channel;
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
volatile int counter;

View File

@ -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<Object[]> data() throws Exception {
List<SslContext> serverContexts = new ArrayList<SslContext>();
serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
List<SslContext> clientContexts = new ArrayList<SslContext>();
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<Object[]> params = new ArrayList<Object[]>();
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<SocketChannel>() {
@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<SocketChannel>() {
@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);
}
});

View File

@ -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<Object[]> data() throws Exception {
List<SslContext> serverContexts = new ArrayList<SslContext>();
serverContexts.add(new JdkSslServerContext(CERT_FILE, KEY_FILE));
List<SslContext> clientContexts = new ArrayList<SslContext>();
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<Object[]> params = new ArrayList<Object[]>();
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<String> {
private static class StartTlsClientHandler extends SimpleChannelInboundHandler<String> {
private final SslHandler sslHandler;
private final boolean autoRead;
private Future<Channel> handshakeFuture;
@ -207,7 +274,7 @@ public class SocketStartTlsTest extends AbstractSocketTest {
}
}
private class StartTlsServerHandler extends SimpleChannelInboundHandler<String> {
private static class StartTlsServerHandler extends SimpleChannelInboundHandler<String> {
private final SslHandler sslHandler;
private final boolean autoRead;
volatile Channel channel;

View File

@ -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:
* <pre>
* keytool -genkey -alias bogus -keysize 2048 -validity 36500
* -keyalg RSA -dname "CN=bogus"
* -keypass secret -storepass secret
* -keystore cert.jks
* </pre>
*/
final class BogusKeyStore {
private static final short[] DATA = {
0xfe, 0xed, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x00, 0x00, 0x01, 0x1a, 0x9f, 0x57, 0xa5,
0x27, 0x00, 0x00, 0x01, 0x9a, 0x30, 0x82, 0x01,
0x96, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01,
0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05,
0x00, 0x04, 0x82, 0x01, 0x82, 0x48, 0x6d, 0xcf,
0x16, 0xb5, 0x50, 0x95, 0x36, 0xbf, 0x47, 0x27,
0x50, 0x58, 0x0d, 0xa2, 0x52, 0x7e, 0x25, 0xab,
0x14, 0x1a, 0x26, 0x5e, 0x2d, 0x8a, 0x23, 0x90,
0x60, 0x7f, 0x12, 0x20, 0x56, 0xd1, 0x43, 0xa2,
0x6b, 0x47, 0x5d, 0xed, 0x9d, 0xd4, 0xe5, 0x83,
0x28, 0x89, 0xc2, 0x16, 0x4c, 0x76, 0x06, 0xad,
0x8e, 0x8c, 0x29, 0x1a, 0x9b, 0x0f, 0xdd, 0x60,
0x4b, 0xb4, 0x62, 0x82, 0x9e, 0x4a, 0x63, 0x83,
0x2e, 0xd2, 0x43, 0x78, 0xc2, 0x32, 0x1f, 0x60,
0xa9, 0x8a, 0x7f, 0x0f, 0x7c, 0xa6, 0x1d, 0xe6,
0x92, 0x9e, 0x52, 0xc7, 0x7d, 0xbb, 0x35, 0x3b,
0xaa, 0x89, 0x73, 0x4c, 0xfb, 0x99, 0x54, 0x97,
0x99, 0x28, 0x6e, 0x66, 0x5b, 0xf7, 0x9b, 0x7e,
0x6d, 0x8a, 0x2f, 0xfa, 0xc3, 0x1e, 0x71, 0xb9,
0xbd, 0x8f, 0xc5, 0x63, 0x25, 0x31, 0x20, 0x02,
0xff, 0x02, 0xf0, 0xc9, 0x2c, 0xdd, 0x3a, 0x10,
0x30, 0xab, 0xe5, 0xad, 0x3d, 0x1a, 0x82, 0x77,
0x46, 0xed, 0x03, 0x38, 0xa4, 0x73, 0x6d, 0x36,
0x36, 0x33, 0x70, 0xb2, 0x63, 0x20, 0xca, 0x03,
0xbf, 0x5a, 0xf4, 0x7c, 0x35, 0xf0, 0x63, 0x1a,
0x12, 0x33, 0x12, 0x58, 0xd9, 0xa2, 0x63, 0x6b,
0x63, 0x82, 0x41, 0x65, 0x70, 0x37, 0x4b, 0x99,
0x04, 0x9f, 0xdd, 0x5e, 0x07, 0x01, 0x95, 0x9f,
0x36, 0xe8, 0xc3, 0x66, 0x2a, 0x21, 0x69, 0x68,
0x40, 0xe6, 0xbc, 0xbb, 0x85, 0x81, 0x21, 0x13,
0xe6, 0xa4, 0xcf, 0xd3, 0x67, 0xe3, 0xfd, 0x75,
0xf0, 0xdf, 0x83, 0xe0, 0xc5, 0x36, 0x09, 0xac,
0x1b, 0xd4, 0xf7, 0x2a, 0x23, 0x57, 0x1c, 0x5c,
0x0f, 0xf4, 0xcf, 0xa2, 0xcf, 0xf5, 0xbd, 0x9c,
0x69, 0x98, 0x78, 0x3a, 0x25, 0xe4, 0xfd, 0x85,
0x11, 0xcc, 0x7d, 0xef, 0xeb, 0x74, 0x60, 0xb1,
0xb7, 0xfb, 0x1f, 0x0e, 0x62, 0xff, 0xfe, 0x09,
0x0a, 0xc3, 0x80, 0x2f, 0x10, 0x49, 0x89, 0x78,
0xd2, 0x08, 0xfa, 0x89, 0x22, 0x45, 0x91, 0x21,
0xbc, 0x90, 0x3e, 0xad, 0xb3, 0x0a, 0xb4, 0x0e,
0x1c, 0xa1, 0x93, 0x92, 0xd8, 0x72, 0x07, 0x54,
0x60, 0xe7, 0x91, 0xfc, 0xd9, 0x3c, 0xe1, 0x6f,
0x08, 0xe4, 0x56, 0xf6, 0x0b, 0xb0, 0x3c, 0x39,
0x8a, 0x2d, 0x48, 0x44, 0x28, 0x13, 0xca, 0xe9,
0xf7, 0xa3, 0xb6, 0x8a, 0x5f, 0x31, 0xa9, 0x72,
0xf2, 0xde, 0x96, 0xf2, 0xb1, 0x53, 0xb1, 0x3e,
0x24, 0x57, 0xfd, 0x18, 0x45, 0x1f, 0xc5, 0x33,
0x1b, 0xa4, 0xe8, 0x21, 0xfa, 0x0e, 0xb2, 0xb9,
0xcb, 0xc7, 0x07, 0x41, 0xdd, 0x2f, 0xb6, 0x6a,
0x23, 0x18, 0xed, 0xc1, 0xef, 0xe2, 0x4b, 0xec,
0xc9, 0xba, 0xfb, 0x46, 0x43, 0x90, 0xd7, 0xb5,
0x68, 0x28, 0x31, 0x2b, 0x8d, 0xa8, 0x51, 0x63,
0xf7, 0x53, 0x99, 0x19, 0x68, 0x85, 0x66, 0x00,
0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35,
0x30, 0x39, 0x00, 0x00, 0x02, 0x3a, 0x30, 0x82,
0x02, 0x36, 0x30, 0x82, 0x01, 0xe0, 0xa0, 0x03,
0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, 0xf1,
0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
0x30, 0x81, 0xa0, 0x31, 0x0b, 0x30, 0x09, 0x06,
0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 0x52,
0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67,
0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, 0x30,
0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b,
0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d,
0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06,
0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, 0x68,
0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20,
0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31,
0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b,
0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55,
0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e,
0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65,
0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e,
0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30,
0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34,
0x31, 0x33, 0x38, 0x5a, 0x18, 0x0f, 0x32, 0x31,
0x38, 0x37, 0x31, 0x31, 0x32, 0x34, 0x30, 0x35,
0x34, 0x31, 0x33, 0x38, 0x5a, 0x30, 0x81, 0xa0,
0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
0x06, 0x13, 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30,
0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a,
0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d,
0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03,
0x55, 0x04, 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f,
0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69,
0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e,
0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f,
0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16,
0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x41,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x31, 0x30,
0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, 0x6d,
0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74,
0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79,
0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74,
0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41,
0x00, 0xc3, 0xe3, 0x5e, 0x41, 0xa7, 0x87, 0x11,
0x00, 0x42, 0x2a, 0xb0, 0x4b, 0xed, 0xb2, 0xe0,
0x23, 0xdb, 0xb1, 0x3d, 0x58, 0x97, 0x35, 0x60,
0x0b, 0x82, 0x59, 0xd3, 0x00, 0xea, 0xd4, 0x61,
0xb8, 0x79, 0x3f, 0xb6, 0x3c, 0x12, 0x05, 0x93,
0x2e, 0x9a, 0x59, 0x68, 0x14, 0x77, 0x3a, 0xc8,
0x50, 0x25, 0x57, 0xa4, 0x49, 0x18, 0x63, 0x41,
0xf0, 0x2d, 0x28, 0xec, 0x06, 0xfb, 0xb4, 0x9f,
0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d,
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00,
0x65, 0x6c, 0x30, 0x01, 0xc2, 0x8e, 0x3e, 0xcb,
0xb3, 0x77, 0x48, 0xe9, 0x66, 0x61, 0x9a, 0x40,
0x86, 0xaf, 0xf6, 0x03, 0xeb, 0xba, 0x6a, 0xf2,
0xfd, 0xe2, 0xaf, 0x36, 0x5e, 0x7b, 0xaa, 0x22,
0x04, 0xdd, 0x2c, 0x20, 0xc4, 0xfc, 0xdd, 0xd0,
0x82, 0x20, 0x1c, 0x3d, 0xd7, 0x9e, 0x5e, 0x5c,
0x92, 0x5a, 0x76, 0x71, 0x28, 0xf5, 0x07, 0x7d,
0xa2, 0x81, 0xba, 0x77, 0x9f, 0x2a, 0xd9, 0x44,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x6d, 0x79,
0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x1a, 0x9f,
0x5b, 0x56, 0xa0, 0x00, 0x00, 0x01, 0x99, 0x30,
0x82, 0x01, 0x95, 0x30, 0x0e, 0x06, 0x0a, 0x2b,
0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01,
0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x81, 0x29,
0xa8, 0xb6, 0x08, 0x0c, 0x85, 0x75, 0x3e, 0xdd,
0xb5, 0xe5, 0x1a, 0x87, 0x68, 0xd1, 0x90, 0x4b,
0x29, 0x31, 0xee, 0x90, 0xbc, 0x9d, 0x73, 0xa0,
0x3f, 0xe9, 0x0b, 0xa4, 0xef, 0x30, 0x9b, 0x36,
0x9a, 0xb2, 0x54, 0x77, 0x81, 0x07, 0x4b, 0xaa,
0xa5, 0x77, 0x98, 0xe1, 0xeb, 0xb5, 0x7c, 0x4e,
0x48, 0xd5, 0x08, 0xfc, 0x2c, 0x36, 0xe2, 0x65,
0x03, 0xac, 0xe5, 0xf3, 0x96, 0xb7, 0xd0, 0xb5,
0x3b, 0x92, 0xe4, 0x14, 0x05, 0x7a, 0x6a, 0x92,
0x56, 0xfe, 0x4e, 0xab, 0xd3, 0x0e, 0x32, 0x04,
0x22, 0x22, 0x74, 0x47, 0x7d, 0xec, 0x21, 0x99,
0x30, 0x31, 0x64, 0x46, 0x64, 0x9b, 0xc7, 0x13,
0xbf, 0xbe, 0xd0, 0x31, 0x49, 0xe7, 0x3c, 0xbf,
0xba, 0xb1, 0x20, 0xf9, 0x42, 0xf4, 0xa9, 0xa9,
0xe5, 0x13, 0x65, 0x32, 0xbf, 0x7c, 0xcc, 0x91,
0xd3, 0xfd, 0x24, 0x47, 0x0b, 0xe5, 0x53, 0xad,
0x50, 0x30, 0x56, 0xd1, 0xfa, 0x9c, 0x37, 0xa8,
0xc1, 0xce, 0xf6, 0x0b, 0x18, 0xaa, 0x7c, 0xab,
0xbd, 0x1f, 0xdf, 0xe4, 0x80, 0xb8, 0xa7, 0xe0,
0xad, 0x7d, 0x50, 0x74, 0xf1, 0x98, 0x78, 0xbc,
0x58, 0xb9, 0xc2, 0x52, 0xbe, 0xd2, 0x5b, 0x81,
0x94, 0x83, 0x8f, 0xb9, 0x4c, 0xee, 0x01, 0x2b,
0x5e, 0xc9, 0x6e, 0x9b, 0xf5, 0x63, 0x69, 0xe4,
0xd8, 0x0b, 0x47, 0xd8, 0xfd, 0xd8, 0xe0, 0xed,
0xa8, 0x27, 0x03, 0x74, 0x1e, 0x5d, 0x32, 0xe6,
0x5c, 0x63, 0xc2, 0xfb, 0x3f, 0xee, 0xb4, 0x13,
0xc6, 0x0e, 0x6e, 0x74, 0xe0, 0x22, 0xac, 0xce,
0x79, 0xf9, 0x43, 0x68, 0xc1, 0x03, 0x74, 0x2b,
0xe1, 0x18, 0xf8, 0x7f, 0x76, 0x9a, 0xea, 0x82,
0x3f, 0xc2, 0xa6, 0xa7, 0x4c, 0xfe, 0xae, 0x29,
0x3b, 0xc1, 0x10, 0x7c, 0xd5, 0x77, 0x17, 0x79,
0x5f, 0xcb, 0xad, 0x1f, 0xd8, 0xa1, 0xfd, 0x90,
0xe1, 0x6b, 0xb2, 0xef, 0xb9, 0x41, 0x26, 0xa4,
0x0b, 0x4f, 0xc6, 0x83, 0x05, 0x6f, 0xf0, 0x64,
0x40, 0xe1, 0x44, 0xc4, 0xf9, 0x40, 0x2b, 0x3b,
0x40, 0xdb, 0xaf, 0x35, 0xa4, 0x9b, 0x9f, 0xc4,
0x74, 0x07, 0xe5, 0x18, 0x60, 0xc5, 0xfe, 0x15,
0x0e, 0x3a, 0x25, 0x2a, 0x11, 0xee, 0x78, 0x2f,
0xb8, 0xd1, 0x6e, 0x4e, 0x3c, 0x0a, 0xb5, 0xb9,
0x40, 0x86, 0x27, 0x6d, 0x8f, 0x53, 0xb7, 0x77,
0x36, 0xec, 0x5d, 0xed, 0x32, 0x40, 0x43, 0x82,
0xc3, 0x52, 0x58, 0xc4, 0x26, 0x39, 0xf3, 0xb3,
0xad, 0x58, 0xab, 0xb7, 0xf7, 0x8e, 0x0e, 0xba,
0x8e, 0x78, 0x9d, 0xbf, 0x58, 0x34, 0xbd, 0x77,
0x73, 0xa6, 0x50, 0x55, 0x00, 0x60, 0x26, 0xbf,
0x6d, 0xb4, 0x98, 0x8a, 0x18, 0x83, 0x89, 0xf8,
0xcd, 0x0d, 0x49, 0x06, 0xae, 0x51, 0x6e, 0xaf,
0xbd, 0xe2, 0x07, 0x13, 0xd8, 0x64, 0xcc, 0xbf,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e,
0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x34, 0x30,
0x82, 0x02, 0x30, 0x30, 0x82, 0x01, 0xda, 0xa0,
0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59,
0xf2, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
0x00, 0x30, 0x81, 0x9d, 0x31, 0x0b, 0x30, 0x09,
0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b,
0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e,
0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14,
0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61,
0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18,
0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54,
0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79,
0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04,
0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x31,
0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03,
0x13, 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74,
0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d,
0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65,
0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, 0x38, 0x30,
0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x35, 0x34,
0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37,
0x31, 0x31, 0x32, 0x33, 0x30, 0x35, 0x34, 0x35,
0x34, 0x30, 0x5a, 0x30, 0x81, 0x9d, 0x31, 0x0b,
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06,
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79,
0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f,
0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04,
0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67,
0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a,
0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74,
0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65,
0x63, 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03,
0x55, 0x04, 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72,
0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55,
0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e,
0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65,
0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e,
0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06,
0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30,
0x48, 0x02, 0x41, 0x00, 0x95, 0xb3, 0x47, 0x17,
0x95, 0x0f, 0x57, 0xcf, 0x66, 0x72, 0x0a, 0x7e,
0x5b, 0x54, 0xea, 0x8c, 0x6f, 0x79, 0xde, 0x94,
0xac, 0x0b, 0x5a, 0xd4, 0xd6, 0x1b, 0x58, 0x12,
0x1a, 0x16, 0x3d, 0xfe, 0xdf, 0xa5, 0x2b, 0x86,
0xbc, 0x64, 0xd4, 0x80, 0x1e, 0x3f, 0xf9, 0xe2,
0x04, 0x03, 0x79, 0x9b, 0xc1, 0x5c, 0xf0, 0xf1,
0xf3, 0xf1, 0xe3, 0xbf, 0x3f, 0xc0, 0x1f, 0xdd,
0xdb, 0xc0, 0x5b, 0x21, 0x02, 0x03, 0x01, 0x00,
0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
0x03, 0x41, 0x00, 0x02, 0xd7, 0xdd, 0xbd, 0x0c,
0x8e, 0x21, 0x20, 0xef, 0x9e, 0x4f, 0x1f, 0xf5,
0x49, 0xf1, 0xae, 0x58, 0x9b, 0x94, 0x3a, 0x1f,
0x70, 0x33, 0xf0, 0x9b, 0xbb, 0xe9, 0xc0, 0xf3,
0x72, 0xcb, 0xde, 0xb6, 0x56, 0x72, 0xcc, 0x1c,
0xf0, 0xd6, 0x5a, 0x2a, 0xbc, 0xa1, 0x7e, 0x23,
0x83, 0xe9, 0xe7, 0xcf, 0x9e, 0xa5, 0xf9, 0xcc,
0xc2, 0x61, 0xf4, 0xdb, 0x40, 0x93, 0x1d, 0x63,
0x8a, 0x50, 0x4c, 0x11, 0x39, 0xb1, 0x91, 0xc1,
0xe6, 0x9d, 0xd9, 0x1a, 0x62, 0x1b, 0xb8, 0xd3,
0xd6, 0x9a, 0x6d, 0xb9, 0x8e, 0x15, 0x51 };
public static InputStream asInputStream() {
byte[] data = new byte[DATA.length];
for (int i = 0; i < data.length; i ++) {
data[i] = (byte) DATA[i];
}
return new ByteArrayInputStream(data);
}
public static char[] getCertificatePassword() {
return "secret".toCharArray();
}
public static char[] getKeyStorePassword() {
return "secret".toCharArray();
}
private BogusKeyStore() { }
}

View File

@ -1,76 +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 javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
import java.security.Security;
public final class BogusSslContextFactory {
private static final String PROTOCOL = "TLS";
private static final SSLContext SERVER_CONTEXT;
private static final SSLContext CLIENT_CONTEXT;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
SSLContext serverContext;
SSLContext clientContext;
try {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(BogusKeyStore.asInputStream(),
BogusKeyStore.getKeyStorePassword());
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, BogusKeyStore.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, BogusTrustManagerFactory.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 BogusSslContextFactory() { }
}

View File

@ -1,69 +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 javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactorySpi;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
/**
* Bogus {@link TrustManagerFactorySpi} which accepts any certificate
* even if it is invalid.
*/
final class BogusTrustManagerFactory 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) {
// NOOP
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// NOOP
}
};
public static TrustManager[] getTrustManagers() {
return new TrustManager[] { DUMMY_TRUST_MANAGER };
}
@Override
protected TrustManager[] engineGetTrustManagers() {
return getTrustManagers();
}
@Override
protected void engineInit(KeyStore keystore) {
// Unused
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters) {
// Unused
}
private BogusTrustManagerFactory() { }
}

View File

@ -17,6 +17,7 @@ package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.handler.ssl.SslContext;
import io.netty.testsuite.transport.TestsuitePermutation;
import io.netty.testsuite.transport.socket.SocketSslEchoTest;
@ -24,9 +25,9 @@ import java.util.List;
public class EpollSocketSslEchoTest extends SocketSslEchoTest {
public EpollSocketSslEchoTest(boolean useChunkedWriteHandler,
boolean useCompositeByteBuf) {
super(useChunkedWriteHandler, useCompositeByteBuf);
public EpollSocketSslEchoTest(
SslContext serverCtx, SslContext clientCtx, boolean useChunkedWriteHandler, boolean useCompositeByteBuf) {
super(serverCtx, clientCtx, useChunkedWriteHandler, useCompositeByteBuf);
}
@Override

View File

@ -0,0 +1,36 @@
/*
* 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.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.handler.ssl.SslContext;
import io.netty.testsuite.transport.TestsuitePermutation;
import io.netty.testsuite.transport.socket.SocketSslGreetingTest;
import java.util.List;
public class EpollSocketSslGreetingTest extends SocketSslGreetingTest {
public EpollSocketSslGreetingTest(SslContext serverCtx, SslContext clientCtx) {
super(serverCtx, clientCtx);
}
@Override
protected List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> newFactories() {
return EpollSocketTestPermutation.INSTANCE.socket();
}
}

View File

@ -17,6 +17,7 @@ package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.handler.ssl.SslContext;
import io.netty.testsuite.transport.TestsuitePermutation;
import io.netty.testsuite.transport.socket.SocketStartTlsTest;
@ -24,6 +25,10 @@ import java.util.List;
public class EpollSocketStartTlsTest extends SocketStartTlsTest {
public EpollSocketStartTlsTest(SslContext serverCtx, SslContext clientCtx) {
super(serverCtx, clientCtx);
}
@Override
protected List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> newFactories() {
return EpollSocketTestPermutation.INSTANCE.socket();

View File

@ -298,6 +298,11 @@ final class DefaultChannelHandlerContext implements ChannelHandlerContext, Resou
return channel.attr(key);
}
@Override
public <T> boolean hasAttr(AttributeKey<T> key) {
return channel.hasAttr(key);
}
@Override
public ChannelHandlerContext fireChannelRegistered() {
DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_REGISTERED);