Add support for KeyManagerFactory when using SslProvider.OpenSsl.

Motivation:

To be able to use SslProvider.OpenSsl with existing java apps that use the JDK SSL API we need to also provide a way to use it with an existing KeyManagerFactory.

Modification:

Make use of new tcnative apis and so hook in KeyManagerFactory.

Result:

SslProvider.OpenSsl can be used with KeyManagerFactory as well.
This commit is contained in:
Norman Maurer 2016-06-20 14:07:53 +02:00
parent 3a69adfefb
commit 5e64985089
10 changed files with 504 additions and 223 deletions

View File

@ -341,17 +341,6 @@ public class JdkSslContext extends SslContext {
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
}
static KeyManagerFactory buildKeyManagerFactory(X509Certificate[] certChain, PrivateKey key, String keyPassword,
KeyManagerFactory kmf)
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
return buildKeyManagerFactory(certChain, algorithm, key, keyPassword, kmf);
}
/**
* Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
* and a certificate chain.
@ -375,20 +364,4 @@ public class JdkSslContext extends SslContext {
return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm,
toPrivateKey(keyFile, keyPassword), keyPassword, kmf);
}
static KeyManagerFactory buildKeyManagerFactory(X509Certificate[] certChainFile,
String keyAlgorithm, PrivateKey key,
String keyPassword, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, IOException,
CertificateException, UnrecoverableKeyException {
char[] keyPasswordChars = keyPassword == null ? EmptyArrays.EMPTY_CHARS : keyPassword.toCharArray();
KeyStore ks = buildKeyStore(certChainFile, key, keyPasswordChars);
// Set up key manager factory to use our key store
if (kmf == null) {
kmf = KeyManagerFactory.getInstance(keyAlgorithm);
}
kmf.init(ks, keyPasswordChars);
return kmf;
}
}

View File

@ -17,6 +17,7 @@
package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.internal.NativeLibraryLoader;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
@ -48,6 +49,7 @@ public final class OpenSsl {
static final Set<String> AVAILABLE_CIPHER_SUITES;
private static final Set<String> AVAILABLE_OPENSSL_CIPHER_SUITES;
private static final Set<String> AVAILABLE_JAVA_CIPHER_SUITES;
private static final boolean SUPPORTS_KEYMANAGER_FACTORY;
// Protocols
static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
@ -117,9 +119,12 @@ public final class OpenSsl {
if (cause == null) {
final Set<String> availableOpenSslCipherSuites = new LinkedHashSet<String>(128);
boolean supportsKeyManagerFactory = false;
final long aprPool = Pool.create(0);
try {
final long sslCtx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
long privateKeyBio = 0;
long certBio = 0;
try {
SSLContext.setOptions(sslCtx, SSL.SSL_OP_ALL);
SSLContext.setCipherSuite(sslCtx, "ALL");
@ -132,8 +137,22 @@ public final class OpenSsl {
}
availableOpenSslCipherSuites.add(c);
}
try {
SelfSignedCertificate cert = new SelfSignedCertificate();
certBio = OpenSslContext.toBIO(cert.cert());
SSL.setCertificateChainBio(ssl, certBio, false);
supportsKeyManagerFactory = true;
} catch (Throwable ignore) {
logger.debug("KeyManagerFactory not supported.");
}
} finally {
SSL.freeSSL(ssl);
if (privateKeyBio != 0) {
SSL.freeBIO(privateKeyBio);
}
if (certBio != 0) {
SSL.freeBIO(certBio);
}
}
} finally {
SSLContext.free(sslCtx);
@ -163,10 +182,12 @@ public final class OpenSsl {
availableCipherSuites.add(cipher);
}
AVAILABLE_CIPHER_SUITES = availableCipherSuites;
SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory;
} else {
AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
AVAILABLE_CIPHER_SUITES = Collections.emptySet();
SUPPORTS_KEYMANAGER_FACTORY = false;
}
}
@ -268,6 +289,13 @@ public final class OpenSsl {
return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite);
}
/**
* Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL.
*/
public static boolean supportsKeyManagerFactory() {
return SUPPORTS_KEYMANAGER_FACTORY;
}
static boolean isError(long errorCode) {
return errorCode != SSL.SSL_ERROR_NONE;
}

