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
|
||||
"(?:(?: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<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.
|
||||
*
|
||||
|
@ -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 <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 Throwable UNAVAILABILITY_CAUSE;
|
||||
|
||||
private static final List<String> 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<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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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<OpenSslEngine> DESTROYED_UPDATER;
|
||||
private static final AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> 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.
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user