HTTP/2 Cipher Suite Support

Motivation:
The HTTP/2 specification places restrictions on the cipher suites that can be used. There is no central place to pull the ciphers that are allowed by the specification, supported by different java versions, and recommended by the community.

Modifications:
-HTTP/2 will have a security utility class to define supported ciphers
-netty-handler will be modified to support filtering the supplied list of ciphers to the supported ciphers for the current SSLEngine

Result:
-Netty provides unified support for HTTP/2 cipher lists and ciphers can be pruned by currently supported ciphers
This commit is contained in:
Scott Mitchell 2014-09-10 17:26:53 -04:00
parent fbe75ed637
commit d022ac1016
16 changed files with 414 additions and 88 deletions

View File

@ -0,0 +1,81 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.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 <a
* href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html">SunJSSE Supported
* Ciphers</a> and <a
* href="https://wiki.mozilla.org/Security/Server_Side_TLS#Non-Backward_Compatible_Ciphersuite">Mozilla Cipher
* Suites</a> in accordance with the <a
* href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-9.2.2">HTTP/2 Specification</a>.
*/
public static final List<String> CIPHERS;
private static final List<String> 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<String> 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<String> 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<String> ciphers = new ArrayList<String>(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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> ciphers, List<String> defaultCiphers, Set<String> supportedCiphers);
}

View File

@ -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<String> protocols, boolean isServer) {
return engine;

View File

@ -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<String> ciphers, List<String> defaultCiphers,
Set<String> supportedCiphers) {
if (ciphers == null) {
return defaultCiphers.toArray(new String[defaultCiphers.size()]);
} else {
List<String> newCiphers = new ArrayList<String>(supportedCiphers.size());
for (String c : ciphers) {
if (c == null) {
break;
}
newCiphers.add(c);
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}
}

View File

@ -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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, wrapperFactory);
super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);

View File

@ -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<String> DEFAULT_CIPHERS;
static final Set<String> 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<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
for (i = 0; i < supportedProtocols.length; ++i) {
supportedProtocolsSet.add(supportedProtocols[i]);
}
List<String> protocols = new ArrayList<String>();
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<String>(supportedCiphers.length);
for (i = 0; i < supportedCiphers.length; ++i) {
SUPPORTED_CIPHERS.add(supportedCiphers[i]);
}
List<String> ciphers = new ArrayList<String>();
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<String> enabled, String... names) {
for (String n: names) {
for (String s: supported) {
if (n.equals(s)) {
enabled.add(s);
break;
}
private static void addIfSupported(Set<String> supported, List<String> 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<String> unmodifiableCipherSuites;
private final SslEngineWrapperFactory wrapperFactory;
JdkSslContext(Iterable<String> ciphers, SslEngineWrapperFactory wrapperFactory) {
JdkSslContext(Iterable<String> 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<String> ciphers) {
if (ciphers == null) {
return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]);
} else {
List<String> newCiphers = new ArrayList<String>();
for (String c: ciphers) {
if (c == null) {
break;
}
newCiphers.add(c);
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}
}

View File

@ -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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, wrapperFactory);
super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);

View File

@ -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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> 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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> 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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> 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<String> ciphers, Iterable<String> nextProtocols,
SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> 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() { }

View File

@ -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<String> ciphers, List<String> defaultCiphers,
Set<String> supportedCiphers) {
if (defaultCiphers == null) {
throw new NullPointerException("defaultCiphers");
}
if (supportedCiphers == null) {
throw new NullPointerException("supportedCiphers");
}
final List<String> newCiphers;
if (ciphers == null) {
newCiphers = new ArrayList<String>(defaultCiphers.size());
ciphers = defaultCiphers;
} else {
newCiphers = new ArrayList<String>(supportedCiphers.size());
}
for (String c : ciphers) {
if (c == null) {
break;
}
if (supportedCiphers.contains(c)) {
newCiphers.add(c);
}
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}

View File

@ -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<String> ciphers = Arrays.asList("foo", "goo", "noooo", "aaaa");
List<String> defaultCiphers = Arrays.asList("FOO", "GOO");
Set<String> 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<String> ciphers = Arrays.asList("foo", "goo", "nooooo");
List<String> defaultCiphers = Arrays.asList("FOO", "GOO");
Set<String> 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<String> defaultCiphers = Arrays.asList("FOO", "GOO");
Set<String> 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<String> ciphers = Arrays.asList("foo", "goo", "nooooo");
List<String> defaultCiphers = Arrays.asList("FOO", "GOO");
Set<String> 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<String> defaultCiphers = Arrays.asList("FOO", "GOO");
Set<String> 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]);
}
}
}

View File

@ -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<ByteBuf> dataCapture = null;
try {
sendChannel.writeAndFlush(message);
receiverLatch.await(5, TimeUnit.SECONDS);
message.resetReaderIndex();
ArgumentCaptor<ByteBuf> 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();
}
}
}
}
}