View File

@ -15,24 +15,34 @@
*/
package io.netty.handler.ssl;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.apache.tomcat.jni.CertificateRequestedCallback;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Set;
/**
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/
public final class OpenSslClientContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
private final OpenSslSessionContext sessionContext;
/**
@ -188,51 +198,37 @@ public final class OpenSslClientContext extends OpenSslContext {
ClientAuth.NONE);
boolean success = false;
try {
checkKeyManagerFactory(keyManagerFactory);
if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
throw new IllegalArgumentException(
"Either both keyCertChain and key needs to be null or none of them");
}
synchronized (OpenSslContext.class) {
if (keyCertChain != null && key != null) {
/* Load the certificate file and private key. */
long keyBio = 0;
long keyCertChainBio = 0;
try {
keyCertChainBio = toBIO(keyCertChain);
keyBio = toBIO(key);
if (!SSLContext.setCertificateBio(
ctx, keyCertChainBio, keyBio, keyPassword, SSL.SSL_AIDX_RSA)) {
long error = SSL.getLastErrorNumber();
if (OpenSsl.isError(error)) {
throw new SSLException("failed to set certificate and key: "
+ SSL.getErrorString(error));
}
try {
if (!OpenSsl.supportsKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
// We may have more then one cert in the chain so add all of them now. We must NOT skip the
// first cert when client mode.
if (!SSLContext.setCertificateChainBio(ctx, keyCertChainBio, false)) {
long error = SSL.getLastErrorNumber();
if (OpenSsl.isError(error)) {
throw new SSLException(
"failed to set certificate chain: " + SSL.getErrorString(error));
}
if (keyCertChain != null && key != null) {
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
} finally {
if (keyBio != 0) {
SSL.freeBIO(keyBio);
} else {
if (keyCertChain != null) {
keyManagerFactory = buildKeyManagerFactory(
keyCertChain, key, keyPassword, keyManagerFactory);
}
if (keyCertChainBio != 0) {
SSL.freeBIO(keyCertChainBio);
if (keyManagerFactory != null) {
X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
OpenSslKeyMaterialManager materialManager = useExtendedKeyManager(keyManager) ?
new OpenSslExtendedKeyMaterialManager(
(X509ExtendedKeyManager) keyManager, keyPassword) :
new OpenSslKeyMaterialManager(keyManager, keyPassword);
SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
engineMap, materialManager));
}
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
@ -278,6 +274,11 @@ public final class OpenSslClientContext extends OpenSslContext {
return sessionContext;
}
@Override
OpenSslKeyMaterialManager keyMaterialManager() {
return null;
}
// No cache is currently supported for client side mode.
private static final class OpenSslClientSessionContext extends OpenSslSessionContext {
private OpenSslClientSessionContext(OpenSslContext context) {
@ -348,4 +349,77 @@ public final class OpenSslClientContext extends OpenSslContext {
manager.checkServerTrusted(peerCerts, auth, engine);
}
}
private static final class OpenSslCertificateRequestedCallback implements CertificateRequestedCallback {
private final OpenSslEngineMap engineMap;
private final OpenSslKeyMaterialManager keyManagerHolder;
OpenSslCertificateRequestedCallback(OpenSslEngineMap engineMap, OpenSslKeyMaterialManager keyManagerHolder) {
this.engineMap = engineMap;
this.keyManagerHolder = keyManagerHolder;
}
@Override
public void requested(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
final OpenSslEngine engine = engineMap.get(ssl);
try {
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
final String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
final X500Principal[] issuers;
if (asn1DerEncodedPrincipals == null) {
issuers = null;
} else {
issuers = new X500Principal[asn1DerEncodedPrincipals.length];
for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
}
}
keyManagerHolder.setKeyMaterial(engine, keyTypes, issuers);
} catch (Throwable cause) {
logger.debug("request of key failed", cause);
SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
e.initCause(cause);
engine.handshakeException = e;
}
}
/**
* Gets the supported key types for client certificates.
*
* @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
* See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
* @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
* {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
*/
private static Set<String> supportedClientKeyTypes(byte[] clientCertificateTypes) {
Set<String> result = new HashSet<String>(clientCertificateTypes.length);
for (byte keyTypeCode : clientCertificateTypes) {
String keyType = clientKeyType(keyTypeCode);
if (keyType == null) {
// Unsupported client key type -- ignore
continue;
}
result.add(keyType);
}
return result;
}
private static String clientKeyType(byte clientCertificateType) {
// See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
switch (clientCertificateType) {
case CertificateRequestedCallback.TLS_CT_RSA_SIGN:
return OpenSslKeyMaterialManager.KEY_TYPE_RSA; // RFC rsa_sign
case CertificateRequestedCallback.TLS_CT_RSA_FIXED_DH:
return OpenSslKeyMaterialManager.KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
case CertificateRequestedCallback.TLS_CT_ECDSA_SIGN:
return OpenSslKeyMaterialManager.KEY_TYPE_EC; // RFC ecdsa_sign
case CertificateRequestedCallback.TLS_CT_RSA_FIXED_ECDH:
return OpenSslKeyMaterialManager.KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
case CertificateRequestedCallback.TLS_CT_ECDSA_FIXED_ECDH:
return OpenSslKeyMaterialManager.KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
default:
return null;
}
}
}
}

View File

@ -18,6 +18,7 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -26,12 +27,14 @@ import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import java.security.AccessController;
import java.security.PrivateKey;
@ -57,7 +60,7 @@ public abstract class OpenSslContext extends SslContext {
* To make it easier for users to replace JDK implemention with OpenSsl version we also use
* {@code jdk.tls.rejectClientInitiatedRenegotiation} to allow disabling client initiated renegotiation.
* Java8+ uses this system property as well.
*
* <p>
* See also <a href="http://blog.ivanristic.com/2014/03/ssl-tls-improvements-in-java-8.html">
* Significant SSL/TLS improvements in Java 8</a>
*/
@ -69,20 +72,23 @@ public abstract class OpenSslContext extends SslContext {
// TODO: Maybe make configurable ?
protected static final int VERIFY_DEPTH = 10;
/** The OpenSSL SSL_CTX object */
/**
* The OpenSSL SSL_CTX object
*/
protected volatile long ctx;
final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
long aprPool;
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
private volatile int aprPoolDestroyed;
private volatile boolean rejectRemoteInitiatedRenegotiation;
private final List<String> unmodifiableCiphers;
private final long sessionCacheSize;
private final long sessionTimeout;
private final OpenSslApplicationProtocolNegotiator apn;
private final int mode;
private final Certificate[] keyCertChain;
private final ClientAuth clientAuth;
final Certificate[] keyCertChain;
final ClientAuth clientAuth;
final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
volatile boolean rejectRemoteInitiatedRenegotiation;
static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR =
new OpenSslApplicationProtocolNegotiator() {
@ -178,7 +184,7 @@ public abstract class OpenSslContext extends SslContext {
convertedCiphers = null;
} else {
convertedCiphers = new ArrayList<String>();
for (String c: ciphers) {
for (String c : ciphers) {
if (c == null) {
break;
}
@ -247,18 +253,18 @@ public abstract class OpenSslContext extends SslContext {
int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior());
switch (apn.protocol()) {
case NPN:
SSLContext.setNpnProtos(ctx, protocols, selectorBehavior);
break;
case ALPN:
SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior);
break;
case NPN_AND_ALPN:
SSLContext.setNpnProtos(ctx, protocols, selectorBehavior);
SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior);
break;
default:
throw new Error();
case NPN:
SSLContext.setNpnProtos(ctx, protocols, selectorBehavior);
break;
case ALPN:
SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior);
break;
case NPN_AND_ALPN:
SSLContext.setNpnProtos(ctx, protocols, selectorBehavior);
SSLContext.setAlpnProtos(ctx, protocols, selectorBehavior);
break;
default:
throw new Error();
}
}
@ -294,12 +300,12 @@ public abstract class OpenSslContext extends SslContext {
private static int opensslSelectorFailureBehavior(SelectorFailureBehavior behavior) {
switch (behavior) {
case NO_ADVERTISE:
return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE;
case CHOOSE_MY_LAST_PROTOCOL:
return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL;
default:
throw new Error();
case NO_ADVERTISE:
return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE;
case CHOOSE_MY_LAST_PROTOCOL:
return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL;
default:
throw new Error();
}
}
@ -330,10 +336,11 @@ public abstract class OpenSslContext extends SslContext {
@Override
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
return new OpenSslEngine(ctx, alloc, isClient(), sessionContext(), apn, engineMap,
rejectRemoteInitiatedRenegotiation, peerHost, peerPort, keyCertChain, clientAuth);
return new OpenSslEngine(this, alloc, peerHost, peerPort);
}
abstract OpenSslKeyMaterialManager keyMaterialManager();
/**
* Returns a new server-side {@link SSLEngine} with the current configuration.
*/
@ -356,6 +363,7 @@ public abstract class OpenSslContext extends SslContext {
/**
* Returns the stats of this context.
*
* @deprecated use {@link #sessionContext#stats()}
*/
@Deprecated
@ -380,6 +388,7 @@ public abstract class OpenSslContext extends SslContext {
/**
* Sets the SSL session ticket keys of this context.
*
* @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
*/
@Deprecated
@ -434,9 +443,19 @@ public abstract class OpenSslContext extends SslContext {
throw new IllegalStateException("no X509TrustManager found");
}
protected static X509KeyManager chooseX509KeyManager(KeyManager[] kms) {
for (KeyManager km : kms) {
if (km instanceof X509KeyManager) {
return (X509KeyManager) km;
}
}
throw new IllegalStateException("no X509KeyManager found");
}
/**
* Translate a {@link ApplicationProtocolConfig} object to a
* {@link OpenSslApplicationProtocolNegotiator} object.
*
* @param config The configuration which defines the translation
* @return The results of the translation
*/
@ -446,38 +465,42 @@ public abstract class OpenSslContext extends SslContext {
}
switch (config.protocol()) {
case NONE:
return NONE_PROTOCOL_NEGOTIATOR;
case ALPN:
case NPN:
case NPN_AND_ALPN:
switch (config.selectedListenerFailureBehavior()) {
case CHOOSE_MY_LAST_PROTOCOL:
case ACCEPT:
switch (config.selectorFailureBehavior()) {
case CHOOSE_MY_LAST_PROTOCOL:
case NO_ADVERTISE:
return new OpenSslDefaultApplicationProtocolNegotiator(
config);
default:
throw new UnsupportedOperationException(
new StringBuilder("OpenSSL provider does not support ")
.append(config.selectorFailureBehavior())
.append(" behavior").toString());
case NONE:
return NONE_PROTOCOL_NEGOTIATOR;
case ALPN:
case NPN:
case NPN_AND_ALPN:
switch (config.selectedListenerFailureBehavior()) {
case CHOOSE_MY_LAST_PROTOCOL:
case ACCEPT:
switch (config.selectorFailureBehavior()) {
case CHOOSE_MY_LAST_PROTOCOL:
case NO_ADVERTISE:
return new OpenSslDefaultApplicationProtocolNegotiator(
config);
default:
throw new UnsupportedOperationException(
new StringBuilder("OpenSSL provider does not support ")
.append(config.selectorFailureBehavior())
.append(" behavior").toString());
}
default:
throw new UnsupportedOperationException(
new StringBuilder("OpenSSL provider does not support ")
.append(config.selectedListenerFailureBehavior())
.append(" behavior").toString());
}
default:
throw new UnsupportedOperationException(
new StringBuilder("OpenSSL provider does not support ")
.append(config.selectedListenerFailureBehavior())
.append(" behavior").toString());
}
default:
throw new Error();
throw new Error();
}
}
static boolean useExtendedTrustManager(X509TrustManager trustManager) {
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
}
static boolean useExtendedKeyManager(X509KeyManager keyManager) {
return PlatformDependent.javaVersion() >= 7 && keyManager instanceof X509ExtendedKeyManager;
}
abstract static class AbstractCertificateVerifier implements CertificateVerifier {
@ -490,7 +513,7 @@ public abstract class OpenSslContext extends SslContext {
@Override
public final int verify(long ssl, byte[][] chain, String auth) {
X509Certificate[] peerCerts = certificates(chain);
final OpenSslEngine engine = engineMap.remove(ssl);
final OpenSslEngine engine = engineMap.get(ssl);
try {
verify(engine, peerCerts, auth);
return CertificateVerifier.X509_V_OK;
@ -521,6 +544,7 @@ public abstract class OpenSslContext extends SslContext {
private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
private final Map<Long, OpenSslEngine> engines = PlatformDependent.newConcurrentHashMap();
@Override
public OpenSslEngine remove(long ssl) {
return engines.remove(ssl);
@ -530,8 +554,41 @@ public abstract class OpenSslContext extends SslContext {
public void add(OpenSslEngine engine) {
engines.put(engine.sslPointer(), engine);
}
@Override
public OpenSslEngine get(long ssl) {
return engines.get(ssl);
}
}
static void setKeyMaterial(long ctx, X509Certificate[] keyCertChain, PrivateKey key, String keyPassword)
throws SSLException {
/* Load the certificate file and private key. */
long keyBio = 0;
long keyCertChainBio = 0;
try {
keyCertChainBio = toBIO(keyCertChain);
keyBio = toBIO(key);
SSLContext.setCertificateBio(
ctx, keyCertChainBio, keyBio,
keyPassword == null ? StringUtil.EMPTY_STRING : keyPassword, SSL.SSL_AIDX_RSA);
// We may have more then one cert in the chain so add all of them now.
SSLContext.setCertificateChainBio(ctx, keyCertChainBio, false);
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
} finally {
if (keyBio != 0) {
SSL.freeBIO(keyBio);
}
if (keyCertChainBio != 0) {
SSL.freeBIO(keyCertChainBio);
}
}
}
/**
* Return the pointer to a <a href="https://www.openssl.org/docs/crypto/BIO_get_mem_ptr.html">in-memory BIO</a>
* or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}.
@ -554,7 +611,7 @@ public abstract class OpenSslContext extends SslContext {
* Return the pointer to a <a href="https://www.openssl.org/docs/crypto/BIO_get_mem_ptr.html">in-memory BIO</a>
* or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}.
*/
static long toBIO(X509Certificate[] certChain) throws Exception {
static long toBIO(X509Certificate... certChain) throws Exception {
if (certChain == null) {
return 0;
}
@ -615,11 +672,4 @@ public abstract class OpenSslContext extends SslContext {
buffer.release();
}
}
static void checkKeyManagerFactory(KeyManagerFactory keyManagerFactory) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory is currently not supported with OpenSslContext");
}
}
}

View File

@ -172,6 +172,7 @@ public final class OpenSslEngine extends SSLEngine {
// OpenSSL state
private long ssl;
private long networkBIO;
private boolean certificateSet;
private enum HandshakeState {
/**
@ -221,64 +222,35 @@ public final class OpenSslEngine extends SSLEngine {
private final Certificate[] localCerts;
private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1];
private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1];
private final OpenSslKeyMaterialManager keyMaterialManager;
// This is package-private as we set it from OpenSslContext if an exception is thrown during
// the verification step.
SSLHandshakeException handshakeException;
/**
* Creates a new instance
*
* @param sslCtx an OpenSSL {@code SSL_CTX} object
* @param alloc the {@link ByteBufAllocator} that will be used by this engine
*/
@Deprecated
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc,
@SuppressWarnings("unused") String fallbackApplicationProtocol) {
this(sslCtx, alloc, false, null, OpenSslContext.NONE_PROTOCOL_NEGOTIATOR, OpenSslEngineMap.EMPTY, false,
ClientAuth.NONE);
}
OpenSslEngine(long sslCtx, ByteBufAllocator alloc,
boolean clientMode, OpenSslSessionContext sessionContext,
OpenSslApplicationProtocolNegotiator apn, OpenSslEngineMap engineMap,
boolean rejectRemoteInitiatedRenegation,
ClientAuth clientAuth) {
this(sslCtx, alloc, clientMode, sessionContext, apn, engineMap, rejectRemoteInitiatedRenegation, null, -1,
null, clientAuth);
}
OpenSslEngine(long sslCtx, ByteBufAllocator alloc,
boolean clientMode, OpenSslSessionContext sessionContext,
OpenSslApplicationProtocolNegotiator apn, OpenSslEngineMap engineMap,
boolean rejectRemoteInitiatedRenegation, String peerHost, int peerPort,
Certificate[] localCerts,
ClientAuth clientAuth) {
OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort) {
super(peerHost, peerPort);
OpenSsl.ensureAvailability();
if (sslCtx == 0) {
throw new NullPointerException("sslCtx");
}
this.alloc = checkNotNull(alloc, "alloc");
this.apn = checkNotNull(apn, "apn");
ssl = SSL.newSSL(sslCtx, !clientMode);
session = new OpenSslSession(sessionContext);
apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
ssl = SSL.newSSL(context.ctx, !context.isClient());
session = new OpenSslSession(context.sessionContext());
networkBIO = SSL.makeNetworkBIO(ssl);
this.clientMode = clientMode;
this.engineMap = engineMap;
this.rejectRemoteInitiatedRenegation = rejectRemoteInitiatedRenegation;
this.localCerts = localCerts;
clientMode = context.isClient();
engineMap = context.engineMap;
rejectRemoteInitiatedRenegation = context.rejectRemoteInitiatedRenegotiation;
localCerts = context.keyCertChain;
// Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the
// needed JNI methods.
setClientAuth(clientMode ? ClientAuth.NONE : checkNotNull(clientAuth, "clientAuth"));
setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth);
// Use SNI if peerHost was specified
// See https://github.com/netty/netty/issues/4746
if (clientMode && peerHost != null) {
SSL.setTlsExtHostName(ssl, peerHost);
}
keyMaterialManager = context.keyMaterialManager();
}
@Override
@ -1287,6 +1259,11 @@ public final class OpenSslEngine extends SSLEngine {
lastAccessed = System.currentTimeMillis();
}
if (!certificateSet && keyMaterialManager != null) {
certificateSet = true;
keyMaterialManager.setKeyMaterial(this);
}
int code = SSL.doHandshake(ssl);
if (code <= 0) {
// Check if we have a pending exception that was created during the handshake and if so throw it after
@ -1311,6 +1288,7 @@ public final class OpenSslEngine extends SSLEngine {
}
// if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished.
session.handshakeFinished();
engineMap.remove(ssl);
return FINISHED;
}

View File

@ -17,18 +17,6 @@ package io.netty.handler.ssl;
interface OpenSslEngineMap {
OpenSslEngineMap EMPTY = new OpenSslEngineMap() {
@Override
public OpenSslEngine remove(long ssl) {
return null;
}
@Override
public void add(OpenSslEngine engine) {
// NOOP
}
};
/**
* Remove the {@link OpenSslEngine} with the given {@code ssl} address and
* return it.
@ -39,4 +27,9 @@ interface OpenSslEngineMap {
* Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}.
*/
void add(OpenSslEngine engine);
/**
* Get the {@link OpenSslEngine} for the given {@code ssl} address.
*/
OpenSslEngine get(long ssl);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2016 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 javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
final class OpenSslExtendedKeyMaterialManager extends OpenSslKeyMaterialManager {
private final X509ExtendedKeyManager keyManager;
OpenSslExtendedKeyMaterialManager(X509ExtendedKeyManager keyManager, String password) {
super(keyManager, password);
this.keyManager = keyManager;
}
@Override
protected String chooseClientAlias(OpenSslEngine engine, String[] keyTypes, X500Principal[] issuer) {
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
}
@Override
protected String chooseServerAlias(OpenSslEngine engine, String type) {
return keyManager.chooseEngineServerAlias(type, null, engine);
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2016 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 org.apache.tomcat.jni.SSL;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509KeyManager;
import javax.security.auth.x500.X500Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
/**
* Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and
* {@link X509Certificate}s.
*/
class OpenSslKeyMaterialManager {
// Code in this class is inspired by code of conscrypts:
// - https://android.googlesource.com/platform/external/
// conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
// - https://android.googlesource.com/platform/external/
// conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java
//
static final String KEY_TYPE_RSA = "RSA";
static final String KEY_TYPE_DH_RSA = "DH_RSA";
static final String KEY_TYPE_EC = "EC";
static final String KEY_TYPE_EC_EC = "EC_EC";
static final String KEY_TYPE_EC_RSA = "EC_RSA";
// key type mappings for types.
private static final Map<String, String> KEY_TYPES = new HashMap<String, String>();
static {
KEY_TYPES.put("RSA", KEY_TYPE_RSA);
KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA);
KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA);
KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC);
KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA);
KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC);
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
}
private final X509KeyManager keyManager;
private final String password;
OpenSslKeyMaterialManager(X509KeyManager keyManager, String password) {
this.keyManager = keyManager;
this.password = password;
}
void setKeyMaterial(OpenSslEngine engine) throws SSLException {
long ssl = engine.sslPointer();
String[] authMethods = SSL.authenticationMethods(ssl);
for (String authMethod : authMethods) {
String type = KEY_TYPES.get(authMethod);
if (type != null) {
setKeyMaterial(ssl, chooseServerAlias(engine, type));
}
}
}
void setKeyMaterial(OpenSslEngine engine, String[] keyTypes, X500Principal[] issuer) throws SSLException {
setKeyMaterial(engine.sslPointer(), chooseClientAlias(engine, keyTypes, issuer));
}
private void setKeyMaterial(long ssl, String alias) throws SSLException {
long keyBio = 0;
long keyCertChainBio = 0;
try {
// TODO: Should we cache these and so not need to do a memory copy all the time ?
PrivateKey key = keyManager.getPrivateKey(alias);
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
if (certificates != null && certificates.length != 0) {
keyCertChainBio = OpenSslContext.toBIO(keyManager.getCertificateChain(alias));
if (key != null) {
keyBio = OpenSslContext.toBIO(key);
}
SSL.setCertificateBio(ssl, keyCertChainBio, keyBio, password);
// We may have more then one cert in the chain so add all of them now.
SSL.setCertificateChainBio(ssl, keyCertChainBio, false);
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException(e);
} finally {
if (keyBio != 0) {
SSL.freeBIO(keyBio);
}
if (keyCertChainBio != 0) {
SSL.freeBIO(keyCertChainBio);
}
}
}
protected String chooseClientAlias(@SuppressWarnings("unused") OpenSslEngine engine,
String[] keyTypes, X500Principal[] issuer) {
return keyManager.chooseClientAlias(keyTypes, issuer, null);
}
protected String chooseServerAlias(@SuppressWarnings("unused") OpenSslEngine engine, String type) {
return keyManager.chooseServerAlias(type, null, null);
}
}

View File

@ -23,7 +23,9 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.security.KeyStore;
@ -38,6 +40,7 @@ import static io.netty.util.internal.ObjectUtil.*;
public final class OpenSslServerContext extends OpenSslContext {
private static final byte[] ID = new byte[] {'n', 'e', 't', 't', 'y'};
private final OpenSslServerSessionContext sessionContext;
private final OpenSslKeyMaterialManager keyMaterialManager;
/**
* Creates a new instance.
@ -346,63 +349,43 @@ public final class OpenSslServerContext extends OpenSslContext {
// Create a new SSL_CTX and configure it.
boolean success = false;
try {
checkKeyManagerFactory(keyManagerFactory);
checkNotNull(keyCertChain, "keyCertChainFile");
checkNotNull(key, "keyFile");
if (keyPassword == null) {
keyPassword = "";
}
synchronized (OpenSslContext.class) {
/* Set certificate verification policy. */
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
long keyCertChainBio = 0;
try {
keyCertChainBio = toBIO(keyCertChain);
/* Load the certificate chain. We must skip the first cert when server mode */
if (!SSLContext.setCertificateChainBio(ctx, keyCertChainBio, true)) {
long error = SSL.getLastErrorNumber();
if (OpenSsl.isError(error)) {
String err = SSL.getErrorString(error);
throw new SSLException(
"failed to set certificate chain: " + err);
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
if (!OpenSsl.supportsKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
}
} catch (Exception e) {
throw new SSLException(
"failed to set certificate chain", e);
} finally {
if (keyCertChainBio != 0) {
SSL.freeBIO(keyCertChainBio);
}
}
/* Load the certificate file and private key. */
long keyBio = 0;
keyCertChainBio = 0;
try {
keyBio = toBIO(key);
keyCertChainBio = toBIO(keyCertChain);
if (!SSLContext.setCertificateBio(
ctx, keyCertChainBio, keyBio, keyPassword, SSL.SSL_AIDX_RSA)) {
long error = SSL.getLastErrorNumber();
if (OpenSsl.isError(error)) {
String err = SSL.getErrorString(error);
throw new SSLException("failed to set certificate and key: " + err);
/* Set certificate verification policy. */
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
keyMaterialManager = null;
} else {
if (keyCertChain != null) {
keyManagerFactory = buildKeyManagerFactory(
keyCertChain, key, keyPassword, keyManagerFactory);
}
if (keyManagerFactory != null) {
X509KeyManager keyManager = chooseX509KeyManager(
buildKeyManagerFactory(keyCertChain, key, keyPassword, keyManagerFactory)
.getKeyManagers());
keyMaterialManager = useExtendedKeyManager(keyManager) ?
new OpenSslExtendedKeyMaterialManager(
(X509ExtendedKeyManager) keyManager, keyPassword) :
new OpenSslKeyMaterialManager(keyManager, keyPassword);
} else {
keyMaterialManager = null;
}
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
} finally {
if (keyBio != 0) {
SSL.freeBIO(keyBio);
}
if (keyCertChainBio != 0) {
SSL.freeBIO(keyCertChainBio);
}
}
try {
if (trustCertCollection != null) {
@ -448,6 +431,11 @@ public final class OpenSslServerContext extends OpenSslContext {
return sessionContext;
}
@Override
OpenSslKeyMaterialManager keyMaterialManager() {
return keyMaterialManager;
}
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
private final X509TrustManager manager;

View File

@ -24,6 +24,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.util.internal.EmptyArrays;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
@ -51,6 +52,8 @@ import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@ -1034,4 +1037,38 @@ public abstract class SslContext {
throw new SSLException(e);
}
}
static KeyManagerFactory buildKeyManagerFactory(X509Certificate[] certChain, PrivateKey key, String keyPassword,
KeyManagerFactory kmf)
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
return buildKeyManagerFactory(certChain, algorithm, key, keyPassword, kmf);
}
static KeyManagerFactory buildKeyManagerFactory(X509Certificate[] certChainFile,
String keyAlgorithm, PrivateKey key,
String keyPassword, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, IOException,
CertificateException, UnrecoverableKeyException {
char[] keyPasswordChars = keyPassword == null ? EmptyArrays.EMPTY_CHARS : keyPassword.toCharArray();
KeyStore ks = buildKeyStore(certChainFile, key, keyPasswordChars);
// Set up key manager factory to use our key store
if (kmf == null) {
kmf = KeyManagerFactory.getInstance(keyAlgorithm);
}
kmf.init(ks, keyPasswordChars);
return kmf;
}
static KeyManagerFactory buildDefaultKeyManagerFactory()
throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(null, null);
return keyManagerFactory;
}
}