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:
commit
054e4c5233
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,28 +1,32 @@
|
||||
#Eclipse project files
|
||||
# Eclipse project files
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
|
||||
#IntelliJ IDEA project files and directories
|
||||
# IntelliJ IDEA project files and directories
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
#Geany project file
|
||||
# Geany project file
|
||||
.geany
|
||||
|
||||
#KDevelop project file and directory
|
||||
# KDevelop project file and directory
|
||||
.kdev4/
|
||||
*.kdev4
|
||||
|
||||
#Build targets
|
||||
# Build targets
|
||||
/target
|
||||
*/target
|
||||
|
||||
#Report directories
|
||||
# Report directories
|
||||
/reports
|
||||
*/reports
|
||||
|
||||
#Mac-specific directory that no other operating system needs.
|
||||
.DS_Store
|
||||
# Mac-specific directory that no other operating system needs.
|
||||
.DS_Store
|
||||
|
||||
# JVM crash logs
|
||||
hs_err_pid*.log
|
||||
|
||||
|
59
NOTICE.txt
59
NOTICE.txt
@ -50,14 +50,6 @@ WebSocket and HTTP server, which can be obtained at:
|
||||
* HOMEPAGE:
|
||||
* https://github.com/joewalnes/webbit
|
||||
|
||||
This product contains a modified portion of 'Caliper', Google's micro-
|
||||
benchmarking framework, which can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.caliper.txt (Apache License 2.0)
|
||||
* HOMEPAGE:
|
||||
* http://code.google.com/p/caliper/
|
||||
|
||||
This product contains a modified portion of 'SLF4J', a simple logging
|
||||
facade for Java, which can be obtained at:
|
||||
|
||||
@ -72,6 +64,15 @@ Bloch of Google, Inc:
|
||||
* LICENSE:
|
||||
* license/LICENSE.deque.txt (Public Domain)
|
||||
|
||||
This product contains a modified version of Roland Kuhn's ASL2
|
||||
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
||||
It can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.abstractnodequeue.txt (Public Domain)
|
||||
* HOMEPAGE:
|
||||
* https://github.com/akka/akka/blob/wip-2.2.3-for-scala-2.11/akka-actor/src/main/java/akka/dispatch/AbstractNodeQueue.java
|
||||
|
||||
This product optionally depends on 'JZlib', a re-implementation of zlib in
|
||||
pure Java, which can be obtained at:
|
||||
|
||||
@ -88,6 +89,23 @@ interchange format, which can be obtained at:
|
||||
* HOMEPAGE:
|
||||
* http://code.google.com/p/protobuf/
|
||||
|
||||
This product optionally depends on 'Bouncy Castle Crypto APIs' to generate
|
||||
a temporary self-signed X.509 certificate when the JVM does not provide the
|
||||
equivalent functionality. It can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.bouncycastle.txt (MIT License)
|
||||
* HOMEPAGE:
|
||||
* http://www.bouncycastle.org/
|
||||
|
||||
This product optionally depends on 'Snappy', a compression library produced
|
||||
by Google Inc, which can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.snappy.txt (New BSD License)
|
||||
* HOMEPAGE:
|
||||
* http://code.google.com/p/snappy/
|
||||
|
||||
This product optionally depends on 'JBoss Marshalling', an alternative Java
|
||||
serialization API, which can be obtained at:
|
||||
|
||||
@ -96,6 +114,14 @@ serialization API, which can be obtained at:
|
||||
* HOMEPAGE:
|
||||
* http://www.jboss.org/jbossmarshalling
|
||||
|
||||
This product optionally depends on 'Caliper', Google's micro-
|
||||
benchmarking framework, which can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.caliper.txt (Apache License 2.0)
|
||||
* HOMEPAGE:
|
||||
* http://code.google.com/p/caliper/
|
||||
|
||||
This product optionally depends on 'Apache Commons Logging', a logging
|
||||
framework, which can be obtained at:
|
||||
|
||||
@ -111,20 +137,3 @@ can be obtained at:
|
||||
* license/LICENSE.log4j.txt (Apache License 2.0)
|
||||
* HOMEPAGE:
|
||||
* http://logging.apache.org/log4j/
|
||||
|
||||
This product optionally depends on 'Snappy', a compression library produced
|
||||
by Google Inc, which can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.snappy.txt (New BSD License)
|
||||
* HOMEPAGE:
|
||||
* http://code.google.com/p/snappy/
|
||||
|
||||
This product contains a modified version of Roland Kuhn's ASL2
|
||||
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
||||
It can be obtained at:
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.abstractnodequeue.txt (Public Domain)
|
||||
* HOMEPAGE:
|
||||
* https://github.com/akka/akka/blob/wip-2.2.3-for-scala-2.11/akka-actor/src/main/java/akka/dispatch/AbstractNodeQueue.java
|
@ -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() {
|
||||
|
@ -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()));
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() { }
|
||||
}
|
||||
|
@ -42,6 +42,12 @@ final class PlatformDependent0 {
|
||||
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
private static final long ADDRESS_FIELD_OFFSET;
|
||||
|
||||
/**
|
||||
* Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling
|
||||
* during a large copy.
|
||||
*/
|
||||
private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
|
||||
|
||||
/**
|
||||
* {@code true} if and only if the platform supports unaligned access.
|
||||
*
|
||||
@ -155,11 +161,9 @@ final class PlatformDependent0 {
|
||||
}
|
||||
try {
|
||||
Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
|
||||
if (cleaner == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"attempted to deallocate the buffer which was allocated via JNIEnv->NewDirectByteBuffer()");
|
||||
if (cleaner != null) {
|
||||
cleaner.clean();
|
||||
}
|
||||
cleaner.clean();
|
||||
} catch (Throwable t) {
|
||||
// Nothing we can do here.
|
||||
}
|
||||
@ -308,11 +312,25 @@ final class PlatformDependent0 {
|
||||
}
|
||||
|
||||
static void copyMemory(long srcAddr, long dstAddr, long length) {
|
||||
UNSAFE.copyMemory(srcAddr, dstAddr, length);
|
||||
//UNSAFE.copyMemory(srcAddr, dstAddr, length);
|
||||
while (length > 0) {
|
||||
long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
|
||||
UNSAFE.copyMemory(srcAddr, dstAddr, size);
|
||||
length -= size;
|
||||
srcAddr += size;
|
||||
dstAddr += size;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) {
|
||||
UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
|
||||
//UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length);
|
||||
while (length > 0) {
|
||||
long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
|
||||
UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size);
|
||||
length -= size;
|
||||
srcOffset += size;
|
||||
dstOffset += size;
|
||||
}
|
||||
}
|
||||
|
||||
static <U, W> AtomicReferenceFieldUpdater<U, W> newAtomicReferenceFieldUpdater(
|
||||
|
242
example/pom.xml
242
example/pom.xml
@ -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>
|
||||
@ -106,219 +108,23 @@
|
||||
<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>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<executable>${java.home}/bin/java</executable>
|
||||
<commandlineArgs>
|
||||
${argLine.common}
|
||||
${argLine.bootcp}
|
||||
${argLine.leak}
|
||||
${argLine.coverage}
|
||||
-classpath %classpath
|
||||
${argLine.example}
|
||||
${exampleClass}
|
||||
</commandlineArgs>
|
||||
<classpathScope>runtime</classpathScope>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spdy-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.spdy.server.SpdyServer</argument>
|
||||
</arguments>
|
||||
<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>
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
184
handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
Normal file
184
handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
Normal 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()]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
84
handler/src/main/java/io/netty/handler/ssl/OpenSsl.java
Normal file
84
handler/src/main/java/io/netty/handler/ssl/OpenSsl.java
Normal 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() { }
|
||||
}
|
885
handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
Normal file
885
handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
144
handler/src/main/java/io/netty/handler/ssl/PemReader.java
Normal file
144
handler/src/main/java/io/netty/handler/ssl/PemReader.java
Normal 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() { }
|
||||
}
|
541
handler/src/main/java/io/netty/handler/ssl/SslContext.java
Normal file
541
handler/src/main/java/io/netty/handler/ssl/SslContext.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
// 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 (;;) {
|
||||
if (decodeOut == null) {
|
||||
decodeOut = ctx.alloc().buffer(initialOutAppBufCapacity);
|
||||
}
|
||||
|
||||
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
|
||||
|
31
handler/src/main/java/io/netty/handler/ssl/SslProvider.java
Normal file
31
handler/src/main/java/io/netty/handler/ssl/SslProvider.java
Normal 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
|
||||
}
|
@ -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() { }
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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() { }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
23
license/LICENSE.bouncycastle.txt
Normal file
23
license/LICENSE.bouncycastle.txt
Normal 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.
|
||||
|
243
pom.xml
243
pom.xml
@ -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,15 +170,153 @@
|
||||
</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>
|
||||
-server
|
||||
<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
|
||||
-XX:+TieredCompilation
|
||||
@ -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
50
run-example.sh
Executable 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"
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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() { }
|
||||
}
|
@ -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() { }
|
||||
}
|
@ -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() { }
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user