diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2SecurityUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2SecurityUtil.java
new file mode 100644
index 0000000000..7a010fa07e
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2SecurityUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.codec.http2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides utilities related to security requirements specific to HTTP/2.
+ */
+public final class Http2SecurityUtil {
+ private Http2SecurityUtil() { }
+
+ /**
+ * The following list is derived from SunJSSE Supported
+ * Ciphers and Mozilla Cipher
+ * Suites in accordance with the HTTP/2 Specification.
+ */
+ public static final List CIPHERS;
+
+ private static final List CIPHERS_JAVA_MOZILLA_INCREASED_SECURITY = Collections.unmodifiableList(Arrays
+ .asList(
+ /* Java 8 */
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDHE-ECDSA-AES256-GCM-SHA384 */
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDHE-ECDSA-AES128-GCM-SHA256 */
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDHE-RSA-AES256-GCM-SHA384 */
+ "TLS_RSA_WITH_AES_256_GCM_SHA384", /* openssl = AES256-GCM-SHA384 */
+ /* REQUIRED BY HTTP/2 SPEC */
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDHE-RSA-AES128-GCM-SHA256 */
+ /* REQUIRED BY HTTP/2 SPEC */
+ "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", /* openssl = DHE-RSA-AES128-GCM-SHA256 */
+ "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" /* openssl = DHE-DSS-AES128-GCM-SHA256 */));
+
+ private static final List CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY = Collections.unmodifiableList(Arrays
+ .asList(
+ /* Java 6,7,8 */
+ "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", /* openssl = ECDHE-ECDSA-RC4-SHA */
+ "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", /* openssl = ECDH-ECDSA-RC4-SHA */
+ /* Java 8 */
+ "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDH-ECDSA-AES256-GCM-SHA384 */
+ "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", /* openssl = ECDH-RSA-AES256-GCM-SHA384 */
+ "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", /* openssl = DHE-RSA-AES256-GCM-SHA384 */
+ "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", /* openssl = DHE-DSS-AES256-GCM-SHA384 */
+ "TLS_RSA_WITH_AES_128_GCM_SHA256", /* openssl = AES128-GCM-SHA256 */
+ "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", /* openssl = ECDH-ECDSA-AES128-GCM-SHA256 */
+ "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" /* openssl = ECDH-RSA-AES128-GCM-SHA256 */));
+
+ private static final List CIPHERS_JAVA_DISABLED_DEFAULT = Collections.unmodifiableList(Arrays.asList(
+ /* Java 8 */
+ "TLS_DH_anon_WITH_AES_256_GCM_SHA384", /* openssl = ADH-AES256-GCM-SHA384 */
+ "TLS_DH_anon_WITH_AES_128_GCM_SHA256", /* openssl = ADH-AES128-GCM-SHA256 */
+ /* Java 6,7,8 */
+ "TLS_ECDH_anon_WITH_RC4_128_SHA" /* openssl = AECDH-RC4-SHA */));
+
+ static {
+ List ciphers = new ArrayList(CIPHERS_JAVA_MOZILLA_INCREASED_SECURITY.size()
+ + CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY.size() + CIPHERS_JAVA_DISABLED_DEFAULT.size());
+ ciphers.addAll(CIPHERS_JAVA_MOZILLA_INCREASED_SECURITY);
+ ciphers.addAll(CIPHERS_JAVA_NO_MOZILLA_INCREASED_SECURITY);
+ ciphers.addAll(CIPHERS_JAVA_DISABLED_DEFAULT);
+ CIPHERS = Collections.unmodifiableList(ciphers);
+ }
+}
diff --git a/example/src/main/java/io/netty/example/http2/client/Http2Client.java b/example/src/main/java/io/netty/example/http2/client/Http2Client.java
index 9203e1d0d6..9cf50ce58b 100644
--- a/example/src/main/java/io/netty/example/http2/client/Http2Client.java
+++ b/example/src/main/java/io/netty/example/http2/client/Http2Client.java
@@ -28,8 +28,10 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
+import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
@@ -57,12 +59,10 @@ public final class Http2Client {
if (SSL) {
sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE,
- Arrays.asList("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- // NOTE: Block ciphers are prohibited by the HTTP/2 specification
- // http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2.
- // The following cipher exists to allow these examples to run with older JREs.
- // Please consult the HTTP/2 specification when selecting cipher suites.
- "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"),
+ Http2SecurityUtil.CIPHERS,
+ /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
+ * Please refer to the HTTP/2 specification for cipher requirements. */
+ SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyAlpnSslEngineWrapper.instance(),
0, 0);
diff --git a/example/src/main/java/io/netty/example/http2/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/server/Http2Server.java
index 4fec25cd67..c38b85a581 100644
--- a/example/src/main/java/io/netty/example/http2/server/Http2Server.java
+++ b/example/src/main/java/io/netty/example/http2/server/Http2Server.java
@@ -23,10 +23,12 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
+import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.util.Arrays;
@@ -47,12 +49,10 @@ public final class Http2Server {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(
ssc.certificate(), ssc.privateKey(), null,
- Arrays.asList("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- // NOTE: Block ciphers are prohibited by the HTTP/2 specification
- // http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2.
- // The following cipher exists to allow these examples to run with older JREs.
- // Please consult the HTTP/2 specification when selecting cipher suites.
- "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"),
+ Http2SecurityUtil.CIPHERS,
+ /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
+ * Please refer to the HTTP/2 specification for cipher requirements. */
+ SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(
SelectedProtocol.HTTP_2.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
diff --git a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
index 95ea49dfe3..9fe42b07a2 100644
--- a/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
+++ b/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java
@@ -24,11 +24,13 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheObjectAggregator;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.BufferedReader;
@@ -49,7 +51,10 @@ public final class MemcacheClient {
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContext.newClientContext(
- null, InsecureTrustManagerFactory.INSTANCE, null,
+ null, InsecureTrustManagerFactory.INSTANCE, Http2SecurityUtil.CIPHERS,
+ /* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
+ * Please refer to the HTTP/2 specification for cipher requirements. */
+ SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyAlpnSslEngineWrapper.instance(),
0, 0);
diff --git a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
index bccdf208d6..b8f66619c2 100644
--- a/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
+++ b/example/src/main/java/io/netty/example/spdy/client/SpdyClient.java
@@ -27,6 +27,7 @@ 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.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@@ -55,7 +56,7 @@ public final class SpdyClient {
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx = SslContext.newClientContext(
- null, InsecureTrustManagerFactory.INSTANCE, null,
+ null, InsecureTrustManagerFactory.INSTANCE, null, IdentityCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyNpnSslEngineWrapper.instance(),
0, 0);
diff --git a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
index 05d9c69037..bcd3257018 100644
--- a/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
+++ b/example/src/main/java/io/netty/example/spdy/server/SpdyServer.java
@@ -24,6 +24,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
@@ -56,7 +57,7 @@ public final class SpdyServer {
// Configure SSL.
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContext.newServerContext(
- ssc.certificate(), ssc.privateKey(), null, null,
+ ssc.certificate(), ssc.privateKey(), null, null, IdentityCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyNpnSslEngineWrapper.instance(),
0, 0);
diff --git a/handler/src/main/java/io/netty/handler/ssl/CipherSuiteFilter.java b/handler/src/main/java/io/netty/handler/ssl/CipherSuiteFilter.java
new file mode 100644
index 0000000000..13c539cd32
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/CipherSuiteFilter.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+import java.util.Set;
+
+/**
+ * Provides a means to filter the supplied cipher suite based upon the supported and default cipher suites.
+ */
+public interface CipherSuiteFilter {
+ /**
+ * Filter the requested {@code ciphers} based upon other cipher characteristics.
+ * @param ciphers The requested ciphers
+ * @param defaultCiphers The default recommended ciphers for the current {@link SSLEngine} as determined by Netty
+ * @param supportedCiphers The supported ciphers for the current {@link SSLEngine}
+ * @return The filter list of ciphers. Must not return {@code null}.
+ */
+ String[] filterCipherSuites(Iterable ciphers, List defaultCiphers, Set supportedCiphers);
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java b/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java
index c7db27c58a..e6654edd5d 100644
--- a/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java
+++ b/handler/src/main/java/io/netty/handler/ssl/DefaultSslWrapperFactory.java
@@ -20,18 +20,14 @@ import java.util.List;
import javax.net.ssl.SSLEngine;
/**
- * Factory for not wrapping {@link SSLEngine} object and just returning it
+ * Factory for not wrapping {@link SSLEngine} object and just returning it.
*/
public final class DefaultSslWrapperFactory implements SslEngineWrapperFactory {
- private static final DefaultSslWrapperFactory INSTANCE = new DefaultSslWrapperFactory();
+ public static final DefaultSslWrapperFactory INSTANCE = new DefaultSslWrapperFactory();
private DefaultSslWrapperFactory() {
}
- public static DefaultSslWrapperFactory instance() {
- return INSTANCE;
- }
-
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, List protocols, boolean isServer) {
return engine;
diff --git a/handler/src/main/java/io/netty/handler/ssl/IdentityCipherSuiteFilter.java b/handler/src/main/java/io/netty/handler/ssl/IdentityCipherSuiteFilter.java
new file mode 100644
index 0000000000..c9ebfb0da2
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/IdentityCipherSuiteFilter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class will not do any filtering of ciphers suites.
+ */
+public final class IdentityCipherSuiteFilter implements CipherSuiteFilter {
+ public static final IdentityCipherSuiteFilter INSTANCE = new IdentityCipherSuiteFilter();
+
+ private IdentityCipherSuiteFilter() { }
+
+ @Override
+ public String[] filterCipherSuites(Iterable ciphers, List defaultCiphers,
+ Set supportedCiphers) {
+ if (ciphers == null) {
+ return defaultCiphers.toArray(new String[defaultCiphers.size()]);
+ } else {
+ List newCiphers = new ArrayList(supportedCiphers.size());
+ for (String c : ciphers) {
+ if (c == null) {
+ break;
+ }
+ newCiphers.add(c);
+ }
+ return newCiphers.toArray(new String[newCiphers.size()]);
+ }
+ }
+
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
index 99affaa2cd..28ab305e5f 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
@@ -79,7 +79,8 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@code null} to use the default.
*/
public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
- this(certChainFile, trustManagerFactory, null, null, DefaultSslWrapperFactory.instance(), 0, 0);
+ this(certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
+ null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
}
/**
@@ -92,6 +93,7 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -103,11 +105,11 @@ public final class JdkSslClientContext extends JdkSslContext {
*/
public JdkSslClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
- super(ciphers, wrapperFactory);
+ super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
index 0ce4119a85..05c8edc471 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
@@ -23,7 +23,9 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
@@ -39,9 +41,11 @@ public abstract class JdkSslContext extends SslContext {
static final String PROTOCOL = "TLS";
static final String[] PROTOCOLS;
static final List DEFAULT_CIPHERS;
+ static final Set SUPPORTED_CIPHERS;
static {
SSLContext context;
+ int i;
try {
context = SSLContext.getInstance(PROTOCOL);
context.init(null, null, null);
@@ -52,10 +56,14 @@ public abstract class JdkSslContext extends SslContext {
SSLEngine engine = context.createSSLEngine();
// Choose the sensible default list of protocols.
- String[] supportedProtocols = engine.getSupportedProtocols();
+ final String[] supportedProtocols = engine.getSupportedProtocols();
+ Set supportedProtocolsSet = new HashSet(supportedProtocols.length);
+ for (i = 0; i < supportedProtocols.length; ++i) {
+ supportedProtocolsSet.add(supportedProtocols[i]);
+ }
List protocols = new ArrayList();
addIfSupported(
- supportedProtocols, protocols,
+ supportedProtocolsSet, protocols,
"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3");
if (!protocols.isEmpty()) {
@@ -65,10 +73,14 @@ public abstract class JdkSslContext extends SslContext {
}
// Choose the sensible default list of cipher suites.
- String[] supportedCiphers = engine.getSupportedCipherSuites();
+ final String[] supportedCiphers = engine.getSupportedCipherSuites();
+ SUPPORTED_CIPHERS = new HashSet(supportedCiphers.length);
+ for (i = 0; i < supportedCiphers.length; ++i) {
+ SUPPORTED_CIPHERS.add(supportedCiphers[i]);
+ }
List ciphers = new ArrayList();
addIfSupported(
- supportedCiphers, ciphers,
+ SUPPORTED_CIPHERS, 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",
@@ -98,13 +110,11 @@ public abstract class JdkSslContext extends SslContext {
}
}
- private static void addIfSupported(String[] supported, List enabled, String... names) {
- for (String n: names) {
- for (String s: supported) {
- if (n.equals(s)) {
- enabled.add(s);
- break;
- }
+ private static void addIfSupported(Set supported, List enabled, String... names) {
+ for (int i = 0; i < names.length; ++i) {
+ String n = names[i];
+ if (supported.contains(n)) {
+ enabled.add(n);
}
}
}
@@ -113,11 +123,14 @@ public abstract class JdkSslContext extends SslContext {
private final List unmodifiableCipherSuites;
private final SslEngineWrapperFactory wrapperFactory;
- JdkSslContext(Iterable ciphers, SslEngineWrapperFactory wrapperFactory) {
+ JdkSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, SslEngineWrapperFactory wrapperFactory) {
if (wrapperFactory == null) {
throw new NullPointerException("wrapperFactory");
}
- cipherSuites = toCipherSuiteArray(ciphers);
+ if (cipherFilter == null) {
+ throw new NullPointerException("cipherFilter");
+ }
+ cipherSuites = cipherFilter.filterCipherSuites(ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
this.wrapperFactory = wrapperFactory;
}
@@ -174,19 +187,4 @@ public abstract class JdkSslContext extends SslContext {
private SSLEngine wrapEngine(SSLEngine engine) {
return wrapperFactory.wrapSslEngine(engine, nextProtocols(), isServer());
}
-
- private static String[] toCipherSuiteArray(Iterable ciphers) {
- if (ciphers == null) {
- return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]);
- } else {
- List newCiphers = new ArrayList();
- for (String c: ciphers) {
- if (c == null) {
- break;
- }
- newCiphers.add(c);
- }
- return newCiphers.toArray(new String[newCiphers.size()]);
- }
- }
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
index 955cc16a31..5009f2c030 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
@@ -74,7 +74,8 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@code null} if it's not password-protected.
*/
public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
- this(certChainFile, keyFile, keyPassword, null, null, DefaultSslWrapperFactory.instance(), 0, 0);
+ this(certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
+ null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
}
/**
@@ -86,6 +87,7 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -97,11 +99,11 @@ public final class JdkSslServerContext extends JdkSslContext {
*/
public JdkSslServerContext(
File certChainFile, File keyFile, String keyPassword,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
- super(ciphers, wrapperFactory);
+ super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
index 7b04827fd7..e9f03aef7a 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
@@ -133,6 +133,7 @@ public abstract class SslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -146,12 +147,12 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newServerContext(
null, certChainFile, keyFile, keyPassword,
- ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
+ ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
/**
@@ -181,8 +182,8 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
SslProvider provider, File certChainFile, File keyFile, String keyPassword) throws SSLException {
- return newServerContext(provider, certChainFile, keyFile, keyPassword, null, null,
- DefaultSslWrapperFactory.instance(), 0, 0);
+ return newServerContext(provider, certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
+ null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
}
/**
@@ -196,6 +197,8 @@ public abstract class SslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
+ * Only required if {@code provider} is {@link SslProvider#JDK}
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -210,8 +213,8 @@ public abstract class SslContext {
public static SslContext newServerContext(
SslProvider provider,
File certChainFile, File keyFile, String keyPassword,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider == null) {
@@ -222,7 +225,7 @@ public abstract class SslContext {
case JDK:
return new JdkSslServerContext(
certChainFile, keyFile, keyPassword,
- ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
+ ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
case OPENSSL:
return new OpenSslServerContext(
certChainFile, keyFile, keyPassword,
@@ -291,6 +294,7 @@ public abstract class SslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -305,12 +309,12 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newClientContext(
null, certChainFile, trustManagerFactory,
- ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
+ ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
/**
@@ -370,8 +374,8 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
- return newClientContext(provider, certChainFile, trustManagerFactory, null, null,
- DefaultSslWrapperFactory.instance(), 0, 0);
+ return newClientContext(provider, certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
+ null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
}
/**
@@ -386,6 +390,7 @@ public abstract class SslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
@@ -401,8 +406,8 @@ public abstract class SslContext {
public static SslContext newClientContext(
SslProvider provider,
File certChainFile, TrustManagerFactory trustManagerFactory,
- Iterable ciphers, Iterable nextProtocols,
- SslEngineWrapperFactory wrapperFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter,
+ Iterable nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider != null && provider != SslProvider.JDK) {
@@ -411,7 +416,7 @@ public abstract class SslContext {
return new JdkSslClientContext(
certChainFile, trustManagerFactory,
- ciphers, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
+ ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
}
SslContext() { }
diff --git a/handler/src/main/java/io/netty/handler/ssl/SupportedCipherSuiteFilter.java b/handler/src/main/java/io/netty/handler/ssl/SupportedCipherSuiteFilter.java
new file mode 100644
index 0000000000..7c904327c3
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/SupportedCipherSuiteFilter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class will filter all requested ciphers out that are not supported by the current {@link SSLEngine}.
+ */
+public final class SupportedCipherSuiteFilter implements CipherSuiteFilter {
+ public static final SupportedCipherSuiteFilter INSTANCE = new SupportedCipherSuiteFilter();
+
+ private SupportedCipherSuiteFilter() { }
+
+ @Override
+ public String[] filterCipherSuites(Iterable ciphers, List defaultCiphers,
+ Set supportedCiphers) {
+ if (defaultCiphers == null) {
+ throw new NullPointerException("defaultCiphers");
+ }
+ if (supportedCiphers == null) {
+ throw new NullPointerException("supportedCiphers");
+ }
+
+ final List newCiphers;
+ if (ciphers == null) {
+ newCiphers = new ArrayList(defaultCiphers.size());
+ ciphers = defaultCiphers;
+ } else {
+ newCiphers = new ArrayList(supportedCiphers.size());
+ }
+ for (String c : ciphers) {
+ if (c == null) {
+ break;
+ }
+ if (supportedCiphers.contains(c)) {
+ newCiphers.add(c);
+ }
+ }
+ return newCiphers.toArray(new String[newCiphers.size()]);
+ }
+
+}
diff --git a/handler/src/test/java/io/netty/handler/ssl/CipherSuiteFilterTest.java b/handler/src/test/java/io/netty/handler/ssl/CipherSuiteFilterTest.java
new file mode 100644
index 0000000000..679fced7f0
--- /dev/null
+++ b/handler/src/test/java/io/netty/handler/ssl/CipherSuiteFilterTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.mockito.internal.util.collections.Sets;
+
+public class CipherSuiteFilterTest {
+ @Test
+ public void supportedCipherSuiteFilterNonEmpty() {
+ List ciphers = Arrays.asList("foo", "goo", "noooo", "aaaa");
+ List defaultCiphers = Arrays.asList("FOO", "GOO");
+ Set supportedCiphers = Sets.newSet("FOO", "aaaa", "bbbbbb", "GOO", "goo");
+ CipherSuiteFilter filter = SupportedCipherSuiteFilter.INSTANCE;
+ String[] results = filter.filterCipherSuites(ciphers, defaultCiphers, supportedCiphers);
+ assertEquals(2, results.length);
+ assertEquals("goo", results[0]);
+ assertEquals("aaaa", results[1]);
+ }
+
+ @Test
+ public void supportedCipherSuiteFilterToEmpty() {
+ List ciphers = Arrays.asList("foo", "goo", "nooooo");
+ List defaultCiphers = Arrays.asList("FOO", "GOO");
+ Set supportedCiphers = Sets.newSet("FOO", "aaaa", "bbbbbb", "GOO");
+ CipherSuiteFilter filter = SupportedCipherSuiteFilter.INSTANCE;
+ String[] results = filter.filterCipherSuites(ciphers, defaultCiphers, supportedCiphers);
+ assertEquals(0, results.length);
+ }
+
+ @Test
+ public void supportedCipherSuiteFilterToDefault() {
+ List defaultCiphers = Arrays.asList("FOO", "GOO");
+ Set supportedCiphers = Sets.newSet("GOO", "FOO", "aaaa", "bbbbbb");
+ CipherSuiteFilter filter = SupportedCipherSuiteFilter.INSTANCE;
+ String[] results = filter.filterCipherSuites(null, defaultCiphers, supportedCiphers);
+ assertEquals(2, results.length);
+ assertEquals("FOO", results[0]);
+ assertEquals("GOO", results[1]);
+ }
+
+ @Test
+ public void defaulCipherSuiteNoFilter() {
+ List ciphers = Arrays.asList("foo", "goo", "nooooo");
+ List defaultCiphers = Arrays.asList("FOO", "GOO");
+ Set supportedCiphers = Sets.newSet("FOO", "aaaa", "bbbbbb", "GOO");
+ CipherSuiteFilter filter = IdentityCipherSuiteFilter.INSTANCE;
+ String[] results = filter.filterCipherSuites(ciphers, defaultCiphers, supportedCiphers);
+ assertEquals(ciphers.size(), results.length);
+ for (int i = 0; i < ciphers.size(); ++i) {
+ assertEquals(ciphers.get(i), results[i]);
+ }
+ }
+
+ @Test
+ public void defaulCipherSuiteTakeDefault() {
+ List defaultCiphers = Arrays.asList("FOO", "GOO");
+ Set supportedCiphers = Sets.newSet("FOO", "aaaa", "bbbbbb", "GOO");
+ CipherSuiteFilter filter = IdentityCipherSuiteFilter.INSTANCE;
+ String[] results = filter.filterCipherSuites(null, defaultCiphers, supportedCiphers);
+ assertEquals(defaultCiphers.size(), results.length);
+ for (int i = 0; i < defaultCiphers.size(); ++i) {
+ assertEquals(defaultCiphers.get(i), results[i]);
+ }
+ }
+}
diff --git a/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java
index a5d3d5d688..9e92bd1116 100644
--- a/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/JettySslEngineTest.java
@@ -15,12 +15,10 @@
*/
package io.netty.handler.ssl;
-import static org.junit.Assume.assumeNoException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assume.assumeNoException;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
@@ -41,6 +39,7 @@ import io.netty.util.NetUtil;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -49,12 +48,13 @@ import javax.net.ssl.SSLException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import static org.mockito.Mockito.verify;
public class JettySslEngineTest {
private static final String APPLICATION_LEVEL_PROTOCOL = "my-protocol";
-
@Mock
private MessageReciever serverReceiver;
@Mock
@@ -116,7 +116,7 @@ public class JettySslEngineTest {
try {
wrapper = JettyNpnSslEngineWrapper.instance();
} catch (SSLException e) {
- // NPN availability is dependent on the java version. If NPN is not available because of
+ // NPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
@@ -130,7 +130,7 @@ public class JettySslEngineTest {
try {
wrapper = JettyAlpnSslEngineWrapper.instance();
} catch (SSLException e) {
- // ALPN availability is dependent on the java version. If NPN is not available because of
+ // ALPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
@@ -139,12 +139,12 @@ public class JettySslEngineTest {
}
private void mySetup(SslEngineWrapperFactory wrapper) throws InterruptedException, SSLException,
- CertificateException {
+ CertificateException {
SelfSignedCertificate ssc = new SelfSignedCertificate();
serverSslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey(), null, null,
- Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
+ IdentityCipherSuiteFilter.INSTANCE, Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
clientSslCtx = SslContext.newClientContext(SslProvider.JDK, null, InsecureTrustManagerFactory.INSTANCE, null,
- Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
+ IdentityCipherSuiteFilter.INSTANCE, Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
serverConnectedChannel = null;
sb = new ServerBootstrap();
@@ -182,8 +182,8 @@ public class JettySslEngineTest {
}
private void runTest() throws Exception {
- ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes());
- ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes());
+ final ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes());
+ final ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes());
try {
writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver);
writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver);
@@ -206,9 +206,21 @@ public class JettySslEngineTest {
private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
MessageReciever receiver) throws Exception {
- sendChannel.writeAndFlush(message);
- receiverLatch.await(5, TimeUnit.SECONDS);
- message.resetReaderIndex();
- verify(receiver).messageReceived(eq(message));
+ List dataCapture = null;
+ try {
+ sendChannel.writeAndFlush(message);
+ receiverLatch.await(5, TimeUnit.SECONDS);
+ message.resetReaderIndex();
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ByteBuf.class);
+ verify(receiver).messageReceived(captor.capture());
+ dataCapture = captor.getAllValues();
+ assertEquals(message, dataCapture.get(0));
+ } finally {
+ if (dataCapture != null) {
+ for (ByteBuf data : dataCapture) {
+ data.release();
+ }
+ }
+ }
}
}