diff --git a/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java b/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java index 458529dc8f..44d1fe0ece 100644 --- a/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java +++ b/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java @@ -72,8 +72,8 @@ final class CipherSuiteConverter { "^(?:(" + // BEGIN handshake algorithm "(?:(?:EXP-)?" + "(?:" + - "(?:DHE|EDH|ECDH|ECDHE)-(?:DSS|RSA|ECDSA)|" + - "(?:ADH|AECDH|KRB5|PSK)" + + "(?:DHE|EDH|ECDH|ECDHE|SRP)-(?:DSS|RSA|ECDSA)|" + + "(?:ADH|AECDH|KRB5|PSK|SRP)" + ')' + ")|" + "EXP" + @@ -243,6 +243,33 @@ final class CipherSuiteConverter { } } + /** + * Converts the specified Java cipher suites to the colon-separated OpenSSL cipher suite specification. + */ + static String toOpenSsl(Iterable javaCipherSuites) { + final StringBuilder buf = new StringBuilder(); + for (String c: javaCipherSuites) { + if (c == null) { + break; + } + + String converted = toOpenSsl(c); + if (converted != null) { + c = converted; + } + + buf.append(c); + buf.append(':'); + } + + if (buf.length() > 0) { + buf.setLength(buf.length() - 1); + return buf.toString(); + } else { + return ""; + } + } + /** * Converts the specified Java cipher suite to its corresponding OpenSSL cipher suite name. * diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java index ac06375ac0..d87bd2dfb0 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java @@ -20,7 +20,13 @@ 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.Pool; import org.apache.tomcat.jni.SSL; +import org.apache.tomcat.jni.SSLContext; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Tells if {@code netty-tcnative} and its OpenSSL support @@ -31,6 +37,8 @@ public final class OpenSsl { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); private static final Throwable UNAVAILABILITY_CAUSE; + private static final List AVAILABLE_CIPHER_SUITES; + static { Throwable cause = null; try { @@ -44,6 +52,40 @@ public final class OpenSsl { OpenSslEngine.class.getSimpleName() + " will be unavailable.", t); } UNAVAILABILITY_CAUSE = cause; + + if (cause == null) { + final List availableCipherSuites = new ArrayList(128); + final long aprPool = Pool.create(0); + try { + final long sslCtx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + try { + SSLContext.setOptions(sslCtx, SSL.SSL_OP_ALL); + SSLContext.setCipherSuite(sslCtx, "ALL"); + final long ssl = SSL.newSSL(sslCtx, true); + try { + for (String c: SSL.getCiphers(ssl)) { + // Filter out bad input. + if (c == null || c.length() == 0 || availableCipherSuites.contains(c)) { + continue; + } + availableCipherSuites.add(c); + } + } finally { + SSL.freeSSL(ssl); + } + } finally { + SSLContext.free(sslCtx); + } + } catch (Exception e) { + logger.warn("Failed to get the list of available OpenSSL cipher suites.", e); + } finally { + Pool.destroy(aprPool); + } + + AVAILABLE_CIPHER_SUITES = Collections.unmodifiableList(availableCipherSuites); + } else { + AVAILABLE_CIPHER_SUITES = Collections.emptyList(); + } } /** @@ -78,6 +120,14 @@ public final class OpenSsl { return UNAVAILABILITY_CAUSE; } + /** + * Returns all the available OpenSSL cipher suites. + * Please note that the returned array may include the cipher suites that are insecure or non-functional. + */ + public static String[] availableCipherSuites() { + return AVAILABLE_CIPHER_SUITES.toArray(new String[AVAILABLE_CIPHER_SUITES.size()]); + } + static boolean isError(long errorCode) { return errorCode != SSL.SSL_ERROR_NONE; } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java index 213f58bdfc..8a1b5f04a7 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java @@ -139,15 +139,7 @@ public abstract class OpenSslContext extends SslContext { /* List the ciphers that are 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()); + SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(this.ciphers)); } catch (SSLException e) { throw e; } catch (Exception e) { diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 4ecc44467f..fccd9cc38f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -32,8 +32,8 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; -import javax.security.cert.X509Certificate; import javax.security.cert.CertificateException; +import javax.security.cert.X509Certificate; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.security.Principal; @@ -47,7 +47,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.regex.Pattern; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; import static javax.net.ssl.SSLEngineResult.Status.*; @@ -119,7 +118,6 @@ public final class OpenSslEngine extends SSLEngine { private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER; private static final AtomicReferenceFieldUpdater SESSION_UPDATER; - private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-"); // OpenSSL state private long ssl; private long networkBIO; @@ -652,17 +650,39 @@ public final class OpenSslEngine extends SSLEngine { @Override public String[] getSupportedCipherSuites() { - return EmptyArrays.EMPTY_STRINGS; + return OpenSsl.availableCipherSuites(); } @Override public String[] getEnabledCipherSuites() { - return EmptyArrays.EMPTY_STRINGS; + String[] enabled = SSL.getCiphers(ssl); + if (enabled == null) { + return EmptyArrays.EMPTY_STRINGS; + } else { + for (int i = 0; i < enabled.length; i++) { + String c = enabled[i]; + // TODO: Determine the protocol using SSL_CIPHER_get_version() + String mapped = CipherSuiteConverter.toJava(c, "TLS"); + if (mapped != null) { + enabled[i] = mapped; + } + } + return enabled; + } } @Override - public void setEnabledCipherSuites(String[] strings) { - throw new UnsupportedOperationException(); + public void setEnabledCipherSuites(String[] cipherSuites) { + if (cipherSuites == null) { + throw new NullPointerException("cipherSuites"); + } + + final String converted = CipherSuiteConverter.toOpenSsl(Arrays.asList(cipherSuites)); + try { + SSL.setCipherSuites(ssl, converted); + } catch (Exception e) { + throw new IllegalStateException("failed to enable cipher suites: " + converted, e); + } } @Override @@ -1225,6 +1245,7 @@ public final class OpenSslEngine extends SSLEngine { } @Override + @SuppressWarnings("FinalizeDeclaration") protected void finalize() throws Throwable { super.finalize(); // Call shutdown as the user may have created the OpenSslEngine and not used it at all. diff --git a/handler/src/test/java/io/netty/handler/ssl/CipherSuiteConverterTest.java b/handler/src/test/java/io/netty/handler/ssl/CipherSuiteConverterTest.java index 66a995163a..92e1aad49c 100644 --- a/handler/src/test/java/io/netty/handler/ssl/CipherSuiteConverterTest.java +++ b/handler/src/test/java/io/netty/handler/ssl/CipherSuiteConverterTest.java @@ -237,6 +237,27 @@ public class CipherSuiteConverterTest { testO2JMapping("DHE_RSA_WITH_CAMELLIA128_SHA", "DHE-RSA-CAMELLIA128-SHA"); testO2JMapping("DHE_DSS_WITH_CAMELLIA128_SHA", "DHE-DSS-CAMELLIA128-SHA"); testO2JMapping("EDH_RSA_WITH_3DES_EDE_CBC_SHA", "EDH-RSA-DES-CBC3-SHA"); + testO2JMapping("SRP_DSS_WITH_AES_256_CBC_SHA", "SRP-DSS-AES-256-CBC-SHA"); + testO2JMapping("SRP_RSA_WITH_AES_256_CBC_SHA", "SRP-RSA-AES-256-CBC-SHA"); + testO2JMapping("SRP_WITH_AES_256_CBC_SHA", "SRP-AES-256-CBC-SHA"); + testO2JMapping("DH_anon_WITH_AES_256_GCM_SHA384", "ADH-AES256-GCM-SHA384"); + testO2JMapping("DH_anon_WITH_AES_256_CBC_SHA256", "ADH-AES256-SHA256"); + testO2JMapping("DH_anon_WITH_CAMELLIA256_SHA", "ADH-CAMELLIA256-SHA"); + testO2JMapping("SRP_DSS_WITH_AES_128_CBC_SHA", "SRP-DSS-AES-128-CBC-SHA"); + testO2JMapping("SRP_RSA_WITH_AES_128_CBC_SHA", "SRP-RSA-AES-128-CBC-SHA"); + testO2JMapping("SRP_WITH_AES_128_CBC_SHA", "SRP-AES-128-CBC-SHA"); + testO2JMapping("DH_anon_WITH_SEED_SHA", "ADH-SEED-SHA"); + testO2JMapping("DH_anon_WITH_CAMELLIA128_SHA", "ADH-CAMELLIA128-SHA"); + testO2JMapping("RSA_WITH_RC2_CBC_MD5", "RC2-CBC-MD5"); + testO2JMapping("SRP_DSS_WITH_3DES_EDE_CBC_SHA", "SRP-DSS-3DES-EDE-CBC-SHA"); + testO2JMapping("SRP_RSA_WITH_3DES_EDE_CBC_SHA", "SRP-RSA-3DES-EDE-CBC-SHA"); + testO2JMapping("SRP_WITH_3DES_EDE_CBC_SHA", "SRP-3DES-EDE-CBC-SHA"); + testO2JMapping("RSA_WITH_3DES_EDE_CBC_MD5", "DES-CBC3-MD5"); + testO2JMapping("EDH_RSA_WITH_DES_CBC_SHA", "EDH-RSA-DES-CBC-SHA"); + testO2JMapping("EDH_DSS_WITH_DES_CBC_SHA", "EDH-DSS-DES-CBC-SHA"); + testO2JMapping("RSA_WITH_DES_CBC_MD5", "DES-CBC-MD5"); + testO2JMapping("EDH_RSA_EXPORT_WITH_DES_CBC_40_SHA", "EXP-EDH-RSA-DES-CBC-SHA"); + testO2JMapping("EDH_DSS_EXPORT_WITH_DES_CBC_40_SHA", "EXP-EDH-DSS-DES-CBC-SHA"); } private static void testO2JMapping(String javaCipherSuite, String openSslCipherSuite) {