Implement OpenSslEngine.getSupportedCipherSuites() and get/setEnabledCipherSuites()
Motivation: To make OpenSslEngine a full drop-in replacement, we need to implement getSupportedCipherSuites() and get/setEnabledCipherSuites(). Modifications: - Retrieve the list of the available cipher suites when initializing OpenSsl. - Improve CipherSuiteConverter to understand SRP - Add more test data to CipherSuiteConverterTest - Add bulk-conversion method to CipherSuiteConverter Result: OpenSslEngine should now be a drop-in replacement for JDK SSLEngineImpl for most cases.
This commit is contained in:
parent
827a4cbf36
commit
2056aed50d
@ -72,8 +72,8 @@ final class CipherSuiteConverter {
|
|||||||
"^(?:(" + // BEGIN handshake algorithm
|
"^(?:(" + // BEGIN handshake algorithm
|
||||||
"(?:(?:EXP-)?" +
|
"(?:(?:EXP-)?" +
|
||||||
"(?:" +
|
"(?:" +
|
||||||
"(?:DHE|EDH|ECDH|ECDHE)-(?:DSS|RSA|ECDSA)|" +
|
"(?:DHE|EDH|ECDH|ECDHE|SRP)-(?:DSS|RSA|ECDSA)|" +
|
||||||
"(?:ADH|AECDH|KRB5|PSK)" +
|
"(?:ADH|AECDH|KRB5|PSK|SRP)" +
|
||||||
')' +
|
')' +
|
||||||
")|" +
|
")|" +
|
||||||
"EXP" +
|
"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<String> 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.
|
* Converts the specified Java cipher suite to its corresponding OpenSSL cipher suite name.
|
||||||
*
|
*
|
||||||
|
@ -20,7 +20,13 @@ import io.netty.util.internal.NativeLibraryLoader;
|
|||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
import org.apache.tomcat.jni.Library;
|
import org.apache.tomcat.jni.Library;
|
||||||
|
import org.apache.tomcat.jni.Pool;
|
||||||
import org.apache.tomcat.jni.SSL;
|
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 <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
|
* Tells if <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
|
||||||
@ -31,6 +37,8 @@ public final class OpenSsl {
|
|||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
|
||||||
private static final Throwable UNAVAILABILITY_CAUSE;
|
private static final Throwable UNAVAILABILITY_CAUSE;
|
||||||
|
|
||||||
|
private static final List<String> AVAILABLE_CIPHER_SUITES;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Throwable cause = null;
|
Throwable cause = null;
|
||||||
try {
|
try {
|
||||||
@ -44,6 +52,40 @@ public final class OpenSsl {
|
|||||||
OpenSslEngine.class.getSimpleName() + " will be unavailable.", t);
|
OpenSslEngine.class.getSimpleName() + " will be unavailable.", t);
|
||||||
}
|
}
|
||||||
UNAVAILABILITY_CAUSE = cause;
|
UNAVAILABILITY_CAUSE = cause;
|
||||||
|
|
||||||
|
if (cause == null) {
|
||||||
|
final List<String> availableCipherSuites = new ArrayList<String>(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;
|
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) {
|
static boolean isError(long errorCode) {
|
||||||
return errorCode != SSL.SSL_ERROR_NONE;
|
return errorCode != SSL.SSL_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
@ -139,15 +139,7 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
|
|
||||||
/* List the ciphers that are permitted to negotiate. */
|
/* List the ciphers that are permitted to negotiate. */
|
||||||
try {
|
try {
|
||||||
// Convert the cipher list into a colon-separated string.
|
SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(this.ciphers));
|
||||||
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) {
|
} catch (SSLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -32,8 +32,8 @@ import javax.net.ssl.SSLSession;
|
|||||||
import javax.net.ssl.SSLSessionBindingEvent;
|
import javax.net.ssl.SSLSessionBindingEvent;
|
||||||
import javax.net.ssl.SSLSessionBindingListener;
|
import javax.net.ssl.SSLSessionBindingListener;
|
||||||
import javax.net.ssl.SSLSessionContext;
|
import javax.net.ssl.SSLSessionContext;
|
||||||
import javax.security.cert.X509Certificate;
|
|
||||||
import javax.security.cert.CertificateException;
|
import javax.security.cert.CertificateException;
|
||||||
|
import javax.security.cert.X509Certificate;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ReadOnlyBufferException;
|
import java.nio.ReadOnlyBufferException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
@ -47,7 +47,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
||||||
import static javax.net.ssl.SSLEngineResult.Status.*;
|
import static javax.net.ssl.SSLEngineResult.Status.*;
|
||||||
@ -119,7 +118,6 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER;
|
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER;
|
||||||
private static final AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> SESSION_UPDATER;
|
private static final AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> SESSION_UPDATER;
|
||||||
|
|
||||||
private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-");
|
|
||||||
// OpenSSL state
|
// OpenSSL state
|
||||||
private long ssl;
|
private long ssl;
|
||||||
private long networkBIO;
|
private long networkBIO;
|
||||||
@ -652,17 +650,39 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getSupportedCipherSuites() {
|
public String[] getSupportedCipherSuites() {
|
||||||
return EmptyArrays.EMPTY_STRINGS;
|
return OpenSsl.availableCipherSuites();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getEnabledCipherSuites() {
|
public String[] getEnabledCipherSuites() {
|
||||||
|
String[] enabled = SSL.getCiphers(ssl);
|
||||||
|
if (enabled == null) {
|
||||||
return EmptyArrays.EMPTY_STRINGS;
|
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
|
@Override
|
||||||
public void setEnabledCipherSuites(String[] strings) {
|
public void setEnabledCipherSuites(String[] cipherSuites) {
|
||||||
throw new UnsupportedOperationException();
|
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
|
@Override
|
||||||
@ -1225,6 +1245,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("FinalizeDeclaration")
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
super.finalize();
|
super.finalize();
|
||||||
// Call shutdown as the user may have created the OpenSslEngine and not used it at all.
|
// Call shutdown as the user may have created the OpenSslEngine and not used it at all.
|
||||||
|
@ -237,6 +237,27 @@ public class CipherSuiteConverterTest {
|
|||||||
testO2JMapping("DHE_RSA_WITH_CAMELLIA128_SHA", "DHE-RSA-CAMELLIA128-SHA");
|
testO2JMapping("DHE_RSA_WITH_CAMELLIA128_SHA", "DHE-RSA-CAMELLIA128-SHA");
|
||||||
testO2JMapping("DHE_DSS_WITH_CAMELLIA128_SHA", "DHE-DSS-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("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) {
|
private static void testO2JMapping(String javaCipherSuite, String openSslCipherSuite) {
|
||||||
|
Loading…
Reference in New Issue
Block a user