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:
parent
3a69adfefb
commit
5e64985089
@ -341,17 +341,6 @@ public class JdkSslContext extends SslContext {
|
|||||||
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
|
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,
|
* Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
|
||||||
* and a certificate chain.
|
* and a certificate chain.
|
||||||
@ -375,20 +364,4 @@ public class JdkSslContext extends SslContext {
|
|||||||
return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm,
|
return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm,
|
||||||
toPrivateKey(keyFile, keyPassword), keyPassword, kmf);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||||
import io.netty.util.internal.NativeLibraryLoader;
|
import io.netty.util.internal.NativeLibraryLoader;
|
||||||
import io.netty.util.internal.SystemPropertyUtil;
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
@ -48,6 +49,7 @@ public final class OpenSsl {
|
|||||||
static final Set<String> AVAILABLE_CIPHER_SUITES;
|
static final Set<String> AVAILABLE_CIPHER_SUITES;
|
||||||
private static final Set<String> AVAILABLE_OPENSSL_CIPHER_SUITES;
|
private static final Set<String> AVAILABLE_OPENSSL_CIPHER_SUITES;
|
||||||
private static final Set<String> AVAILABLE_JAVA_CIPHER_SUITES;
|
private static final Set<String> AVAILABLE_JAVA_CIPHER_SUITES;
|
||||||
|
private static final boolean SUPPORTS_KEYMANAGER_FACTORY;
|
||||||
|
|
||||||
// Protocols
|
// Protocols
|
||||||
static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
|
static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
|
||||||
@ -117,9 +119,12 @@ public final class OpenSsl {
|
|||||||
|
|
||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
final Set<String> availableOpenSslCipherSuites = new LinkedHashSet<String>(128);
|
final Set<String> availableOpenSslCipherSuites = new LinkedHashSet<String>(128);
|
||||||
|
boolean supportsKeyManagerFactory = false;
|
||||||
final long aprPool = Pool.create(0);
|
final long aprPool = Pool.create(0);
|
||||||
try {
|
try {
|
||||||
final long sslCtx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
|
final long sslCtx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
|
||||||
|
long privateKeyBio = 0;
|
||||||
|
long certBio = 0;
|
||||||
try {
|
try {
|
||||||
SSLContext.setOptions(sslCtx, SSL.SSL_OP_ALL);
|
SSLContext.setOptions(sslCtx, SSL.SSL_OP_ALL);
|
||||||
SSLContext.setCipherSuite(sslCtx, "ALL");
|
SSLContext.setCipherSuite(sslCtx, "ALL");
|
||||||
@ -132,8 +137,22 @@ public final class OpenSsl {
|
|||||||
}
|
}
|
||||||
availableOpenSslCipherSuites.add(c);
|
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 {
|
} finally {
|
||||||
SSL.freeSSL(ssl);
|
SSL.freeSSL(ssl);
|
||||||
|
if (privateKeyBio != 0) {
|
||||||
|
SSL.freeBIO(privateKeyBio);
|
||||||
|
}
|
||||||
|
if (certBio != 0) {
|
||||||
|
SSL.freeBIO(certBio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
SSLContext.free(sslCtx);
|
SSLContext.free(sslCtx);
|
||||||
@ -163,10 +182,12 @@ public final class OpenSsl {
|
|||||||
availableCipherSuites.add(cipher);
|
availableCipherSuites.add(cipher);
|
||||||
}
|
}
|
||||||
AVAILABLE_CIPHER_SUITES = availableCipherSuites;
|
AVAILABLE_CIPHER_SUITES = availableCipherSuites;
|
||||||
|
SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory;
|
||||||
} else {
|
} else {
|
||||||
AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
|
AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
|
||||||
AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
|
AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
|
||||||
AVAILABLE_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);
|
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) {
|
static boolean isError(long errorCode) {
|
||||||
return errorCode != SSL.SSL_ERROR_NONE;
|
return errorCode != SSL.SSL_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,34 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.ssl;
|
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.SSL;
|
||||||
import org.apache.tomcat.jni.SSLContext;
|
import org.apache.tomcat.jni.SSLContext;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.X509Certificate;
|
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.
|
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||||
*/
|
*/
|
||||||
public final class OpenSslClientContext extends OpenSslContext {
|
public final class OpenSslClientContext extends OpenSslContext {
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
|
||||||
private final OpenSslSessionContext sessionContext;
|
private final OpenSslSessionContext sessionContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,51 +198,37 @@ public final class OpenSslClientContext extends OpenSslContext {
|
|||||||
ClientAuth.NONE);
|
ClientAuth.NONE);
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
checkKeyManagerFactory(keyManagerFactory);
|
|
||||||
if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
|
if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Either both keyCertChain and key needs to be null or none of them");
|
"Either both keyCertChain and key needs to be null or none of them");
|
||||||
}
|
}
|
||||||
synchronized (OpenSslContext.class) {
|
synchronized (OpenSslContext.class) {
|
||||||
if (keyCertChain != null && key != null) {
|
|
||||||
/* Load the certificate file and private key. */
|
|
||||||
long keyBio = 0;
|
|
||||||
long keyCertChainBio = 0;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
keyCertChainBio = toBIO(keyCertChain);
|
if (!OpenSsl.supportsKeyManagerFactory()) {
|
||||||
|
if (keyManagerFactory != null) {
|
||||||
keyBio = toBIO(key);
|
throw new IllegalArgumentException(
|
||||||
|
"KeyManagerFactory not supported");
|
||||||
if (!SSLContext.setCertificateBio(
|
}
|
||||||
ctx, keyCertChainBio, keyBio, keyPassword, SSL.SSL_AIDX_RSA)) {
|
if (keyCertChain != null && key != null) {
|
||||||
long error = SSL.getLastErrorNumber();
|
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
||||||
if (OpenSsl.isError(error)) {
|
}
|
||||||
throw new SSLException("failed to set certificate and key: "
|
} else {
|
||||||
+ SSL.getErrorString(error));
|
if (keyCertChain != null) {
|
||||||
|
keyManagerFactory = buildKeyManagerFactory(
|
||||||
|
keyCertChain, key, keyPassword, keyManagerFactory);
|
||||||
|
}
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SSLException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SSLException("failed to set certificate and key", e);
|
throw new SSLException("failed to set certificate and key", e);
|
||||||
} finally {
|
|
||||||
if (keyBio != 0) {
|
|
||||||
SSL.freeBIO(keyBio);
|
|
||||||
}
|
|
||||||
if (keyCertChainBio != 0) {
|
|
||||||
SSL.freeBIO(keyCertChainBio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
|
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
|
||||||
@ -278,6 +274,11 @@ public final class OpenSslClientContext extends OpenSslContext {
|
|||||||
return sessionContext;
|
return sessionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
OpenSslKeyMaterialManager keyMaterialManager() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// No cache is currently supported for client side mode.
|
// No cache is currently supported for client side mode.
|
||||||
private static final class OpenSslClientSessionContext extends OpenSslSessionContext {
|
private static final class OpenSslClientSessionContext extends OpenSslSessionContext {
|
||||||
private OpenSslClientSessionContext(OpenSslContext context) {
|
private OpenSslClientSessionContext(OpenSslContext context) {
|
||||||
@ -348,4 +349,77 @@ public final class OpenSslClientContext extends OpenSslContext {
|
|||||||
manager.checkServerTrusted(peerCerts, auth, engine);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.ssl;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
import io.netty.util.internal.SystemPropertyUtil;
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
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;
|
||||||
@ -26,12 +27,14 @@ 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 org.apache.tomcat.jni.SSLContext;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivateKey;
|
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
|
* 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.
|
* {@code jdk.tls.rejectClientInitiatedRenegotiation} to allow disabling client initiated renegotiation.
|
||||||
* Java8+ uses this system property as well.
|
* 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">
|
* 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>
|
* Significant SSL/TLS improvements in Java 8</a>
|
||||||
*/
|
*/
|
||||||
@ -69,20 +72,23 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
// TODO: Maybe make configurable ?
|
// TODO: Maybe make configurable ?
|
||||||
protected static final int VERIFY_DEPTH = 10;
|
protected static final int VERIFY_DEPTH = 10;
|
||||||
|
|
||||||
/** The OpenSSL SSL_CTX object */
|
/**
|
||||||
|
* The OpenSSL SSL_CTX object
|
||||||
|
*/
|
||||||
protected volatile long ctx;
|
protected volatile long ctx;
|
||||||
final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
|
|
||||||
long aprPool;
|
long aprPool;
|
||||||
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
|
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
|
||||||
private volatile int aprPoolDestroyed;
|
private volatile int aprPoolDestroyed;
|
||||||
private volatile boolean rejectRemoteInitiatedRenegotiation;
|
|
||||||
private final List<String> unmodifiableCiphers;
|
private final List<String> unmodifiableCiphers;
|
||||||
private final long sessionCacheSize;
|
private final long sessionCacheSize;
|
||||||
private final long sessionTimeout;
|
private final long sessionTimeout;
|
||||||
private final OpenSslApplicationProtocolNegotiator apn;
|
private final OpenSslApplicationProtocolNegotiator apn;
|
||||||
private final int mode;
|
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 =
|
static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR =
|
||||||
new OpenSslApplicationProtocolNegotiator() {
|
new OpenSslApplicationProtocolNegotiator() {
|
||||||
@ -178,7 +184,7 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
convertedCiphers = null;
|
convertedCiphers = null;
|
||||||
} else {
|
} else {
|
||||||
convertedCiphers = new ArrayList<String>();
|
convertedCiphers = new ArrayList<String>();
|
||||||
for (String c: ciphers) {
|
for (String c : ciphers) {
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -330,10 +336,11 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||||
return new OpenSslEngine(ctx, alloc, isClient(), sessionContext(), apn, engineMap,
|
return new OpenSslEngine(this, alloc, peerHost, peerPort);
|
||||||
rejectRemoteInitiatedRenegotiation, peerHost, peerPort, keyCertChain, clientAuth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract OpenSslKeyMaterialManager keyMaterialManager();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new server-side {@link SSLEngine} with the current configuration.
|
* 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.
|
* Returns the stats of this context.
|
||||||
|
*
|
||||||
* @deprecated use {@link #sessionContext#stats()}
|
* @deprecated use {@link #sessionContext#stats()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -380,6 +388,7 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the SSL session ticket keys of this context.
|
* Sets the SSL session ticket keys of this context.
|
||||||
|
*
|
||||||
* @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
|
* @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -434,9 +443,19 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
throw new IllegalStateException("no X509TrustManager found");
|
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
|
* Translate a {@link ApplicationProtocolConfig} object to a
|
||||||
* {@link OpenSslApplicationProtocolNegotiator} object.
|
* {@link OpenSslApplicationProtocolNegotiator} object.
|
||||||
|
*
|
||||||
* @param config The configuration which defines the translation
|
* @param config The configuration which defines the translation
|
||||||
* @return The results of the translation
|
* @return The results of the translation
|
||||||
*/
|
*/
|
||||||
@ -480,6 +499,10 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
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 {
|
abstract static class AbstractCertificateVerifier implements CertificateVerifier {
|
||||||
private final OpenSslEngineMap engineMap;
|
private final OpenSslEngineMap engineMap;
|
||||||
|
|
||||||
@ -490,7 +513,7 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
@Override
|
@Override
|
||||||
public final int verify(long ssl, byte[][] chain, String auth) {
|
public final int verify(long ssl, byte[][] chain, String auth) {
|
||||||
X509Certificate[] peerCerts = certificates(chain);
|
X509Certificate[] peerCerts = certificates(chain);
|
||||||
final OpenSslEngine engine = engineMap.remove(ssl);
|
final OpenSslEngine engine = engineMap.get(ssl);
|
||||||
try {
|
try {
|
||||||
verify(engine, peerCerts, auth);
|
verify(engine, peerCerts, auth);
|
||||||
return CertificateVerifier.X509_V_OK;
|
return CertificateVerifier.X509_V_OK;
|
||||||
@ -521,6 +544,7 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
|
|
||||||
private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
|
private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
|
||||||
private final Map<Long, OpenSslEngine> engines = PlatformDependent.newConcurrentHashMap();
|
private final Map<Long, OpenSslEngine> engines = PlatformDependent.newConcurrentHashMap();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OpenSslEngine remove(long ssl) {
|
public OpenSslEngine remove(long ssl) {
|
||||||
return engines.remove(ssl);
|
return engines.remove(ssl);
|
||||||
@ -530,8 +554,41 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
public void add(OpenSslEngine engine) {
|
public void add(OpenSslEngine engine) {
|
||||||
engines.put(engine.sslPointer(), 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>
|
* 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}.
|
* 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>
|
* 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}.
|
* 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) {
|
if (certChain == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -615,11 +672,4 @@ public abstract class OpenSslContext extends SslContext {
|
|||||||
buffer.release();
|
buffer.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void checkKeyManagerFactory(KeyManagerFactory keyManagerFactory) {
|
|
||||||
if (keyManagerFactory != null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"KeyManagerFactory is currently not supported with OpenSslContext");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
// OpenSSL state
|
// OpenSSL state
|
||||||
private long ssl;
|
private long ssl;
|
||||||
private long networkBIO;
|
private long networkBIO;
|
||||||
|
private boolean certificateSet;
|
||||||
|
|
||||||
private enum HandshakeState {
|
private enum HandshakeState {
|
||||||
/**
|
/**
|
||||||
@ -221,64 +222,35 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
private final Certificate[] localCerts;
|
private final Certificate[] localCerts;
|
||||||
private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1];
|
private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1];
|
||||||
private final ByteBuffer[] singleDstBuffer = 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
|
// This is package-private as we set it from OpenSslContext if an exception is thrown during
|
||||||
// the verification step.
|
// the verification step.
|
||||||
SSLHandshakeException handshakeException;
|
SSLHandshakeException handshakeException;
|
||||||
|
|
||||||
/**
|
OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||||
* 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) {
|
|
||||||
super(peerHost, peerPort);
|
super(peerHost, peerPort);
|
||||||
OpenSsl.ensureAvailability();
|
OpenSsl.ensureAvailability();
|
||||||
if (sslCtx == 0) {
|
|
||||||
throw new NullPointerException("sslCtx");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.alloc = checkNotNull(alloc, "alloc");
|
this.alloc = checkNotNull(alloc, "alloc");
|
||||||
this.apn = checkNotNull(apn, "apn");
|
apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
|
||||||
ssl = SSL.newSSL(sslCtx, !clientMode);
|
ssl = SSL.newSSL(context.ctx, !context.isClient());
|
||||||
session = new OpenSslSession(sessionContext);
|
session = new OpenSslSession(context.sessionContext());
|
||||||
networkBIO = SSL.makeNetworkBIO(ssl);
|
networkBIO = SSL.makeNetworkBIO(ssl);
|
||||||
this.clientMode = clientMode;
|
clientMode = context.isClient();
|
||||||
this.engineMap = engineMap;
|
engineMap = context.engineMap;
|
||||||
this.rejectRemoteInitiatedRenegation = rejectRemoteInitiatedRenegation;
|
rejectRemoteInitiatedRenegation = context.rejectRemoteInitiatedRenegotiation;
|
||||||
this.localCerts = localCerts;
|
localCerts = context.keyCertChain;
|
||||||
|
|
||||||
// Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the
|
// Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the
|
||||||
// needed JNI methods.
|
// needed JNI methods.
|
||||||
setClientAuth(clientMode ? ClientAuth.NONE : checkNotNull(clientAuth, "clientAuth"));
|
setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth);
|
||||||
|
|
||||||
// Use SNI if peerHost was specified
|
// Use SNI if peerHost was specified
|
||||||
// See https://github.com/netty/netty/issues/4746
|
// See https://github.com/netty/netty/issues/4746
|
||||||
if (clientMode && peerHost != null) {
|
if (clientMode && peerHost != null) {
|
||||||
SSL.setTlsExtHostName(ssl, peerHost);
|
SSL.setTlsExtHostName(ssl, peerHost);
|
||||||
}
|
}
|
||||||
|
keyMaterialManager = context.keyMaterialManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1287,6 +1259,11 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
lastAccessed = System.currentTimeMillis();
|
lastAccessed = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!certificateSet && keyMaterialManager != null) {
|
||||||
|
certificateSet = true;
|
||||||
|
keyMaterialManager.setKeyMaterial(this);
|
||||||
|
}
|
||||||
|
|
||||||
int code = SSL.doHandshake(ssl);
|
int code = SSL.doHandshake(ssl);
|
||||||
if (code <= 0) {
|
if (code <= 0) {
|
||||||
// Check if we have a pending exception that was created during the handshake and if so throw it after
|
// 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.
|
// if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished.
|
||||||
session.handshakeFinished();
|
session.handshakeFinished();
|
||||||
|
engineMap.remove(ssl);
|
||||||
return FINISHED;
|
return FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,18 +17,6 @@ package io.netty.handler.ssl;
|
|||||||
|
|
||||||
interface OpenSslEngineMap {
|
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
|
* Remove the {@link OpenSslEngine} with the given {@code ssl} address and
|
||||||
* return it.
|
* return it.
|
||||||
@ -39,4 +27,9 @@ interface OpenSslEngineMap {
|
|||||||
* Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}.
|
* Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}.
|
||||||
*/
|
*/
|
||||||
void add(OpenSslEngine engine);
|
void add(OpenSslEngine engine);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link OpenSslEngine} for the given {@code ssl} address.
|
||||||
|
*/
|
||||||
|
OpenSslEngine get(long ssl);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,9 @@ import javax.net.ssl.KeyManagerFactory;
|
|||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
@ -38,6 +40,7 @@ import static io.netty.util.internal.ObjectUtil.*;
|
|||||||
public final class OpenSslServerContext extends OpenSslContext {
|
public final class OpenSslServerContext extends OpenSslContext {
|
||||||
private static final byte[] ID = new byte[] {'n', 'e', 't', 't', 'y'};
|
private static final byte[] ID = new byte[] {'n', 'e', 't', 't', 'y'};
|
||||||
private final OpenSslServerSessionContext sessionContext;
|
private final OpenSslServerSessionContext sessionContext;
|
||||||
|
private final OpenSslKeyMaterialManager keyMaterialManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
@ -346,63 +349,43 @@ public final class OpenSslServerContext extends OpenSslContext {
|
|||||||
// Create a new SSL_CTX and configure it.
|
// Create a new SSL_CTX and configure it.
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
checkKeyManagerFactory(keyManagerFactory);
|
|
||||||
checkNotNull(keyCertChain, "keyCertChainFile");
|
checkNotNull(keyCertChain, "keyCertChainFile");
|
||||||
checkNotNull(key, "keyFile");
|
checkNotNull(key, "keyFile");
|
||||||
|
|
||||||
if (keyPassword == null) {
|
synchronized (OpenSslContext.class) {
|
||||||
keyPassword = "";
|
try {
|
||||||
|
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
||||||
|
if (!OpenSsl.supportsKeyManagerFactory()) {
|
||||||
|
if (keyManagerFactory != null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"KeyManagerFactory not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (OpenSslContext.class) {
|
|
||||||
/* Set certificate verification policy. */
|
/* Set certificate verification policy. */
|
||||||
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
||||||
long keyCertChainBio = 0;
|
|
||||||
try {
|
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
||||||
keyCertChainBio = toBIO(keyCertChain);
|
keyMaterialManager = null;
|
||||||
/* Load the certificate chain. We must skip the first cert when server mode */
|
} else {
|
||||||
if (!SSLContext.setCertificateChainBio(ctx, keyCertChainBio, true)) {
|
if (keyCertChain != null) {
|
||||||
long error = SSL.getLastErrorNumber();
|
keyManagerFactory = buildKeyManagerFactory(
|
||||||
if (OpenSsl.isError(error)) {
|
keyCertChain, key, keyPassword, keyManagerFactory);
|
||||||
String err = SSL.getErrorString(error);
|
|
||||||
throw new SSLException(
|
|
||||||
"failed to set certificate chain: " + err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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. */
|
if (keyManagerFactory != null) {
|
||||||
long keyBio = 0;
|
X509KeyManager keyManager = chooseX509KeyManager(
|
||||||
keyCertChainBio = 0;
|
buildKeyManagerFactory(keyCertChain, key, keyPassword, keyManagerFactory)
|
||||||
try {
|
.getKeyManagers());
|
||||||
keyBio = toBIO(key);
|
keyMaterialManager = useExtendedKeyManager(keyManager) ?
|
||||||
keyCertChainBio = toBIO(keyCertChain);
|
new OpenSslExtendedKeyMaterialManager(
|
||||||
if (!SSLContext.setCertificateBio(
|
(X509ExtendedKeyManager) keyManager, keyPassword) :
|
||||||
ctx, keyCertChainBio, keyBio, keyPassword, SSL.SSL_AIDX_RSA)) {
|
new OpenSslKeyMaterialManager(keyManager, keyPassword);
|
||||||
long error = SSL.getLastErrorNumber();
|
} else {
|
||||||
if (OpenSsl.isError(error)) {
|
keyMaterialManager = null;
|
||||||
String err = SSL.getErrorString(error);
|
|
||||||
throw new SSLException("failed to set certificate and key: " + err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SSLException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SSLException("failed to set certificate and key", 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 {
|
try {
|
||||||
if (trustCertCollection != null) {
|
if (trustCertCollection != null) {
|
||||||
@ -448,6 +431,11 @@ public final class OpenSslServerContext extends OpenSslContext {
|
|||||||
return sessionContext;
|
return sessionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
OpenSslKeyMaterialManager keyMaterialManager() {
|
||||||
|
return keyMaterialManager;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
|
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
|
||||||
private final X509TrustManager manager;
|
private final X509TrustManager manager;
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
@ -51,6 +52,8 @@ import java.security.KeyStore;
|
|||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
@ -1034,4 +1037,38 @@ public abstract class SslContext {
|
|||||||
throw new SSLException(e);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user