Skip to content

Commit

Permalink
OpenSslEngine with no finalizer
Browse files Browse the repository at this point in the history
Motivation:
OpenSslEngine and OpenSslContext currently rely on finalizers to ensure that native resources are cleaned up. Finalizers require the GC to do extra work, and this extra work can be avoided if the user instead takes responsibility of releasing the native resources.

Modifications:
- Make a base class for OpenSslENgine and OpenSslContext which does not have a finalizer but instead implements ReferenceCounted. If this engine is inserted into the pipeline it will be released by the SslHandler
- Add a new SslProvider which can be used to enable this new feature

Result:
Users can opt-in to a finalizer free OpenSslEngine and OpenSslContext.
Fixes netty#4958
  • Loading branch information
Scottmitch committed Aug 5, 2016
1 parent de74868 commit e76de9f
Show file tree
Hide file tree
Showing 21 changed files with 3,362 additions and 2,868 deletions.
237 changes: 11 additions & 226 deletions handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,25 @@
*/
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 java.io.File;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

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;

import static io.netty.handler.ssl.ReferenceCountedOpenSslClientContext.newSessionContext;

/**
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
* <p>This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers
* and manually release the native memory see {@link ReferenceCountedOpenSslClientContext}.
*/
public final class OpenSslClientContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
private final OpenSslSessionContext sessionContext;

/**
Expand Down Expand Up @@ -187,7 +178,6 @@ public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory tr
keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}

@SuppressWarnings("deprecation")
OpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
Expand All @@ -198,73 +188,12 @@ public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory tr
ClientAuth.NONE);
boolean success = false;
try {
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) {
try {
if (!OpenSsl.useKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
if (keyCertChain != null && key != null) {
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
}
} else {
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));
}
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}

SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);

try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
} else if (trustManagerFactory == null) {
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());

// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the callbacks.
//
// See https://github.com/netty/netty/issues/5372

// Use this to prevent an error when running on java < 7
if (useExtendedTrustManager(manager)) {
SSLContext.setCertVerifyCallback(ctx,
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
} else {
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
}
} catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e);
}
}
sessionContext = new OpenSslClientSessionContext(this);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory);
success = true;
} finally {
if (!success) {
destroy();
release();
}
}
}
Expand All @@ -278,148 +207,4 @@ public OpenSslSessionContext sessionContext() {
OpenSslKeyMaterialManager keyMaterialManager() {
return null;
}

// No cache is currently supported for client side mode.
private static final class OpenSslClientSessionContext extends OpenSslSessionContext {
private OpenSslClientSessionContext(OpenSslContext context) {
super(context);
}

@Override
public void setSessionTimeout(int seconds) {
if (seconds < 0) {
throw new IllegalArgumentException();
}
}

@Override
public int getSessionTimeout() {
return 0;
}

@Override
public void setSessionCacheSize(int size) {
if (size < 0) {
throw new IllegalArgumentException();
}
}

@Override
public int getSessionCacheSize() {
return 0;
}

@Override
public void setSessionCacheEnabled(boolean enabled) {
// ignored
}

@Override
public boolean isSessionCacheEnabled() {
return false;
}
}

private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
private final X509TrustManager manager;

TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) {
super(engineMap);
this.manager = manager;
}

@Override
void verify(OpenSslEngine engine, X509Certificate[] peerCerts, String auth)
throws Exception {
manager.checkServerTrusted(peerCerts, auth);
}
}

private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier {
private final X509ExtendedTrustManager manager;

ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) {
super(engineMap);
this.manager = manager;
}

@Override
void verify(OpenSslEngine engine, X509Certificate[] peerCerts, String auth)
throws Exception {
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;
}
}
}
}
Loading

0 comments on commit e76de9f

Please sign in to comment.