diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java
index b04a3485c2..bd7a28dd12 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java
@@ -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.
+ *
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;
/**
@@ -187,7 +178,6 @@ public final class OpenSslClientContext extends OpenSslContext {
keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
- @SuppressWarnings("deprecation")
OpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
KeyManagerFactory keyManagerFactory, Iterable ciphers,
@@ -198,73 +188,12 @@ public final class OpenSslClientContext extends OpenSslContext {
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();
}
}
}
@@ -278,148 +207,4 @@ public final class OpenSslClientContext extends OpenSslContext {
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 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 supportedClientKeyTypes(byte[] clientCertificateTypes) {
- Set result = new HashSet(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;
- }
- }
- }
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
index f85a910e8f..5e9d36c7e1 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
@@ -15,661 +15,46 @@
*/
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;
-import org.apache.tomcat.jni.CertificateVerifier;
-import org.apache.tomcat.jni.Pool;
-import org.apache.tomcat.jni.SSL;
-import org.apache.tomcat.jni.SSLContext;
-import javax.net.ssl.KeyManager;
+import java.security.cert.Certificate;
+
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;
-import java.security.PrivilegedAction;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.CertificateRevokedException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import static io.netty.util.internal.ObjectUtil.checkNotNull;
-import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
-import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
-
-public abstract class OpenSslContext extends SslContext {
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslContext.class);
- /**
- * 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.
- *
- * See also
- * Significant SSL/TLS improvements in Java 8
- */
- private static final boolean JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION =
- SystemPropertyUtil.getBoolean("jdk.tls.rejectClientInitiatedRenegotiation", false);
- private static final List DEFAULT_CIPHERS;
- private static final Integer DH_KEY_LENGTH;
-
- // TODO: Maybe make configurable ?
- protected static final int VERIFY_DEPTH = 10;
-
- /**
- * The OpenSSL SSL_CTX object
- */
- protected volatile long ctx;
- long aprPool;
- @SuppressWarnings({ "unused", "FieldMayBeFinal" })
- private volatile int aprPoolDestroyed;
- private final List unmodifiableCiphers;
- private final long sessionCacheSize;
- private final long sessionTimeout;
- private final OpenSslApplicationProtocolNegotiator apn;
- private final int mode;
-
- final Certificate[] keyCertChain;
- final ClientAuth clientAuth;
- final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
- volatile boolean rejectRemoteInitiatedRenegotiation;
-
- static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR =
- new OpenSslApplicationProtocolNegotiator() {
- @Override
- public ApplicationProtocolConfig.Protocol protocol() {
- return ApplicationProtocolConfig.Protocol.NONE;
- }
-
- @Override
- public List protocols() {
- return Collections.emptyList();
- }
-
- @Override
- public SelectorFailureBehavior selectorFailureBehavior() {
- return SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL;
- }
-
- @Override
- public SelectedListenerFailureBehavior selectedListenerFailureBehavior() {
- return SelectedListenerFailureBehavior.ACCEPT;
- }
- };
-
- static {
- List ciphers = new ArrayList();
- // XXX: Make sure to sync this list with JdkSslEngineFactory.
- Collections.addAll(
- ciphers,
- "ECDHE-RSA-AES128-GCM-SHA256",
- "ECDHE-RSA-AES128-SHA",
- "ECDHE-RSA-AES256-SHA",
- "AES128-GCM-SHA256",
- "AES128-SHA",
- "AES256-SHA",
- "DES-CBC3-SHA");
- DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
-
- if (logger.isDebugEnabled()) {
- logger.debug("Default cipher suite (OpenSSL): " + ciphers);
- }
-
- Integer dhLen = null;
-
- try {
- String dhKeySize = AccessController.doPrivileged(new PrivilegedAction() {
- @Override
- public String run() {
- return SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize");
- }
- });
- if (dhKeySize != null) {
- try {
- dhLen = Integer.parseInt(dhKeySize);
- } catch (NumberFormatException e) {
- logger.debug("OpenSslContext only support -Djdk.tls.ephemeralDHKeySize={int}, but got: "
- + dhKeySize);
- }
- }
- } catch (Throwable ignore) {
- // ignore
- }
- DH_KEY_LENGTH = dhLen;
- }
+import static io.netty.util.ReferenceCountUtil.safeRelease;
+/**
+ * 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 ReferenceCountedOpenSslContext}.
+ */
+public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg,
long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth)
throws SSLException {
- this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
- clientAuth);
+ super(ciphers, cipherFilter, apnCfg, sessionCacheSize, sessionTimeout, mode, keyCertChain,
+ clientAuth, false);
}
OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter,
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth) throws SSLException {
- OpenSsl.ensureAvailability();
-
- if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) {
- throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT");
- }
- this.mode = mode;
- this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
-
- if (mode == SSL.SSL_MODE_SERVER) {
- rejectRemoteInitiatedRenegotiation =
- JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION;
- }
- this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone();
- final List convertedCiphers;
- if (ciphers == null) {
- convertedCiphers = null;
- } else {
- convertedCiphers = new ArrayList();
- for (String c : ciphers) {
- if (c == null) {
- break;
- }
-
- String converted = CipherSuiteConverter.toOpenSsl(c);
- if (converted != null) {
- c = converted;
- }
- convertedCiphers.add(c);
- }
- }
-
- unmodifiableCiphers = Arrays.asList(checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
- convertedCiphers, DEFAULT_CIPHERS, OpenSsl.availableCipherSuites()));
-
- this.apn = checkNotNull(apn, "apn");
-
- // Allocate a new APR pool.
- aprPool = Pool.create(0);
-
- // Create a new SSL_CTX and configure it.
- boolean success = false;
- try {
- synchronized (OpenSslContext.class) {
- try {
- ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, mode);
- } catch (Exception e) {
- throw new SSLException("failed to create an SSL_CTX", e);
- }
-
- SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
- SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
- SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3);
- SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
- SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE);
- SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE);
- SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
- // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK.
- // This also let SSLSession.getId() work the same way for the JDK implementation and the OpenSSLEngine.
- // If tickets are supported SSLSession.getId() will only return an ID on the server-side if it could
- // make use of tickets.
- SSLContext.setOptions(ctx, SSL.SSL_OP_NO_TICKET);
-
- // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between
- // calling OpenSSLEngine.wrap(...).
- // See https://github.com/netty/netty-tcnative/issues/100
- SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-
- if (DH_KEY_LENGTH != null) {
- SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH);
- }
-
- /* List the ciphers that are permitted to negotiate. */
- try {
- SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(unmodifiableCiphers));
- } catch (SSLException e) {
- throw e;
- } catch (Exception e) {
- throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e);
- }
-
- List nextProtoList = apn.protocols();
- /* Set next protocols for next protocol negotiation extension, if specified */
- if (!nextProtoList.isEmpty()) {
- String[] protocols = nextProtoList.toArray(new String[nextProtoList.size()]);
- 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();
- }
- }
-
- /* Set session cache size, if specified */
- if (sessionCacheSize > 0) {
- this.sessionCacheSize = sessionCacheSize;
- SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
- } else {
- // Get the default session cache size using SSLContext.setSessionCacheSize()
- this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
- // Revert the session cache size to the default value.
- SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
- }
-
- /* Set session timeout, if specified */
- if (sessionTimeout > 0) {
- this.sessionTimeout = sessionTimeout;
- SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
- } else {
- // Get the default session timeout using SSLContext.setSessionCacheTimeout()
- this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
- // Revert the session timeout to the default value.
- SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
- }
- }
- success = true;
- } finally {
- if (!success) {
- destroy();
- }
- }
- }
-
- 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();
- }
+ super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, false);
}
@Override
- public final List cipherSuites() {
- return unmodifiableCiphers;
- }
-
- @Override
- public final long sessionCacheSize() {
- return sessionCacheSize;
- }
-
- @Override
- public final long sessionTimeout() {
- return sessionTimeout;
- }
-
- @Override
- public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
- return apn;
- }
-
- @Override
- public final boolean isClient() {
- return mode == SSL.SSL_MODE_CLIENT;
- }
-
- @Override
- public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
+ final SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) {
return new OpenSslEngine(this, alloc, peerHost, peerPort);
}
- abstract OpenSslKeyMaterialManager keyMaterialManager();
-
- /**
- * Returns a new server-side {@link SSLEngine} with the current configuration.
- */
- @Override
- public final SSLEngine newEngine(ByteBufAllocator alloc) {
- return newEngine(alloc, null, -1);
- }
-
- /**
- * Returns the pointer to the {@code SSL_CTX} object for this {@link OpenSslContext}.
- * Be aware that it is freed as soon as the {@link #finalize()} method is called.
- * At this point {@code 0} will be returned.
- *
- * @deprecated use {@link #sslCtxPointer()}
- */
- @Deprecated
- public final long context() {
- return ctx;
- }
-
- /**
- * Returns the stats of this context.
- *
- * @deprecated use {@link #sessionContext#stats()}
- */
- @Deprecated
- public final OpenSslSessionStats stats() {
- return sessionContext().stats();
- }
-
- /**
- * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries
- * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding.
- */
- public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) {
- this.rejectRemoteInitiatedRenegotiation = rejectRemoteInitiatedRenegotiation;
- }
-
@Override
@SuppressWarnings("FinalizeDeclaration")
protected final void finalize() throws Throwable {
super.finalize();
- destroy();
- }
-
- /**
- * Sets the SSL session ticket keys of this context.
- *
- * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
- */
- @Deprecated
- public final void setTicketKeys(byte[] keys) {
- sessionContext().setTicketKeys(keys);
- }
-
- @Override
- public abstract OpenSslSessionContext sessionContext();
-
- /**
- * Returns the pointer to the {@code SSL_CTX} object for this {@link OpenSslContext}.
- * Be aware that it is freed as soon as the {@link #finalize()} method is called.
- * At this point {@code 0} will be returned.
- */
- public final long sslCtxPointer() {
- return ctx;
- }
-
- // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never
- // get access to an OpenSslSessionContext after this method was called to prevent the user from
- // producing a segfault.
- final void destroy() {
- synchronized (OpenSslContext.class) {
- if (ctx != 0) {
- SSLContext.free(ctx);
- ctx = 0;
- }
-
- // Guard against multiple destroyPools() calls triggered by construction exception and finalize() later
- if (aprPool != 0) {
- Pool.destroy(aprPool);
- aprPool = 0;
- }
- }
- }
-
- protected static X509Certificate[] certificates(byte[][] chain) {
- X509Certificate[] peerCerts = new X509Certificate[chain.length];
- for (int i = 0; i < peerCerts.length; i++) {
- peerCerts[i] = new OpenSslX509Certificate(chain[i]);
- }
- return peerCerts;
- }
-
- protected static X509TrustManager chooseTrustManager(TrustManager[] managers) {
- for (TrustManager m : managers) {
- if (m instanceof X509TrustManager) {
- return (X509TrustManager) m;
- }
- }
- 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
- */
- static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) {
- if (config == null) {
- return NONE_PROTOCOL_NEGOTIATOR;
- }
-
- 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());
- }
- default:
- throw new UnsupportedOperationException(
- new StringBuilder("OpenSSL provider does not support ")
- .append(config.selectedListenerFailureBehavior())
- .append(" behavior").toString());
- }
- default:
- throw new Error();
- }
- }
-
- static boolean useExtendedTrustManager(X509TrustManager trustManager) {
- 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 {
- private final OpenSslEngineMap engineMap;
-
- AbstractCertificateVerifier(OpenSslEngineMap engineMap) {
- this.engineMap = engineMap;
- }
-
- @Override
- public final int verify(long ssl, byte[][] chain, String auth) {
- X509Certificate[] peerCerts = certificates(chain);
- final OpenSslEngine engine = engineMap.get(ssl);
- try {
- verify(engine, peerCerts, auth);
- return CertificateVerifier.X509_V_OK;
- } catch (Throwable cause) {
- logger.debug("verification of certificate failed", cause);
- SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
- e.initCause(cause);
- engine.handshakeException = e;
-
- if (cause instanceof OpenSslCertificateException) {
- return ((OpenSslCertificateException) cause).errorCode();
- }
- if (cause instanceof CertificateExpiredException) {
- return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED;
- }
- if (cause instanceof CertificateNotYetValidException) {
- return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID;
- }
- if (PlatformDependent.javaVersion() >= 7 && cause instanceof CertificateRevokedException) {
- return CertificateVerifier.X509_V_ERR_CERT_REVOKED;
- }
- return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
- }
- }
-
- abstract void verify(OpenSslEngine engine, X509Certificate[] peerCerts, String auth) throws Exception;
- }
-
- private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
- private final Map engines = PlatformDependent.newConcurrentHashMap();
-
- @Override
- public OpenSslEngine remove(long ssl) {
- return engines.remove(ssl);
- }
-
- @Override
- 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 in-memory BIO
- * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}.
- */
- static long toBIO(PrivateKey key) throws Exception {
- if (key == null) {
- return 0;
- }
-
- ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
- PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key);
- try {
- return toBIO(allocator, pem.retain());
- } finally {
- pem.release();
- }
- }
-
- /**
- * Return the pointer to a in-memory BIO
- * 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 {
- if (certChain == null) {
- return 0;
- }
-
- if (certChain.length == 0) {
- throw new IllegalArgumentException("certChain can't be empty");
- }
-
- ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
- PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain);
- try {
- return toBIO(allocator, pem.retain());
- } finally {
- pem.release();
- }
- }
-
- private static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception {
- try {
- // We can turn direct buffers straight into BIOs. No need to
- // make a yet another copy.
- ByteBuf content = pem.content();
-
- if (content.isDirect()) {
- return newBIO(content.retainedSlice());
- }
-
- ByteBuf buffer = allocator.directBuffer(content.readableBytes());
- try {
- buffer.writeBytes(content, content.readerIndex(), content.readableBytes());
- return newBIO(buffer.retainedSlice());
- } finally {
- try {
- // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we
- // need to zero out the bytes of the copy before we're releasing it.
- if (pem.isSensitive()) {
- SslUtils.zeroout(buffer);
- }
- } finally {
- buffer.release();
- }
- }
- } finally {
- pem.release();
- }
- }
-
- private static long newBIO(ByteBuf buffer) throws Exception {
- try {
- long bio = SSL.newMemBIO();
- int readable = buffer.readableBytes();
- if (SSL.writeToBIO(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) {
- SSL.freeBIO(bio);
- throw new IllegalStateException("Could not write data to memory BIO");
- }
- return bio;
- } finally {
- buffer.release();
- }
+ safeRelease(this);
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
index 3e62b2924d..3ab4232629 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
@@ -15,1865 +15,30 @@
*/
package io.netty.handler.ssl;
-import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
-import io.netty.buffer.Unpooled;
-import io.netty.util.internal.EmptyArrays;
-import io.netty.util.internal.InternalThreadLocalMap;
-import io.netty.util.internal.PlatformDependent;
-import io.netty.util.internal.StringUtil;
-import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
-import org.apache.tomcat.jni.Buffer;
-import org.apache.tomcat.jni.SSL;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.nio.ReadOnlyBufferException;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLEngineResult.Status;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionBindingEvent;
-import javax.net.ssl.SSLSessionBindingListener;
-import javax.net.ssl.SSLSessionContext;
-import javax.security.cert.X509Certificate;
-import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
-import static io.netty.handler.ssl.OpenSsl.memoryAddress;
-import static io.netty.util.internal.ObjectUtil.checkNotNull;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
-import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW;
-import static javax.net.ssl.SSLEngineResult.Status.CLOSED;
-import static javax.net.ssl.SSLEngineResult.Status.OK;
+import static io.netty.util.ReferenceCountUtil.safeRelease;
/**
* Implements a {@link SSLEngine} using
* OpenSSL BIO abstractions.
+ *
+ * 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 ReferenceCountedOpenSslEngine}.
*/
-public final class OpenSslEngine extends SSLEngine {
-
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
-
- private static final Certificate[] EMPTY_CERTIFICATES = EmptyArrays.EMPTY_CERTIFICATES;
- private static final X509Certificate[] EMPTY_X509_CERTIFICATES = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
-
- private static final SSLException BEGIN_HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace(
- new SSLException("engine closed"), OpenSslEngine.class, "beginHandshake()");
- private static final SSLException HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace(
- new SSLException("engine closed"), OpenSslEngine.class, "handshake()");
- private static final SSLException RENEGOTIATION_UNSUPPORTED = ThrowableUtil.unknownStackTrace(
- new SSLException("renegotiation unsupported"), OpenSslEngine.class, "beginHandshake()");
- private static final SSLException ENCRYPTED_PACKET_OVERSIZED = ThrowableUtil.unknownStackTrace(
- new SSLException("encrypted packet oversized"), OpenSslEngine.class, "unwrap(...)");
- private static final Class> SNI_HOSTNAME_CLASS;
- private static final Method GET_SERVER_NAMES_METHOD;
- private static final Method SET_SERVER_NAMES_METHOD;
- private static final Method GET_ASCII_NAME_METHOD;
- private static final Method GET_USE_CIPHER_SUITES_ORDER_METHOD;
- private static final Method SET_USE_CIPHER_SUITES_ORDER_METHOD;
-
- static {
- AtomicIntegerFieldUpdater destroyedUpdater =
- PlatformDependent.newAtomicIntegerFieldUpdater(OpenSslEngine.class, "destroyed");
- if (destroyedUpdater == null) {
- destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
- }
- DESTROYED_UPDATER = destroyedUpdater;
-
- Method getUseCipherSuitesOrderMethod = null;
- Method setUseCipherSuitesOrderMethod = null;
- Class> sniHostNameClass = null;
- Method getAsciiNameMethod = null;
- Method getServerNamesMethod = null;
- Method setServerNamesMethod = null;
- if (PlatformDependent.javaVersion() >= 8) {
- try {
- getUseCipherSuitesOrderMethod = SSLParameters.class.getDeclaredMethod("getUseCipherSuitesOrder");
- SSLParameters parameters = new SSLParameters();
- @SuppressWarnings("unused")
- Boolean order = (Boolean) getUseCipherSuitesOrderMethod.invoke(parameters);
- setUseCipherSuitesOrderMethod = SSLParameters.class.getDeclaredMethod("setUseCipherSuitesOrder",
- boolean.class);
- setUseCipherSuitesOrderMethod.invoke(parameters, true);
- } catch (Throwable ignore) {
- getUseCipherSuitesOrderMethod = null;
- setUseCipherSuitesOrderMethod = null;
- }
- try {
- sniHostNameClass = Class.forName("javax.net.ssl.SNIHostName", false,
- PlatformDependent.getClassLoader(OpenSslEngine.class));
- Object sniHostName = sniHostNameClass.getConstructor(String.class).newInstance("netty.io");
- getAsciiNameMethod = sniHostNameClass.getDeclaredMethod("getAsciiName");
- @SuppressWarnings("unused")
- String name = (String) getAsciiNameMethod.invoke(sniHostName);
-
- getServerNamesMethod = SSLParameters.class.getDeclaredMethod("getServerNames");
- setServerNamesMethod = SSLParameters.class.getDeclaredMethod("setServerNames", List.class);
- SSLParameters parameters = new SSLParameters();
- @SuppressWarnings({ "rawtypes", "unused" })
- List serverNames = (List) getServerNamesMethod.invoke(parameters);
- setServerNamesMethod.invoke(parameters, Collections.emptyList());
- } catch (Throwable ingore) {
- sniHostNameClass = null;
- getAsciiNameMethod = null;
- getServerNamesMethod = null;
- setServerNamesMethod = null;
- }
- }
- GET_USE_CIPHER_SUITES_ORDER_METHOD = getUseCipherSuitesOrderMethod;
- SET_USE_CIPHER_SUITES_ORDER_METHOD = setUseCipherSuitesOrderMethod;
- SNI_HOSTNAME_CLASS = sniHostNameClass;
- GET_ASCII_NAME_METHOD = getAsciiNameMethod;
- GET_SERVER_NAMES_METHOD = getServerNamesMethod;
- SET_SERVER_NAMES_METHOD = setServerNamesMethod;
- }
-
- private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
- private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
- private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
-
- // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
- static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
-
- static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH;
-
- private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER;
-
- private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
-
- private static final long EMPTY_ADDR = Buffer.address(Unpooled.EMPTY_BUFFER.nioBuffer());
-
- private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0);
- private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0);
- private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0);
- private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0);
- private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0);
-
- // OpenSSL state
- private long ssl;
- private long networkBIO;
- private boolean certificateSet;
-
- private enum HandshakeState {
- /**
- * Not started yet.
- */
- NOT_STARTED,
- /**
- * Started via unwrap/wrap.
- */
- STARTED_IMPLICITLY,
- /**
- * Started via {@link #beginHandshake()}.
- */
- STARTED_EXPLICITLY,
-
- /**
- * Handshake is finished.
- */
- FINISHED
- }
-
- private HandshakeState handshakeState = HandshakeState.NOT_STARTED;
- private boolean receivedShutdown;
- private volatile int destroyed;
-
- private volatile ClientAuth clientAuth = ClientAuth.NONE;
-
- // Updated once a new handshake is started and so the SSLSession reused.
- private volatile long lastAccessed = -1;
-
- private String endPointIdentificationAlgorithm;
- // Store as object as AlgorithmConstraints only exists since java 7.
- private Object algorithmConstraints;
- private List> sniHostNames;
-
- // SSL Engine status variables
- private boolean isInboundDone;
- private boolean isOutboundDone;
- private boolean engineClosed;
-
- private final boolean clientMode;
- private final ByteBufAllocator alloc;
- private final OpenSslEngineMap engineMap;
- private final OpenSslApplicationProtocolNegotiator apn;
- private final boolean rejectRemoteInitiatedRenegation;
- private final OpenSslSession session;
- 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;
-
+public final class OpenSslEngine extends ReferenceCountedOpenSslEngine {
OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort) {
- super(peerHost, peerPort);
- OpenSsl.ensureAvailability();
- this.alloc = checkNotNull(alloc, "alloc");
- apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
- ssl = SSL.newSSL(context.ctx, !context.isClient());
- session = new OpenSslSession(context.sessionContext());
- networkBIO = SSL.makeNetworkBIO(ssl);
- 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 : 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
- public synchronized SSLSession getHandshakeSession() {
- // Javadocs state return value should be:
- // null if this instance is not currently handshaking, or if the current handshake has not
- // progressed far enough to create a basic SSLSession. Otherwise, this method returns the
- // SSLSession currently being negotiated.
- switch(handshakeState) {
- case NOT_STARTED:
- case FINISHED:
- return null;
- default:
- return session;
- }
- }
-
- /**
- * Returns the pointer to the {@code SSL} object for this {@link OpenSslEngine}.
- * Be aware that it is freed as soon as the {@link #finalize()} or {@link #shutdown} method is called.
- * At this point {@code 0} will be returned.
- */
- public synchronized long sslPointer() {
- return ssl;
- }
-
- /**
- * Destroys this engine.
- */
- public synchronized void shutdown() {
- if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
- engineMap.remove(ssl);
- SSL.freeSSL(ssl);
- SSL.freeBIO(networkBIO);
- ssl = networkBIO = 0;
-
- // internal errors can cause shutdown without marking the engine closed
- isInboundDone = isOutboundDone = engineClosed = true;
- }
-
- // On shutdown clear all errors
- SSL.clearError();
- }
-
- /**
- * Write plaintext data to the OpenSSL internal BIO
- *
- * Calling this function with src.remaining == 0 is undefined.
- */
- private int writePlaintextData(final ByteBuffer src) {
- final int pos = src.position();
- final int limit = src.limit();
- final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH);
- final int sslWrote;
-
- if (src.isDirect()) {
- final long addr = Buffer.address(src) + pos;
- sslWrote = SSL.writeToSSL(ssl, addr, len);
- if (sslWrote > 0) {
- src.position(pos + sslWrote);
- }
- } else {
- ByteBuf buf = alloc.directBuffer(len);
- try {
- final long addr = memoryAddress(buf);
-
- src.limit(pos + len);
-
- buf.setBytes(0, src);
- src.limit(limit);
-
- sslWrote = SSL.writeToSSL(ssl, addr, len);
- if (sslWrote > 0) {
- src.position(pos + sslWrote);
- } else {
- src.position(pos);
- }
- } finally {
- buf.release();
- }
- }
- return sslWrote;
- }
-
- /**
- * Write encrypted data to the OpenSSL network BIO.
- */
- private int writeEncryptedData(final ByteBuffer src) {
- final int pos = src.position();
- final int len = src.remaining();
- final int netWrote;
- if (src.isDirect()) {
- final long addr = Buffer.address(src) + pos;
- netWrote = SSL.writeToBIO(networkBIO, addr, len);
- if (netWrote >= 0) {
- src.position(pos + netWrote);
- }
- } else {
- final ByteBuf buf = alloc.directBuffer(len);
- try {
- final long addr = memoryAddress(buf);
-
- buf.setBytes(0, src);
-
- netWrote = SSL.writeToBIO(networkBIO, addr, len);
- if (netWrote >= 0) {
- src.position(pos + netWrote);
- } else {
- src.position(pos);
- }
- } finally {
- buf.release();
- }
- }
-
- return netWrote;
- }
-
- /**
- * Read plaintext data from the OpenSSL internal BIO
- */
- private int readPlaintextData(final ByteBuffer dst) {
- final int sslRead;
- if (dst.isDirect()) {
- final int pos = dst.position();
- final long addr = Buffer.address(dst) + pos;
- final int len = dst.limit() - pos;
- sslRead = SSL.readFromSSL(ssl, addr, len);
- if (sslRead > 0) {
- dst.position(pos + sslRead);
- }
- } else {
- final int pos = dst.position();
- final int limit = dst.limit();
- final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
- final ByteBuf buf = alloc.directBuffer(len);
- try {
- final long addr = memoryAddress(buf);
-
- sslRead = SSL.readFromSSL(ssl, addr, len);
- if (sslRead > 0) {
- dst.limit(pos + sslRead);
- buf.getBytes(0, dst);
- dst.limit(limit);
- }
- } finally {
- buf.release();
- }
- }
-
- return sslRead;
- }
-
- /**
- * Read encrypted data from the OpenSSL network BIO
- */
- private int readEncryptedData(final ByteBuffer dst, final int pending) {
- final int bioRead;
-
- if (dst.isDirect() && dst.remaining() >= pending) {
- final int pos = dst.position();
- final long addr = Buffer.address(dst) + pos;
- bioRead = SSL.readFromBIO(networkBIO, addr, pending);
- if (bioRead > 0) {
- dst.position(pos + bioRead);
- return bioRead;
- }
- } else {
- final ByteBuf buf = alloc.directBuffer(pending);
- try {
- final long addr = memoryAddress(buf);
-
- bioRead = SSL.readFromBIO(networkBIO, addr, pending);
- if (bioRead > 0) {
- int oldLimit = dst.limit();
- dst.limit(dst.position() + bioRead);
- buf.getBytes(0, dst);
- dst.limit(oldLimit);
- return bioRead;
- }
- } finally {
- buf.release();
- }
- }
-
- return bioRead;
- }
-
- private SSLEngineResult readPendingBytesFromBIO(
- ByteBuffer dst, int bytesConsumed, int bytesProduced, HandshakeStatus status) throws SSLException {
- // Check to see if the engine wrote data into the network BIO
- int pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
- if (pendingNet > 0) {
-
- // Do we have enough room in dst to write encrypted data?
- int capacity = dst.remaining();
- if (capacity < pendingNet) {
- return new SSLEngineResult(BUFFER_OVERFLOW,
- mayFinishHandshake(status != FINISHED ? getHandshakeStatus(pendingNet) : status),
- bytesConsumed, bytesProduced);
- }
-
- // Write the pending data from the network BIO into the dst buffer
- int produced = readEncryptedData(dst, pendingNet);
-
- if (produced <= 0) {
- // We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call later
- // on in which we will produce an exception in case of an error
- SSL.clearError();
- } else {
- bytesProduced += produced;
- pendingNet -= produced;
- }
- // If isOuboundDone is set, then the data from the network BIO
- // was the close_notify message -- we are not required to wait
- // for the receipt the peer's close_notify message -- shutdown.
- if (isOutboundDone) {
- shutdown();
- }
-
- return new SSLEngineResult(getEngineStatus(),
- mayFinishHandshake(status != FINISHED ? getHandshakeStatus(pendingNet) : status),
- bytesConsumed, bytesProduced);
- }
- return null;
- }
-
- @Override
- public SSLEngineResult wrap(
- final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException {
- // Throw required runtime exceptions
- if (srcs == null) {
- throw new IllegalArgumentException("srcs is null");
- }
- if (dst == null) {
- throw new IllegalArgumentException("dst is null");
- }
-
- if (offset >= srcs.length || offset + length > srcs.length) {
- throw new IndexOutOfBoundsException(
- "offset: " + offset + ", length: " + length +
- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
- }
-
- if (dst.isReadOnly()) {
- throw new ReadOnlyBufferException();
- }
-
- synchronized (this) {
- // Check to make sure the engine has not been closed
- if (isDestroyed()) {
- return CLOSED_NOT_HANDSHAKING;
- }
-
- HandshakeStatus status = NOT_HANDSHAKING;
- // Prepare OpenSSL to work in server mode and receive handshake
- if (handshakeState != HandshakeState.FINISHED) {
- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
- // Update accepted so we know we triggered the handshake via wrap
- handshakeState = HandshakeState.STARTED_IMPLICITLY;
- }
-
- status = handshake();
- if (status == NEED_UNWRAP) {
- return NEED_UNWRAP_OK;
- }
-
- if (engineClosed) {
- return NEED_UNWRAP_CLOSED;
- }
- }
-
- // There was no pending data in the network BIO -- encrypt any application data
- int bytesProduced = 0;
- int bytesConsumed = 0;
- int endOffset = offset + length;
- for (int i = offset; i < endOffset; ++i) {
- final ByteBuffer src = srcs[i];
- if (src == null) {
- throw new IllegalArgumentException("srcs[" + i + "] is null");
- }
- while (src.hasRemaining()) {
- final SSLEngineResult pendingNetResult;
- // Write plaintext application data to the SSL engine
- int result = writePlaintextData(src);
- if (result > 0) {
- bytesConsumed += result;
-
- pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
- if (pendingNetResult != null) {
- if (pendingNetResult.getStatus() != OK) {
- return pendingNetResult;
- }
- bytesProduced = pendingNetResult.bytesProduced();
- }
- } else {
- int sslError = SSL.getError(ssl, result);
- switch (sslError) {
- case SSL.SSL_ERROR_ZERO_RETURN:
- // This means the connection was shutdown correctly, close inbound and outbound
- if (!receivedShutdown) {
- closeAll();
- }
- pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
- return pendingNetResult != null ? pendingNetResult : CLOSED_NOT_HANDSHAKING;
- case SSL.SSL_ERROR_WANT_READ:
- // If there is no pending data to read from BIO we should go back to event loop and try
- // to read more data [1]. It is also possible that event loop will detect the socket
- // has been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
- pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
- return pendingNetResult != null ? pendingNetResult :
- new SSLEngineResult(getEngineStatus(),
- NEED_UNWRAP, bytesConsumed, bytesProduced);
- case SSL.SSL_ERROR_WANT_WRITE:
- // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable
- // and we should set the "want write" flag on the selector and try again when the
- // underlying transport is writable [1]. However we are not directly writing to the
- // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation
- // says we should do the following [1]:
- //
- // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved
- // out of the BIO before being able to continue."
- //
- // So we attempt to drain the BIO buffer below, but if there is no data this condition
- // is undefined and we assume their is a fatal error with the openssl engine and close.
- // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
- pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
- return pendingNetResult != null ? pendingNetResult : NEED_WRAP_CLOSED;
- default:
- // Everything else is considered as error
- throw shutdownWithError("SSL_write");
- }
- }
- }
- }
- // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not checked if the srcs was
- // empty, or only contained empty buffers.
- if (bytesConsumed == 0) {
- SSLEngineResult pendingNetResult = readPendingBytesFromBIO(dst, 0, bytesProduced, status);
- if (pendingNetResult != null) {
- return pendingNetResult;
- }
- }
-
- return newResult(bytesConsumed, bytesProduced, status);
- }
- }
-
- /**
- * Log the error, shutdown the engine and throw an exception.
- */
- private SSLException shutdownWithError(String operations) {
- String err = SSL.getLastError();
- return shutdownWithError(operations, err);
- }
-
- private SSLException shutdownWithError(String operation, String err) {
- if (logger.isDebugEnabled()) {
- logger.debug("{} failed: OpenSSL error: {}", operation, err);
- }
-
- // There was an internal error -- shutdown
- shutdown();
- if (handshakeState == HandshakeState.FINISHED) {
- return new SSLException(err);
- }
- return new SSLHandshakeException(err);
- }
-
- public SSLEngineResult unwrap(
- final ByteBuffer[] srcs, int srcsOffset, final int srcsLength,
- final ByteBuffer[] dsts, final int dstsOffset, final int dstsLength) throws SSLException {
-
- // Throw required runtime exceptions
- if (srcs == null) {
- throw new NullPointerException("srcs");
- }
- if (srcsOffset >= srcs.length
- || srcsOffset + srcsLength > srcs.length) {
- throw new IndexOutOfBoundsException(
- "offset: " + srcsOffset + ", length: " + srcsLength +
- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
- }
- if (dsts == null) {
- throw new IllegalArgumentException("dsts is null");
- }
- if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) {
- throw new IndexOutOfBoundsException(
- "offset: " + dstsOffset + ", length: " + dstsLength +
- " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
- }
- long capacity = 0;
- final int endOffset = dstsOffset + dstsLength;
- for (int i = dstsOffset; i < endOffset; i ++) {
- ByteBuffer dst = dsts[i];
- if (dst == null) {
- throw new IllegalArgumentException("dsts[" + i + "] is null");
- }
- if (dst.isReadOnly()) {
- throw new ReadOnlyBufferException();
- }
- capacity += dst.remaining();
- }
-
- final int srcsEndOffset = srcsOffset + srcsLength;
- long len = 0;
- for (int i = srcsOffset; i < srcsEndOffset; i++) {
- ByteBuffer src = srcs[i];
- if (src == null) {
- throw new IllegalArgumentException("srcs[" + i + "] is null");
- }
- len += src.remaining();
- }
-
- synchronized (this) {
- // Check to make sure the engine has not been closed
- if (isDestroyed()) {
- return CLOSED_NOT_HANDSHAKING;
- }
-
- // protect against protocol overflow attack vector
- if (len > MAX_ENCRYPTED_PACKET_LENGTH) {
- isInboundDone = true;
- isOutboundDone = true;
- engineClosed = true;
- shutdown();
- throw ENCRYPTED_PACKET_OVERSIZED;
- }
-
- HandshakeStatus status = NOT_HANDSHAKING;
- // Prepare OpenSSL to work in server mode and receive handshake
- if (handshakeState != HandshakeState.FINISHED) {
- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
- // Update accepted so we know we triggered the handshake via wrap
- handshakeState = HandshakeState.STARTED_IMPLICITLY;
- }
-
- status = handshake();
- if (status == NEED_WRAP) {
- return NEED_WRAP_OK;
- }
- if (engineClosed) {
- return NEED_WRAP_CLOSED;
- }
- }
-
- // Write encrypted data to network BIO
- int bytesConsumed = 0;
- if (srcsOffset < srcsEndOffset) {
- do {
- ByteBuffer src = srcs[srcsOffset];
- int remaining = src.remaining();
- if (remaining == 0) {
- // We must skip empty buffers as BIO_write will return 0 if asked to write something
- // with length 0.
- srcsOffset++;
- continue;
- }
- int written = writeEncryptedData(src);
- if (written > 0) {
- bytesConsumed += written;
-
- if (written == remaining) {
- srcsOffset++;
- } else {
- // We were not able to write everything into the BIO so break the write loop as otherwise
- // we will produce an error on the next write attempt, which will trigger a SSL.clearError()
- // later.
- break;
- }
- } else {
- // BIO_write returned a negative or zero number, this means we could not complete the write
- // operation and should retry later.
- // We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call
- // later on in which we will produce an exception in case of an error
- SSL.clearError();
- break;
- }
- } while (srcsOffset < srcsEndOffset);
- }
-
- // Number of produced bytes
- int bytesProduced = 0;
-
- if (capacity > 0) {
- // Write decrypted data to dsts buffers
- int idx = dstsOffset;
- while (idx < endOffset) {
- ByteBuffer dst = dsts[idx];
- if (!dst.hasRemaining()) {
- idx++;
- continue;
- }
-
- int bytesRead = readPlaintextData(dst);
-
- // TODO: We may want to consider if we move this check and only do it in a less often called place
- // at the price of not being 100% accurate, like for example when calling SSL.getError(...).
- rejectRemoteInitiatedRenegation();
-
- if (bytesRead > 0) {
- bytesProduced += bytesRead;
-
- if (!dst.hasRemaining()) {
- idx++;
- } else {
- // We read everything return now.
- return newResult(bytesConsumed, bytesProduced, status);
- }
- } else {
- int sslError = SSL.getError(ssl, bytesRead);
- switch (sslError) {
- case SSL.SSL_ERROR_ZERO_RETURN:
- // This means the connection was shutdown correctly, close inbound and outbound
- if (!receivedShutdown) {
- closeAll();
- }
- // fall-trough!
- case SSL.SSL_ERROR_WANT_READ:
- case SSL.SSL_ERROR_WANT_WRITE:
- // break to the outer loop
- return newResult(bytesConsumed, bytesProduced, status);
- default:
- return sslReadErrorResult(SSL.getLastErrorNumber(), bytesConsumed, bytesProduced);
- }
- }
- }
- } else {
- // If the capacity of all destination buffers is 0 we need to trigger a SSL_read anyway to ensure
- // everything is flushed in the BIO pair and so we can detect it in the pendingAppData() call.
- if (SSL.readFromSSL(ssl, EMPTY_ADDR, 0) <= 0) {
- // We do not check SSL_get_error as we are not interested in any error that is not fatal.
- int err = SSL.getLastErrorNumber();
- if (OpenSsl.isError(err)) {
- return sslReadErrorResult(err, bytesConsumed, bytesProduced);
- }
- }
- }
- if (pendingAppData() > 0) {
- // We filled all buffers but there is still some data pending in the BIO buffer, return BUFFER_OVERFLOW.
- return new SSLEngineResult(
- BUFFER_OVERFLOW, mayFinishHandshake(status != FINISHED ? getHandshakeStatus() : status),
- bytesConsumed, bytesProduced);
- }
-
- // Check to see if we received a close_notify message from the peer.
- if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
- closeAll();
- }
-
- return newResult(bytesConsumed, bytesProduced, status);
- }
- }
-
- private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced) throws SSLException {
- String errStr = SSL.getErrorString(err);
-
- // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the
- // BIO first or can just shutdown and throw it now.
- // This is needed so we ensure close_notify etc is correctly send to the remote peer.
- // See https://github.com/netty/netty/issues/3900
- if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) {
- if (handshakeException == null && handshakeState != HandshakeState.FINISHED) {
- // we seems to have data left that needs to be transfered and so the user needs
- // call wrap(...). Store the error so we can pick it up later.
- handshakeException = new SSLHandshakeException(errStr);
- }
- return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced);
- }
- throw shutdownWithError("SSL_read", errStr);
- }
-
- private int pendingAppData() {
- // There won't be any application data until we're done handshaking.
- // We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
- return handshakeState == HandshakeState.FINISHED ? SSL.pendingReadableBytesInSSL(ssl) : 0;
- }
-
- private SSLEngineResult newResult(
- int bytesConsumed, int bytesProduced, HandshakeStatus status) throws SSLException {
- return new SSLEngineResult(
- getEngineStatus(), mayFinishHandshake(status != FINISHED ? getHandshakeStatus() : status)
- , bytesConsumed, bytesProduced);
- }
-
- private void closeAll() throws SSLException {
- receivedShutdown = true;
- closeOutbound();
- closeInbound();
- }
-
- private void rejectRemoteInitiatedRenegation() throws SSLHandshakeException {
- if (rejectRemoteInitiatedRenegation && SSL.getHandshakeCount(ssl) > 1) {
- // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it
- // that the renegotiation failed.
- shutdown();
- throw new SSLHandshakeException("remote-initiated renegotation not allowed");
- }
- }
-
- public SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException {
- return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length);
- }
-
- private ByteBuffer[] singleSrcBuffer(ByteBuffer src) {
- singleSrcBuffer[0] = src;
- return singleSrcBuffer;
- }
-
- private void resetSingleSrcBuffer() {
- singleSrcBuffer[0] = null;
- }
-
- private ByteBuffer[] singleDstBuffer(ByteBuffer src) {
- singleDstBuffer[0] = src;
- return singleDstBuffer;
- }
-
- private void resetSingleDstBuffer() {
- singleDstBuffer[0] = null;
- }
-
- @Override
- public synchronized SSLEngineResult unwrap(
- final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
- try {
- return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length);
- } finally {
- resetSingleSrcBuffer();
- }
- }
-
- @Override
- public synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
- try {
- return wrap(singleSrcBuffer(src), dst);
- } finally {
- resetSingleSrcBuffer();
- }
- }
-
- @Override
- public synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
- try {
- return unwrap(singleSrcBuffer(src), singleDstBuffer(dst));
- } finally {
- resetSingleSrcBuffer();
- resetSingleDstBuffer();
- }
- }
-
- @Override
- public synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException {
- try {
- return unwrap(singleSrcBuffer(src), dsts);
- } finally {
- resetSingleSrcBuffer();
- }
- }
-
- @Override
- public Runnable getDelegatedTask() {
- // Currently, we do not delegate SSL computation tasks
- // TODO: in the future, possibly create tasks to do encrypt / decrypt async
-
- return null;
- }
-
- @Override
- public synchronized void closeInbound() throws SSLException {
- if (isInboundDone) {
- return;
- }
-
- isInboundDone = true;
- engineClosed = true;
-
- shutdown();
-
- if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) {
- throw new SSLException(
- "Inbound closed before receiving peer's close_notify: possible truncation attack?");
- }
- }
-
- @Override
- public synchronized boolean isInboundDone() {
- return isInboundDone || engineClosed;
- }
-
- @Override
- public synchronized void closeOutbound() {
- if (isOutboundDone) {
- return;
- }
-
- isOutboundDone = true;
- engineClosed = true;
-
- if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) {
- int mode = SSL.getShutdown(ssl);
- if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
- int err = SSL.shutdownSSL(ssl);
- if (err < 0) {
- int sslErr = SSL.getError(ssl, err);
- switch (sslErr) {
- case SSL.SSL_ERROR_NONE:
- case SSL.SSL_ERROR_WANT_ACCEPT:
- case SSL.SSL_ERROR_WANT_CONNECT:
- case SSL.SSL_ERROR_WANT_WRITE:
- case SSL.SSL_ERROR_WANT_READ:
- case SSL.SSL_ERROR_WANT_X509_LOOKUP:
- case SSL.SSL_ERROR_ZERO_RETURN:
- // Nothing to do here
- break;
- case SSL.SSL_ERROR_SYSCALL:
- case SSL.SSL_ERROR_SSL:
- if (logger.isDebugEnabled()) {
- logger.debug("SSL_shutdown failed: OpenSSL error: {}", SSL.getLastError());
- }
- // There was an internal error -- shutdown
- shutdown();
- break;
- default:
- SSL.clearError();
- break;
- }
- }
- }
- } else {
- // engine closing before initial handshake
- shutdown();
- }
- }
-
- @Override
- public synchronized boolean isOutboundDone() {
- return isOutboundDone;
- }
-
- @Override
- public String[] getSupportedCipherSuites() {
- return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(new String[OpenSsl.AVAILABLE_CIPHER_SUITES.size()]);
- }
-
- @Override
- public String[] getEnabledCipherSuites() {
- final String[] enabled;
- synchronized (this) {
- if (!isDestroyed()) {
- enabled = SSL.getCiphers(ssl);
- } else {
- return EmptyArrays.EMPTY_STRINGS;
- }
- }
- if (enabled == null) {
- return EmptyArrays.EMPTY_STRINGS;
- } else {
- for (int i = 0; i < enabled.length; i++) {
- String mapped = toJavaCipherSuite(enabled[i]);
- if (mapped != null) {
- enabled[i] = mapped;
- }
- }
- return enabled;
- }
- }
-
- @Override
- public void setEnabledCipherSuites(String[] cipherSuites) {
- checkNotNull(cipherSuites, "cipherSuites");
-
- final StringBuilder buf = new StringBuilder();
- for (String c: cipherSuites) {
- if (c == null) {
- break;
- }
-
- String converted = CipherSuiteConverter.toOpenSsl(c);
- if (converted == null) {
- converted = c;
- }
-
- if (!OpenSsl.isCipherSuiteAvailable(converted)) {
- throw new IllegalArgumentException("unsupported cipher suite: " + c + '(' + converted + ')');
- }
-
- buf.append(converted);
- buf.append(':');
- }
-
- if (buf.length() == 0) {
- throw new IllegalArgumentException("empty cipher suites");
- }
- buf.setLength(buf.length() - 1);
-
- final String cipherSuiteSpec = buf.toString();
-
- synchronized (this) {
- if (!isDestroyed()) {
- try {
- SSL.setCipherSuites(ssl, cipherSuiteSpec);
- } catch (Exception e) {
- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e);
- }
- } else {
- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec);
- }
- }
- }
-
- @Override
- public String[] getSupportedProtocols() {
- return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(new String[OpenSsl.SUPPORTED_PROTOCOLS_SET.size()]);
- }
-
- @Override
- public String[] getEnabledProtocols() {
- List enabled = InternalThreadLocalMap.get().arrayList();
- // Seems like there is no way to explict disable SSLv2Hello in openssl so it is always enabled
- enabled.add(OpenSsl.PROTOCOL_SSL_V2_HELLO);
-
- int opts;
- synchronized (this) {
- if (!isDestroyed()) {
- opts = SSL.getOptions(ssl);
- } else {
- return enabled.toArray(new String[1]);
- }
- }
- if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) {
- enabled.add(OpenSsl.PROTOCOL_TLS_V1);
- }
- if ((opts & SSL.SSL_OP_NO_TLSv1_1) == 0) {
- enabled.add(OpenSsl.PROTOCOL_TLS_V1_1);
- }
- if ((opts & SSL.SSL_OP_NO_TLSv1_2) == 0) {
- enabled.add(OpenSsl.PROTOCOL_TLS_V1_2);
- }
- if ((opts & SSL.SSL_OP_NO_SSLv2) == 0) {
- enabled.add(OpenSsl.PROTOCOL_SSL_V2);
- }
- if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) {
- enabled.add(OpenSsl.PROTOCOL_SSL_V3);
- }
- return enabled.toArray(new String[enabled.size()]);
- }
-
- @Override
- public void setEnabledProtocols(String[] protocols) {
- if (protocols == null) {
- // This is correct from the API docs
- throw new IllegalArgumentException();
- }
- boolean sslv2 = false;
- boolean sslv3 = false;
- boolean tlsv1 = false;
- boolean tlsv1_1 = false;
- boolean tlsv1_2 = false;
- for (String p: protocols) {
- if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) {
- throw new IllegalArgumentException("Protocol " + p + " is not supported.");
- }
- if (p.equals(OpenSsl.PROTOCOL_SSL_V2)) {
- sslv2 = true;
- } else if (p.equals(OpenSsl.PROTOCOL_SSL_V3)) {
- sslv3 = true;
- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1)) {
- tlsv1 = true;
- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_1)) {
- tlsv1_1 = true;
- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_2)) {
- tlsv1_2 = true;
- }
- }
- synchronized (this) {
- if (!isDestroyed()) {
- // Enable all and then disable what we not want
- SSL.setOptions(ssl, SSL.SSL_OP_ALL);
-
- // Clear out options which disable protocols
- SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 |
- SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2);
-
- int opts = 0;
- if (!sslv2) {
- opts |= SSL.SSL_OP_NO_SSLv2;
- }
- if (!sslv3) {
- opts |= SSL.SSL_OP_NO_SSLv3;
- }
- if (!tlsv1) {
- opts |= SSL.SSL_OP_NO_TLSv1;
- }
- if (!tlsv1_1) {
- opts |= SSL.SSL_OP_NO_TLSv1_1;
- }
- if (!tlsv1_2) {
- opts |= SSL.SSL_OP_NO_TLSv1_2;
- }
-
- // Disable protocols we do not want
- SSL.setOptions(ssl, opts);
- } else {
- throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols));
- }
- }
- }
-
- @Override
- public SSLSession getSession() {
- return session;
- }
-
- @Override
- public synchronized void beginHandshake() throws SSLException {
- switch (handshakeState) {
- case STARTED_IMPLICITLY:
- checkEngineClosed(BEGIN_HANDSHAKE_ENGINE_CLOSED);
-
- // A user did not start handshake by calling this method by him/herself,
- // but handshake has been started already by wrap() or unwrap() implicitly.
- // Because it's the user's first time to call this method, it is unfair to
- // raise an exception. From the user's standpoint, he or she never asked
- // for renegotiation.
-
- handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user,
- // we should raise an exception.
- break;
- case STARTED_EXPLICITLY:
- // Nothing to do as the handshake is not done yet.
- break;
- case FINISHED:
- if (clientMode) {
- // Only supported for server mode at the moment.
- throw RENEGOTIATION_UNSUPPORTED;
- }
- // For renegotiate on the server side we need to issue the following command sequence with openssl:
- //
- // SSL_renegotiate(ssl)
- // SSL_do_handshake(ssl)
- // ssl->state = SSL_ST_ACCEPT
- // SSL_do_handshake(ssl)
- //
- // Bcause of this we fall-through to call handshake() after setting the state, as this will also take
- // care of updating the internal OpenSslSession object.
- //
- // See also:
- // https://github.com/apache/httpd/blob/2.4.16/modules/ssl/ssl_engine_kernel.c#L812
- // http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04s03.html
- if (SSL.renegotiate(ssl) != 1 || SSL.doHandshake(ssl) != 1) {
- throw shutdownWithError("renegotiation failed");
- }
-
- SSL.setState(ssl, SSL.SSL_ST_ACCEPT);
-
- lastAccessed = System.currentTimeMillis();
-
- // fall-through
- case NOT_STARTED:
- handshakeState = HandshakeState.STARTED_EXPLICITLY;
- handshake();
- break;
- default:
- throw new Error();
- }
- }
-
- private void checkEngineClosed(SSLException cause) throws SSLException {
- if (engineClosed || isDestroyed()) {
- throw cause;
- }
- }
-
- private static HandshakeStatus pendingStatus(int pendingStatus) {
- // Depending on if there is something left in the BIO we need to WRAP or UNWRAP
- return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP;
- }
-
- private HandshakeStatus handshake() throws SSLException {
- if (handshakeState == HandshakeState.FINISHED) {
- return FINISHED;
- }
- checkEngineClosed(HANDSHAKE_ENGINE_CLOSED);
-
- // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the
- // BIO first or can just shutdown and throw it now.
- // This is needed so we ensure close_notify etc is correctly send to the remote peer.
- // See https://github.com/netty/netty/issues/3900
- SSLHandshakeException exception = handshakeException;
- if (exception != null) {
- if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) {
- // There is something pending, we need to consume it first via a WRAP so we not loose anything.
- return NEED_WRAP;
- }
- // No more data left to send to the remote peer, so null out the exception field, shutdown and throw
- // the exception.
- handshakeException = null;
- shutdown();
- throw exception;
- }
-
- // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier.
- engineMap.add(this);
- if (lastAccessed == -1) {
- 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
- // shutdown the connection.
- if (handshakeException != null) {
- exception = handshakeException;
- handshakeException = null;
- shutdown();
- throw exception;
- }
-
- int sslError = SSL.getError(ssl, code);
-
- switch (sslError) {
- case SSL.SSL_ERROR_WANT_READ:
- case SSL.SSL_ERROR_WANT_WRITE:
- return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO));
- default:
- // Everything else is considered as error
- throw shutdownWithError("SSL_do_handshake");
- }
- }
- // 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;
- }
-
- private Status getEngineStatus() {
- return engineClosed? CLOSED : OK;
- }
-
- private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status)
- throws SSLException {
- if (status == NOT_HANDSHAKING && handshakeState != HandshakeState.FINISHED) {
- // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call
- // SSL_do_handshake() again
- return handshake();
- }
- return status;
- }
-
- @Override
- public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
- // Check if we are in the initial handshake phase or shutdown phase
- return needPendingStatus() ? pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO)) : NOT_HANDSHAKING;
- }
-
- private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) {
- // Check if we are in the initial handshake phase or shutdown phase
- return needPendingStatus() ? pendingStatus(pending) : NOT_HANDSHAKING;
- }
-
- private boolean needPendingStatus() {
- return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()
- && (handshakeState != HandshakeState.FINISHED || engineClosed);
- }
-
- /**
- * Converts the specified OpenSSL cipher suite to the Java cipher suite.
- */
- private String toJavaCipherSuite(String openSslCipherSuite) {
- if (openSslCipherSuite == null) {
- return null;
- }
-
- String prefix = toJavaCipherSuitePrefix(SSL.getVersion(ssl));
- return CipherSuiteConverter.toJava(openSslCipherSuite, prefix);
- }
-
- /**
- * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string.
- */
- private static String toJavaCipherSuitePrefix(String protocolVersion) {
- final char c;
- if (protocolVersion == null || protocolVersion.length() == 0) {
- c = 0;
- } else {
- c = protocolVersion.charAt(0);
- }
-
- switch (c) {
- case 'T':
- return "TLS";
- case 'S':
- return "SSL";
- default:
- return "UNKNOWN";
- }
- }
-
- @Override
- public void setUseClientMode(boolean clientMode) {
- if (clientMode != this.clientMode) {
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public boolean getUseClientMode() {
- return clientMode;
- }
-
- @Override
- public void setNeedClientAuth(boolean b) {
- setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE);
- }
-
- @Override
- public boolean getNeedClientAuth() {
- return clientAuth == ClientAuth.REQUIRE;
- }
-
- @Override
- public void setWantClientAuth(boolean b) {
- setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE);
- }
-
- @Override
- public boolean getWantClientAuth() {
- return clientAuth == ClientAuth.OPTIONAL;
- }
-
- private void setClientAuth(ClientAuth mode) {
- if (clientMode) {
- return;
- }
- synchronized (this) {
- if (clientAuth == mode) {
- // No need to issue any JNI calls if the mode is the same
- return;
- }
- switch (mode) {
- case NONE:
- SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, OpenSslContext.VERIFY_DEPTH);
- break;
- case REQUIRE:
- SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, OpenSslContext.VERIFY_DEPTH);
- break;
- case OPTIONAL:
- SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, OpenSslContext.VERIFY_DEPTH);
- break;
- }
- clientAuth = mode;
- }
- }
-
- @Override
- public void setEnableSessionCreation(boolean b) {
- if (b) {
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public boolean getEnableSessionCreation() {
- return false;
- }
-
- @Override
- public synchronized SSLParameters getSSLParameters() {
- SSLParameters sslParameters = super.getSSLParameters();
-
- int version = PlatformDependent.javaVersion();
- if (version >= 7) {
- sslParameters.setEndpointIdentificationAlgorithm(endPointIdentificationAlgorithm);
- SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints);
- if (version >= 8) {
- if (SET_SERVER_NAMES_METHOD != null && sniHostNames != null) {
- try {
- SET_SERVER_NAMES_METHOD.invoke(sslParameters, sniHostNames);
- } catch (IllegalAccessException e) {
- throw new Error(e);
- } catch (InvocationTargetException e) {
- throw new Error(e);
- }
- }
- if (SET_USE_CIPHER_SUITES_ORDER_METHOD != null && !isDestroyed()) {
- try {
- SET_USE_CIPHER_SUITES_ORDER_METHOD.invoke(sslParameters,
- (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0);
- } catch (IllegalAccessException e) {
- throw new Error(e);
- } catch (InvocationTargetException e) {
- throw new Error(e);
- }
- }
- }
- }
- return sslParameters;
- }
-
- @Override
- public synchronized void setSSLParameters(SSLParameters sslParameters) {
- super.setSSLParameters(sslParameters);
-
- int version = PlatformDependent.javaVersion();
- if (version >= 7) {
- endPointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm();
- algorithmConstraints = sslParameters.getAlgorithmConstraints();
- if (version >= 8) {
- if (SNI_HOSTNAME_CLASS != null && clientMode && !isDestroyed()) {
- assert GET_SERVER_NAMES_METHOD != null;
- assert GET_ASCII_NAME_METHOD != null;
- try {
- List> servernames = (List>) GET_SERVER_NAMES_METHOD.invoke(sslParameters);
- if (servernames != null) {
- for (Object serverName : servernames) {
- if (SNI_HOSTNAME_CLASS.isInstance(serverName)) {
- SSL.setTlsExtHostName(ssl, (String) GET_ASCII_NAME_METHOD.invoke(serverName));
- } else {
- throw new IllegalArgumentException("Only " + SNI_HOSTNAME_CLASS.getName()
- + " instances are supported, but found: " +
- serverName);
- }
- }
- }
- sniHostNames = servernames;
- } catch (IllegalAccessException e) {
- throw new Error(e);
- } catch (InvocationTargetException e) {
- throw new Error(e);
- }
- }
- if (GET_USE_CIPHER_SUITES_ORDER_METHOD != null && !isDestroyed()) {
- try {
- if ((Boolean) GET_USE_CIPHER_SUITES_ORDER_METHOD.invoke(sslParameters)) {
- SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
- } else {
- SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
- }
- } catch (IllegalAccessException e) {
- throw new Error(e);
- } catch (InvocationTargetException e) {
- throw new Error(e);
- }
- }
- }
- }
+ super(context, alloc, peerHost, peerPort, false);
}
@Override
@SuppressWarnings("FinalizeDeclaration")
protected void finalize() throws Throwable {
super.finalize();
- // Call shutdown as the user may have created the OpenSslEngine and not used it at all.
- shutdown();
- }
-
- private boolean isDestroyed() {
- return destroyed != 0;
- }
-
- private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor {
- private final OpenSslSessionContext sessionContext;
-
- // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any
- // thread.
- private X509Certificate[] x509PeerCerts;
- private String protocol;
- private String applicationProtocol;
- private Certificate[] peerCerts;
- private String cipher;
- private byte[] id;
- private long creationTime;
-
- // lazy init for memory reasons
- private Map values;
-
- OpenSslSession(OpenSslSessionContext sessionContext) {
- this.sessionContext = sessionContext;
- }
-
- @Override
- public byte[] getId() {
- synchronized (OpenSslEngine.this) {
- if (id == null) {
- return EmptyArrays.EMPTY_BYTES;
- }
- return id.clone();
- }
- }
-
- @Override
- public SSLSessionContext getSessionContext() {
- return sessionContext;
- }
-
- @Override
- public long getCreationTime() {
- synchronized (OpenSslEngine.this) {
- if (creationTime == 0 && !isDestroyed()) {
- creationTime = SSL.getTime(ssl) * 1000L;
- }
- }
- return creationTime;
- }
-
- @Override
- public long getLastAccessedTime() {
- long lastAccessed = OpenSslEngine.this.lastAccessed;
- // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet.
- return lastAccessed == -1 ? getCreationTime() : lastAccessed;
- }
-
- @Override
- public void invalidate() {
- synchronized (OpenSslEngine.this) {
- if (!isDestroyed()) {
- SSL.setTimeout(ssl, 0);
- }
- }
- }
-
- @Override
- public boolean isValid() {
- synchronized (OpenSslEngine.this) {
- if (!isDestroyed()) {
- return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L);
- }
- }
- return false;
- }
-
- @Override
- public void putValue(String name, Object value) {
- if (name == null) {
- throw new NullPointerException("name");
- }
- if (value == null) {
- throw new NullPointerException("value");
- }
- Map values = this.values;
- if (values == null) {
- // Use size of 2 to keep the memory overhead small
- values = this.values = new HashMap(2);
- }
- Object old = values.put(name, value);
- if (value instanceof SSLSessionBindingListener) {
- ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
- }
- notifyUnbound(old, name);
- }
-
- @Override
- public Object getValue(String name) {
- if (name == null) {
- throw new NullPointerException("name");
- }
- if (values == null) {
- return null;
- }
- return values.get(name);
- }
-
- @Override
- public void removeValue(String name) {
- if (name == null) {
- throw new NullPointerException("name");
- }
- Map values = this.values;
- if (values == null) {
- return;
- }
- Object old = values.remove(name);
- notifyUnbound(old, name);
- }
-
- @Override
- public String[] getValueNames() {
- Map values = this.values;
- if (values == null || values.isEmpty()) {
- return EmptyArrays.EMPTY_STRINGS;
- }
- return values.keySet().toArray(new String[values.size()]);
- }
-
- private void notifyUnbound(Object value, String name) {
- if (value instanceof SSLSessionBindingListener) {
- ((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name));
- }
- }
-
- /**
- * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessable by
- * the user.
- */
- void handshakeFinished() throws SSLException {
- synchronized (OpenSslEngine.this) {
- if (!isDestroyed()) {
- id = SSL.getSessionId(ssl);
- cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
- protocol = SSL.getVersion(ssl);
-
- initPeerCerts();
- selectApplicationProtocol();
-
- handshakeState = HandshakeState.FINISHED;
- } else {
- throw new SSLException("Already closed");
- }
- }
- }
-
- /**
- * Init peer certificates that can be obtained via {@link #getPeerCertificateChain()}
- * and {@link #getPeerCertificates()}.
- */
- private void initPeerCerts() {
- // Return the full chain from the JNI layer.
- byte[][] chain = SSL.getPeerCertChain(ssl);
- final byte[] clientCert;
- if (!clientMode) {
- // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer
- // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our
- // array later.
- //
- // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
- clientCert = SSL.getPeerCertificate(ssl);
- } else {
- clientCert = null;
- }
-
- if (chain == null && clientCert == null) {
- peerCerts = EMPTY_CERTIFICATES;
- x509PeerCerts = EMPTY_X509_CERTIFICATES;
- } else {
- int len = chain != null ? chain.length : 0;
-
- int i = 0;
- Certificate[] peerCerts;
- if (clientCert != null) {
- len++;
- peerCerts = new Certificate[len];
- peerCerts[i++] = new OpenSslX509Certificate(clientCert);
- } else {
- peerCerts = new Certificate[len];
- }
- if (chain != null) {
- X509Certificate[] pCerts = new X509Certificate[chain.length];
-
- for (int a = 0; a < pCerts.length; ++i, ++a) {
- byte[] bytes = chain[a];
- pCerts[a] = new OpenSslJavaxX509Certificate(bytes);
- peerCerts[i] = new OpenSslX509Certificate(bytes);
- }
- x509PeerCerts = pCerts;
- } else {
- x509PeerCerts = EMPTY_X509_CERTIFICATES;
- }
- this.peerCerts = peerCerts;
- }
- }
-
- /**
- * Select the application protocol used.
- */
- private void selectApplicationProtocol() throws SSLException {
- SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior();
- List protocols = apn.protocols();
- String applicationProtocol;
- switch (apn.protocol()) {
- case NONE:
- break;
- // We always need to check for applicationProtocol == null as the remote peer may not support
- // the TLS extension or may have returned an empty selection.
- case ALPN:
- applicationProtocol = SSL.getAlpnSelected(ssl);
- if (applicationProtocol != null) {
- this.applicationProtocol = selectApplicationProtocol(
- protocols, behavior, applicationProtocol);
- }
- break;
- case NPN:
- applicationProtocol = SSL.getNextProtoNegotiated(ssl);
- if (applicationProtocol != null) {
- this.applicationProtocol = selectApplicationProtocol(
- protocols, behavior, applicationProtocol);
- }
- break;
- case NPN_AND_ALPN:
- applicationProtocol = SSL.getAlpnSelected(ssl);
- if (applicationProtocol == null) {
- applicationProtocol = SSL.getNextProtoNegotiated(ssl);
- }
- if (applicationProtocol != null) {
- this.applicationProtocol = selectApplicationProtocol(
- protocols, behavior, applicationProtocol);
- }
- break;
- default:
- throw new Error();
- }
- }
-
- private String selectApplicationProtocol(List protocols,
- SelectedListenerFailureBehavior behavior,
- String applicationProtocol) throws SSLException {
- if (behavior == SelectedListenerFailureBehavior.ACCEPT) {
- return applicationProtocol;
- } else {
- int size = protocols.size();
- assert size > 0;
- if (protocols.contains(applicationProtocol)) {
- return applicationProtocol;
- } else {
- if (behavior == SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) {
- return protocols.get(size - 1);
- } else {
- throw new SSLException("unknown protocol " + applicationProtocol);
- }
- }
- }
- }
-
- @Override
- public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
- synchronized (OpenSslEngine.this) {
- if (peerCerts == null || peerCerts.length == 0) {
- throw new SSLPeerUnverifiedException("peer not verified");
- }
- return peerCerts;
- }
- }
-
- @Override
- public Certificate[] getLocalCertificates() {
- if (localCerts == null) {
- return null;
- }
- return localCerts.clone();
- }
-
- @Override
- public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
- synchronized (OpenSslEngine.this) {
- if (x509PeerCerts == null || x509PeerCerts.length == 0) {
- throw new SSLPeerUnverifiedException("peer not verified");
- }
- return x509PeerCerts;
- }
- }
-
- @Override
- public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- Certificate[] peer = getPeerCertificates();
- // No need for null or length > 0 is needed as this is done in getPeerCertificates()
- // already.
- return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
- }
-
- @Override
- public Principal getLocalPrincipal() {
- Certificate[] local = localCerts;
- if (local == null || local.length == 0) {
- return null;
- }
- return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
- }
-
- @Override
- public String getCipherSuite() {
- synchronized (OpenSslEngine.this) {
- if (cipher == null) {
- return INVALID_CIPHER;
- }
- return cipher;
- }
- }
-
- @Override
- public String getProtocol() {
- String protocol = this.protocol;
- if (protocol == null) {
- synchronized (OpenSslEngine.this) {
- if (!isDestroyed()) {
- protocol = SSL.getVersion(ssl);
- } else {
- protocol = StringUtil.EMPTY_STRING;
- }
- }
- }
- return protocol;
- }
-
- @Override
- public String getApplicationProtocol() {
- synchronized (OpenSslEngine.this) {
- return applicationProtocol;
- }
- }
-
- @Override
- public String getPeerHost() {
- return OpenSslEngine.this.getPeerHost();
- }
-
- @Override
- public int getPeerPort() {
- return OpenSslEngine.this.getPeerPort();
- }
-
- @Override
- public int getPacketBufferSize() {
- return MAX_ENCRYPTED_PACKET_LENGTH;
- }
-
- @Override
- public int getApplicationBufferSize() {
- return MAX_PLAINTEXT_LENGTH;
- }
+ safeRelease(this);
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java
index 14e9f02bce..02131b4b26 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java
@@ -21,15 +21,15 @@ interface OpenSslEngineMap {
* Remove the {@link OpenSslEngine} with the given {@code ssl} address and
* return it.
*/
- OpenSslEngine remove(long ssl);
+ ReferenceCountedOpenSslEngine remove(long ssl);
/**
* Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}.
*/
- void add(OpenSslEngine engine);
+ void add(ReferenceCountedOpenSslEngine engine);
/**
* Get the {@link OpenSslEngine} for the given {@code ssl} address.
*/
- OpenSslEngine get(long ssl);
+ ReferenceCountedOpenSslEngine get(long ssl);
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java
index 6ade0ae9d0..38f6a7f723 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java
@@ -28,12 +28,13 @@ final class OpenSslExtendedKeyMaterialManager extends OpenSslKeyMaterialManager
}
@Override
- protected String chooseClientAlias(OpenSslEngine engine, String[] keyTypes, X500Principal[] issuer) {
+ protected String chooseClientAlias(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
+ X500Principal[] issuer) {
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
}
@Override
- protected String chooseServerAlias(OpenSslEngine engine, String type) {
+ protected String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) {
return keyManager.chooseEngineServerAlias(type, null, engine);
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java
index e5f5fbe1de..5a297614a9 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java
@@ -65,7 +65,7 @@ class OpenSslKeyMaterialManager {
this.password = password;
}
- void setKeyMaterial(OpenSslEngine engine) throws SSLException {
+ void setKeyMaterial(ReferenceCountedOpenSslEngine engine) throws SSLException {
long ssl = engine.sslPointer();
String[] authMethods = SSL.authenticationMethods(ssl);
Set aliases = new HashSet(authMethods.length);
@@ -80,7 +80,8 @@ class OpenSslKeyMaterialManager {
}
}
- void setKeyMaterial(OpenSslEngine engine, String[] keyTypes, X500Principal[] issuer) throws SSLException {
+ void setKeyMaterial(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
+ X500Principal[] issuer) throws SSLException {
setKeyMaterial(engine.sslPointer(), chooseClientAlias(engine, keyTypes, issuer));
}
@@ -116,12 +117,12 @@ class OpenSslKeyMaterialManager {
}
}
- protected String chooseClientAlias(@SuppressWarnings("unused") OpenSslEngine engine,
+ protected String chooseClientAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine,
String[] keyTypes, X500Principal[] issuer) {
return keyManager.chooseClientAlias(keyTypes, issuer, null);
}
- protected String chooseServerAlias(@SuppressWarnings("unused") OpenSslEngine engine, String type) {
+ protected String chooseServerAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, String type) {
return keyManager.chooseServerAlias(type, null, null);
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
index 82ba578c46..a71d5d35dd 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java
@@ -15,30 +15,27 @@
*/
package io.netty.handler.ssl;
+import io.netty.handler.ssl.ReferenceCountedOpenSslServerContext.ServerContext;
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.KeyManager;
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;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import static io.netty.util.internal.ObjectUtil.*;
+import static io.netty.handler.ssl.ReferenceCountedOpenSslServerContext.newSessionContext;
/**
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
+ * 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 ReferenceCountedOpenSslServerContext}.
*/
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;
@@ -349,74 +346,14 @@ public final class OpenSslServerContext extends OpenSslContext {
// Create a new SSL_CTX and configure it.
boolean success = false;
try {
- synchronized (OpenSslContext.class) {
- try {
- SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
- if (!OpenSsl.useKeyManagerFactory()) {
- if (keyManagerFactory != null) {
- throw new IllegalArgumentException(
- "KeyManagerFactory not supported");
- }
-
- /* 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(keyManagerFactory.getKeyManagers());
- keyMaterialManager = useExtendedKeyManager(keyManager) ?
- new OpenSslExtendedKeyMaterialManager(
- (X509ExtendedKeyManager) keyManager, keyPassword) :
- new OpenSslKeyMaterialManager(keyManager, keyPassword);
- } else {
- keyMaterialManager = null;
- }
- }
- } catch (Exception e) {
- throw new SSLException("failed to set certificate and key", e);
- }
- try {
- if (trustCertCollection != null) {
- trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
- } else if (trustManagerFactory == null) {
- // Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
- 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 OpenSslServerSessionContext(this);
- sessionContext.setSessionIdContext(ID);
+ ServerContext context = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
+ keyCertChain, key, keyPassword, keyManagerFactory);
+ sessionContext = context.sessionContext;
+ keyMaterialManager = context.keyMaterialManager;
success = true;
} finally {
if (!success) {
- destroy();
+ release();
}
}
}
@@ -430,34 +367,4 @@ public final class OpenSslServerContext extends OpenSslContext {
OpenSslKeyMaterialManager keyMaterialManager() {
return keyMaterialManager;
}
-
- 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.checkClientTrusted(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.checkClientTrusted(peerCerts, auth, engine);
- }
- }
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java
index 7634897d87..c6687f6668 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java
@@ -23,7 +23,7 @@ import org.apache.tomcat.jni.SSLContext;
* {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side.
*/
public final class OpenSslServerSessionContext extends OpenSslSessionContext {
- OpenSslServerSessionContext(OpenSslContext context) {
+ OpenSslServerSessionContext(ReferenceCountedOpenSslContext context) {
super(context);
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java
index bcc76159ff..df13d6ab4c 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java
@@ -32,13 +32,13 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
private static final Enumeration EMPTY = new EmptyEnumeration();
private final OpenSslSessionStats stats;
- final OpenSslContext context;
+ final ReferenceCountedOpenSslContext context;
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
// the GC to collect OpenSslContext as this would also free the pointer and so could result in a
// segfault when the user calls any of the methods here that try to pass the pointer down to the native
// level.
- OpenSslSessionContext(OpenSslContext context) {
+ OpenSslSessionContext(ReferenceCountedOpenSslContext context) {
this.context = context;
stats = new OpenSslSessionStats(context);
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java
index 26d0d33e99..ff93a04b81 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java
@@ -25,13 +25,13 @@ import org.apache.tomcat.jni.SSLContext;
*/
public final class OpenSslSessionStats {
- private final OpenSslContext context;
+ private final ReferenceCountedOpenSslContext context;
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
// the GC to collect OpenSslContext as this would also free the pointer and so could result in a
// segfault when the user calls any of the methods here that try to pass the pointer down to the native
// level.
- OpenSslSessionStats(OpenSslContext context) {
+ OpenSslSessionStats(ReferenceCountedOpenSslContext context) {
this.context = context;
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java
new file mode 100644
index 0000000000..43c79c2002
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java
@@ -0,0 +1,295 @@
+/*
+ * 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 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.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+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;
+
+/**
+ * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
+ * Instances of this class must be {@link #release() released} or else native memory will leak!
+ */
+public final class ReferenceCountedOpenSslClientContext extends ReferenceCountedOpenSslContext {
+ private static final InternalLogger logger =
+ InternalLoggerFactory.getInstance(ReferenceCountedOpenSslClientContext.class);
+ private final OpenSslSessionContext sessionContext;
+
+ ReferenceCountedOpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
+ X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
+ KeyManagerFactory keyManagerFactory, Iterable ciphers,
+ CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+ long sessionCacheSize, long sessionTimeout)
+ throws SSLException {
+ super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
+ ClientAuth.NONE, true);
+ boolean success = false;
+ try {
+ sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
+ keyCertChain, key, keyPassword, keyManagerFactory);
+ success = true;
+ } finally {
+ if (!success) {
+ release();
+ }
+ }
+ }
+
+ @Override
+ OpenSslKeyMaterialManager keyMaterialManager() {
+ return null;
+ }
+
+ @Override
+ public OpenSslSessionContext sessionContext() {
+ return sessionContext;
+ }
+
+ static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx,
+ OpenSslEngineMap engineMap,
+ X509Certificate[] trustCertCollection,
+ TrustManagerFactory trustManagerFactory,
+ X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
+ KeyManagerFactory keyManagerFactory) throws SSLException {
+ 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 (ReferenceCountedOpenSslContext.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 {
+ // javadocs state that keyManagerFactory has precedent over keyCertChain
+ if (keyManagerFactory == null && 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);
+ }
+ }
+ return new OpenSslClientSessionContext(thiz);
+ }
+
+ // No cache is currently supported for client side mode.
+ static final class OpenSslClientSessionContext extends OpenSslSessionContext {
+ OpenSslClientSessionContext(ReferenceCountedOpenSslContext 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(ReferenceCountedOpenSslEngine 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(ReferenceCountedOpenSslEngine 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 ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
+ try {
+ final Set 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 supportedClientKeyTypes(byte[] clientCertificateTypes) {
+ Set result = new HashSet(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;
+ }
+ }
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java
new file mode 100644
index 0000000000..1d1dbfdbc6
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java
@@ -0,0 +1,746 @@
+/*
+ * 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 io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.AbstractReferenceCounted;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeak;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetectorFactory;
+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;
+import org.apache.tomcat.jni.CertificateVerifier;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+
+import java.security.AccessController;
+import java.security.PrivateKey;
+import java.security.PrivilegedAction;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateRevokedException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+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 static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+/**
+ * An implementation of {@link SslContext} which works with libraries that support the
+ * OpenSsl C library API.
+ * Instances of this class must be {@link #release() released} or else native memory will leak!
+ */
+public abstract class ReferenceCountedOpenSslContext extends SslContext implements ReferenceCounted {
+ private static final InternalLogger logger =
+ InternalLoggerFactory.getInstance(ReferenceCountedOpenSslContext.class);
+ /**
+ * 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.
+ *
+ * See also
+ * Significant SSL/TLS improvements in Java 8
+ */
+ private static final boolean JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION =
+ SystemPropertyUtil.getBoolean("jdk.tls.rejectClientInitiatedRenegotiation", false);
+ private static final List DEFAULT_CIPHERS;
+ private static final Integer DH_KEY_LENGTH;
+ private static final ResourceLeakDetector leakDetector =
+ ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslContext.class);
+
+ // TODO: Maybe make configurable ?
+ protected static final int VERIFY_DEPTH = 10;
+
+ /**
+ * The OpenSSL SSL_CTX object
+ */
+ protected volatile long ctx;
+ long aprPool;
+ @SuppressWarnings({ "unused", "FieldMayBeFinal" })
+ private volatile int aprPoolDestroyed;
+ private final List unmodifiableCiphers;
+ private final long sessionCacheSize;
+ private final long sessionTimeout;
+ private final OpenSslApplicationProtocolNegotiator apn;
+ private final int mode;
+
+ // Reference Counting
+ private final ResourceLeak leak;
+ private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() {
+ @Override
+ public ReferenceCounted touch(Object hint) {
+ if (leak != null) {
+ leak.record(hint);
+ }
+
+ return ReferenceCountedOpenSslContext.this;
+ }
+
+ @Override
+ protected void deallocate() {
+ destroy();
+ if (leak != null) {
+ leak.close();
+ }
+ }
+ };
+
+ final Certificate[] keyCertChain;
+ final ClientAuth clientAuth;
+ final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
+ volatile boolean rejectRemoteInitiatedRenegotiation;
+
+ static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR =
+ new OpenSslApplicationProtocolNegotiator() {
+ @Override
+ public ApplicationProtocolConfig.Protocol protocol() {
+ return ApplicationProtocolConfig.Protocol.NONE;
+ }
+
+ @Override
+ public List protocols() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() {
+ return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL;
+ }
+
+ @Override
+ public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() {
+ return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT;
+ }
+ };
+
+ static {
+ List ciphers = new ArrayList();
+ // XXX: Make sure to sync this list with JdkSslEngineFactory.
+ Collections.addAll(
+ ciphers,
+ "ECDHE-RSA-AES128-GCM-SHA256",
+ "ECDHE-RSA-AES128-SHA",
+ "ECDHE-RSA-AES256-SHA",
+ "AES128-GCM-SHA256",
+ "AES128-SHA",
+ "AES256-SHA",
+ "DES-CBC3-SHA");
+ DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Default cipher suite (OpenSSL): " + ciphers);
+ }
+
+ Integer dhLen = null;
+
+ try {
+ String dhKeySize = AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public String run() {
+ return SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize");
+ }
+ });
+ if (dhKeySize != null) {
+ try {
+ dhLen = Integer.valueOf(dhKeySize);
+ } catch (NumberFormatException e) {
+ logger.debug("ReferenceCountedOpenSslContext supports -Djdk.tls.ephemeralDHKeySize={int}, but got: "
+ + dhKeySize);
+ }
+ }
+ } catch (Throwable ignore) {
+ // ignore
+ }
+ DH_KEY_LENGTH = dhLen;
+ }
+
+ ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter,
+ ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout,
+ int mode, Certificate[] keyCertChain, ClientAuth clientAuth, boolean leakDetection)
+ throws SSLException {
+ this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
+ clientAuth, leakDetection);
+ }
+
+ ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter,
+ OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
+ long sessionTimeout, int mode, Certificate[] keyCertChain,
+ ClientAuth clientAuth, boolean leakDetection) throws SSLException {
+ OpenSsl.ensureAvailability();
+
+ if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) {
+ throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT");
+ }
+ leak = leakDetection ? leakDetector.open(this) : null;
+ this.mode = mode;
+ this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
+
+ if (mode == SSL.SSL_MODE_SERVER) {
+ rejectRemoteInitiatedRenegotiation =
+ JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION;
+ }
+ this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone();
+ final List convertedCiphers;
+ if (ciphers == null) {
+ convertedCiphers = null;
+ } else {
+ convertedCiphers = new ArrayList();
+ for (String c : ciphers) {
+ if (c == null) {
+ break;
+ }
+
+ String converted = CipherSuiteConverter.toOpenSsl(c);
+ if (converted != null) {
+ c = converted;
+ }
+ convertedCiphers.add(c);
+ }
+ }
+
+ unmodifiableCiphers = Arrays.asList(checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
+ convertedCiphers, DEFAULT_CIPHERS, OpenSsl.availableCipherSuites()));
+
+ this.apn = checkNotNull(apn, "apn");
+
+ // Allocate a new APR pool.
+ aprPool = Pool.create(0);
+
+ // Create a new SSL_CTX and configure it.
+ boolean success = false;
+ try {
+ synchronized (ReferenceCountedOpenSslContext.class) {
+ try {
+ ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, mode);
+ } catch (Exception e) {
+ throw new SSLException("failed to create an SSL_CTX", e);
+ }
+
+ SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE);
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+ // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK.
+ // This also let SSLSession.getId() work the same way for the JDK implementation and the OpenSSLEngine.
+ // If tickets are supported SSLSession.getId() will only return an ID on the server-side if it could
+ // make use of tickets.
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_TICKET);
+
+ // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between
+ // calling OpenSSLEngine.wrap(...).
+ // See https://github.com/netty/netty-tcnative/issues/100
+ SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ if (DH_KEY_LENGTH != null) {
+ SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH);
+ }
+
+ /* List the ciphers that are permitted to negotiate. */
+ try {
+ SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(unmodifiableCiphers));
+ } catch (SSLException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e);
+ }
+
+ List nextProtoList = apn.protocols();
+ /* Set next protocols for next protocol negotiation extension, if specified */
+ if (!nextProtoList.isEmpty()) {
+ String[] protocols = nextProtoList.toArray(new String[nextProtoList.size()]);
+ 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();
+ }
+ }
+
+ /* Set session cache size, if specified */
+ if (sessionCacheSize > 0) {
+ this.sessionCacheSize = sessionCacheSize;
+ SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
+ } else {
+ // Get the default session cache size using SSLContext.setSessionCacheSize()
+ this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
+ // Revert the session cache size to the default value.
+ SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
+ }
+
+ /* Set session timeout, if specified */
+ if (sessionTimeout > 0) {
+ this.sessionTimeout = sessionTimeout;
+ SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
+ } else {
+ // Get the default session timeout using SSLContext.setSessionCacheTimeout()
+ this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
+ // Revert the session timeout to the default value.
+ SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
+ }
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ release();
+ }
+ }
+ }
+
+ private static int opensslSelectorFailureBehavior(ApplicationProtocolConfig.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();
+ }
+ }
+
+ @Override
+ public final List cipherSuites() {
+ return unmodifiableCiphers;
+ }
+
+ @Override
+ public final long sessionCacheSize() {
+ return sessionCacheSize;
+ }
+
+ @Override
+ public final long sessionTimeout() {
+ return sessionTimeout;
+ }
+
+ @Override
+ public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
+ return apn;
+ }
+
+ @Override
+ public final boolean isClient() {
+ return mode == SSL.SSL_MODE_CLIENT;
+ }
+
+ @Override
+ public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
+ return newEngine0(alloc, peerHost, peerPort);
+ }
+
+ SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) {
+ return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, true);
+ }
+
+ abstract OpenSslKeyMaterialManager keyMaterialManager();
+
+ /**
+ * Returns a new server-side {@link SSLEngine} with the current configuration.
+ */
+ @Override
+ public final SSLEngine newEngine(ByteBufAllocator alloc) {
+ return newEngine(alloc, null, -1);
+ }
+
+ /**
+ * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}.
+ * Be aware that it is freed as soon as the {@link #finalize()} method is called.
+ * At this point {@code 0} will be returned.
+ *
+ * @deprecated use {@link #sslCtxPointer()}
+ */
+ @Deprecated
+ public final long context() {
+ return ctx;
+ }
+
+ /**
+ * Returns the stats of this context.
+ *
+ * @deprecated use {@link #sessionContext#stats()}
+ */
+ @Deprecated
+ public final OpenSslSessionStats stats() {
+ return sessionContext().stats();
+ }
+
+ /**
+ * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries
+ * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding.
+ */
+ public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) {
+ this.rejectRemoteInitiatedRenegotiation = rejectRemoteInitiatedRenegotiation;
+ }
+
+ /**
+ * Sets the SSL session ticket keys of this context.
+ *
+ * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
+ */
+ @Deprecated
+ public final void setTicketKeys(byte[] keys) {
+ sessionContext().setTicketKeys(keys);
+ }
+
+ @Override
+ public abstract OpenSslSessionContext sessionContext();
+
+ /**
+ * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}.
+ * Be aware that it is freed as soon as the {@link #release()} method is called.
+ * At this point {@code 0} will be returned.
+ */
+ public final long sslCtxPointer() {
+ return ctx;
+ }
+
+ // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never
+ // get access to an OpenSslSessionContext after this method was called to prevent the user from
+ // producing a segfault.
+ final void destroy() {
+ synchronized (ReferenceCountedOpenSslContext.class) {
+ if (ctx != 0) {
+ SSLContext.free(ctx);
+ ctx = 0;
+ }
+
+ // Guard against multiple destroyPools() calls triggered by construction exception and finalize() later
+ if (aprPool != 0) {
+ Pool.destroy(aprPool);
+ aprPool = 0;
+ }
+ }
+ }
+
+ protected static X509Certificate[] certificates(byte[][] chain) {
+ X509Certificate[] peerCerts = new X509Certificate[chain.length];
+ for (int i = 0; i < peerCerts.length; i++) {
+ peerCerts[i] = new OpenSslX509Certificate(chain[i]);
+ }
+ return peerCerts;
+ }
+
+ protected static X509TrustManager chooseTrustManager(TrustManager[] managers) {
+ for (TrustManager m : managers) {
+ if (m instanceof X509TrustManager) {
+ return (X509TrustManager) m;
+ }
+ }
+ 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
+ */
+ static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) {
+ if (config == null) {
+ return NONE_PROTOCOL_NEGOTIATOR;
+ }
+
+ 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());
+ }
+ default:
+ throw new UnsupportedOperationException(
+ new StringBuilder("OpenSSL provider does not support ")
+ .append(config.selectedListenerFailureBehavior())
+ .append(" behavior").toString());
+ }
+ default:
+ throw new Error();
+ }
+ }
+
+ static boolean useExtendedTrustManager(X509TrustManager trustManager) {
+ return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
+ }
+
+ static boolean useExtendedKeyManager(X509KeyManager keyManager) {
+ return PlatformDependent.javaVersion() >= 7 && keyManager instanceof X509ExtendedKeyManager;
+ }
+
+ @Override
+ public final int refCnt() {
+ return refCnt.refCnt();
+ }
+
+ @Override
+ public final ReferenceCounted retain() {
+ refCnt.retain();
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted retain(int increment) {
+ refCnt.retain(increment);
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted touch() {
+ refCnt.touch();
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted touch(Object hint) {
+ refCnt.touch(hint);
+ return this;
+ }
+
+ @Override
+ public final boolean release() {
+ return refCnt.release();
+ }
+
+ @Override
+ public final boolean release(int decrement) {
+ return refCnt.release(decrement);
+ }
+
+ abstract static class AbstractCertificateVerifier implements CertificateVerifier {
+ private final OpenSslEngineMap engineMap;
+
+ AbstractCertificateVerifier(OpenSslEngineMap engineMap) {
+ this.engineMap = engineMap;
+ }
+
+ @Override
+ public final int verify(long ssl, byte[][] chain, String auth) {
+ X509Certificate[] peerCerts = certificates(chain);
+ final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
+ try {
+ verify(engine, peerCerts, auth);
+ return CertificateVerifier.X509_V_OK;
+ } catch (Throwable cause) {
+ logger.debug("verification of certificate failed", cause);
+ SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
+ e.initCause(cause);
+ engine.handshakeException = e;
+
+ if (cause instanceof OpenSslCertificateException) {
+ return ((OpenSslCertificateException) cause).errorCode();
+ }
+ if (cause instanceof CertificateExpiredException) {
+ return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED;
+ }
+ if (cause instanceof CertificateNotYetValidException) {
+ return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID;
+ }
+ if (PlatformDependent.javaVersion() >= 7 && cause instanceof CertificateRevokedException) {
+ return CertificateVerifier.X509_V_ERR_CERT_REVOKED;
+ }
+ return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
+ }
+ }
+
+ abstract void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts,
+ String auth) throws Exception;
+ }
+
+ private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
+ private final Map engines = PlatformDependent.newConcurrentHashMap();
+
+ @Override
+ public ReferenceCountedOpenSslEngine remove(long ssl) {
+ return engines.remove(ssl);
+ }
+
+ @Override
+ public void add(ReferenceCountedOpenSslEngine engine) {
+ engines.put(engine.sslPointer(), engine);
+ }
+
+ @Override
+ public ReferenceCountedOpenSslEngine 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 in-memory BIO
+ * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}.
+ */
+ static long toBIO(PrivateKey key) throws Exception {
+ if (key == null) {
+ return 0;
+ }
+
+ ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+ PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key);
+ try {
+ return toBIO(allocator, pem.retain());
+ } finally {
+ pem.release();
+ }
+ }
+
+ /**
+ * Return the pointer to a in-memory BIO
+ * 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 {
+ if (certChain == null) {
+ return 0;
+ }
+
+ if (certChain.length == 0) {
+ throw new IllegalArgumentException("certChain can't be empty");
+ }
+
+ ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+ PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain);
+ try {
+ return toBIO(allocator, pem.retain());
+ } finally {
+ pem.release();
+ }
+ }
+
+ private static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception {
+ try {
+ // We can turn direct buffers straight into BIOs. No need to
+ // make a yet another copy.
+ ByteBuf content = pem.content();
+
+ if (content.isDirect()) {
+ return newBIO(content.retainedSlice());
+ }
+
+ ByteBuf buffer = allocator.directBuffer(content.readableBytes());
+ try {
+ buffer.writeBytes(content, content.readerIndex(), content.readableBytes());
+ return newBIO(buffer.retainedSlice());
+ } finally {
+ try {
+ // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we
+ // need to zero out the bytes of the copy before we're releasing it.
+ if (pem.isSensitive()) {
+ SslUtils.zeroout(buffer);
+ }
+ } finally {
+ buffer.release();
+ }
+ }
+ } finally {
+ pem.release();
+ }
+ }
+
+ private static long newBIO(ByteBuf buffer) throws Exception {
+ try {
+ long bio = SSL.newMemBIO();
+ int readable = buffer.readableBytes();
+ if (SSL.writeToBIO(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) {
+ SSL.freeBIO(bio);
+ throw new IllegalStateException("Could not write data to memory BIO");
+ }
+ return bio;
+ } finally {
+ buffer.release();
+ }
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java
new file mode 100644
index 0000000000..cf84b5a099
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java
@@ -0,0 +1,1951 @@
+/*
+ * 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 io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.Unpooled;
+import io.netty.util.AbstractReferenceCounted;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeak;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetectorFactory;
+import io.netty.util.internal.EmptyArrays;
+import io.netty.util.internal.InternalThreadLocalMap;
+import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.StringUtil;
+import io.netty.util.internal.ThrowableUtil;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+import org.apache.tomcat.jni.Buffer;
+import org.apache.tomcat.jni.SSL;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.X509Certificate;
+
+import static io.netty.handler.ssl.OpenSsl.memoryAddress;
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW;
+import static javax.net.ssl.SSLEngineResult.Status.CLOSED;
+import static javax.net.ssl.SSLEngineResult.Status.OK;
+
+/**
+ * Implements a {@link SSLEngine} using
+ * OpenSSL BIO abstractions.
+ * Instances of this class must be {@link #release() released} or else native memory will leak!
+ */
+public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class);
+
+ private static final Certificate[] EMPTY_CERTIFICATES = EmptyArrays.EMPTY_CERTIFICATES;
+ private static final X509Certificate[] EMPTY_X509_CERTIFICATES = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
+
+ private static final SSLException BEGIN_HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace(
+ new SSLException("engine closed"), ReferenceCountedOpenSslEngine.class, "beginHandshake()");
+ private static final SSLException HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace(
+ new SSLException("engine closed"), ReferenceCountedOpenSslEngine.class, "handshake()");
+ private static final SSLException RENEGOTIATION_UNSUPPORTED = ThrowableUtil.unknownStackTrace(
+ new SSLException("renegotiation unsupported"), ReferenceCountedOpenSslEngine.class, "beginHandshake()");
+ private static final SSLException ENCRYPTED_PACKET_OVERSIZED = ThrowableUtil.unknownStackTrace(
+ new SSLException("encrypted packet oversized"), ReferenceCountedOpenSslEngine.class, "unwrap(...)");
+ private static final Class> SNI_HOSTNAME_CLASS;
+ private static final Method GET_SERVER_NAMES_METHOD;
+ private static final Method SET_SERVER_NAMES_METHOD;
+ private static final Method GET_ASCII_NAME_METHOD;
+ private static final Method GET_USE_CIPHER_SUITES_ORDER_METHOD;
+ private static final Method SET_USE_CIPHER_SUITES_ORDER_METHOD;
+ private static final ResourceLeakDetector leakDetector =
+ ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class);
+
+ static {
+ AtomicIntegerFieldUpdater destroyedUpdater =
+ PlatformDependent.newAtomicIntegerFieldUpdater(ReferenceCountedOpenSslEngine.class, "destroyed");
+ if (destroyedUpdater == null) {
+ destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(ReferenceCountedOpenSslEngine.class, "destroyed");
+ }
+ DESTROYED_UPDATER = destroyedUpdater;
+
+ Method getUseCipherSuitesOrderMethod = null;
+ Method setUseCipherSuitesOrderMethod = null;
+ Class> sniHostNameClass = null;
+ Method getAsciiNameMethod = null;
+ Method getServerNamesMethod = null;
+ Method setServerNamesMethod = null;
+ if (PlatformDependent.javaVersion() >= 8) {
+ try {
+ getUseCipherSuitesOrderMethod = SSLParameters.class.getDeclaredMethod("getUseCipherSuitesOrder");
+ SSLParameters parameters = new SSLParameters();
+ @SuppressWarnings("unused")
+ Boolean order = (Boolean) getUseCipherSuitesOrderMethod.invoke(parameters);
+ setUseCipherSuitesOrderMethod = SSLParameters.class.getDeclaredMethod("setUseCipherSuitesOrder",
+ boolean.class);
+ setUseCipherSuitesOrderMethod.invoke(parameters, true);
+ } catch (Throwable ignore) {
+ getUseCipherSuitesOrderMethod = null;
+ setUseCipherSuitesOrderMethod = null;
+ }
+ try {
+ sniHostNameClass = Class.forName("javax.net.ssl.SNIHostName", false,
+ PlatformDependent.getClassLoader(ReferenceCountedOpenSslEngine.class));
+ Object sniHostName = sniHostNameClass.getConstructor(String.class).newInstance("netty.io");
+ getAsciiNameMethod = sniHostNameClass.getDeclaredMethod("getAsciiName");
+ @SuppressWarnings("unused")
+ String name = (String) getAsciiNameMethod.invoke(sniHostName);
+
+ getServerNamesMethod = SSLParameters.class.getDeclaredMethod("getServerNames");
+ setServerNamesMethod = SSLParameters.class.getDeclaredMethod("setServerNames", List.class);
+ SSLParameters parameters = new SSLParameters();
+ @SuppressWarnings({ "rawtypes", "unused" })
+ List serverNames = (List) getServerNamesMethod.invoke(parameters);
+ setServerNamesMethod.invoke(parameters, Collections.emptyList());
+ } catch (Throwable ingore) {
+ sniHostNameClass = null;
+ getAsciiNameMethod = null;
+ getServerNamesMethod = null;
+ setServerNamesMethod = null;
+ }
+ }
+ GET_USE_CIPHER_SUITES_ORDER_METHOD = getUseCipherSuitesOrderMethod;
+ SET_USE_CIPHER_SUITES_ORDER_METHOD = setUseCipherSuitesOrderMethod;
+ SNI_HOSTNAME_CLASS = sniHostNameClass;
+ GET_ASCII_NAME_METHOD = getAsciiNameMethod;
+ GET_SERVER_NAMES_METHOD = getServerNamesMethod;
+ SET_SERVER_NAMES_METHOD = setServerNamesMethod;
+ }
+
+ private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
+ private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
+ private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
+
+ // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
+ static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
+
+ static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH;
+
+ private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER;
+
+ private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
+
+ private static final long EMPTY_ADDR = Buffer.address(Unpooled.EMPTY_BUFFER.nioBuffer());
+
+ private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0);
+ private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0);
+ private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0);
+ private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0);
+ private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0);
+
+ // OpenSSL state
+ private long ssl;
+ private long networkBIO;
+ private boolean certificateSet;
+
+ private enum HandshakeState {
+ /**
+ * Not started yet.
+ */
+ NOT_STARTED,
+ /**
+ * Started via unwrap/wrap.
+ */
+ STARTED_IMPLICITLY,
+ /**
+ * Started via {@link #beginHandshake()}.
+ */
+ STARTED_EXPLICITLY,
+
+ /**
+ * Handshake is finished.
+ */
+ FINISHED
+ }
+
+ private HandshakeState handshakeState = HandshakeState.NOT_STARTED;
+ private boolean receivedShutdown;
+ private volatile int destroyed;
+
+ // Reference Counting
+ private final ResourceLeak leak;
+ private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() {
+ @Override
+ public ReferenceCounted touch(Object hint) {
+ if (leak != null) {
+ leak.record(hint);
+ }
+
+ return ReferenceCountedOpenSslEngine.this;
+ }
+
+ @Override
+ protected void deallocate() {
+ shutdown();
+ if (leak != null) {
+ leak.close();
+ }
+ }
+ };
+
+ private volatile ClientAuth clientAuth = ClientAuth.NONE;
+
+ // Updated once a new handshake is started and so the SSLSession reused.
+ private volatile long lastAccessed = -1;
+
+ private String endPointIdentificationAlgorithm;
+ // Store as object as AlgorithmConstraints only exists since java 7.
+ private Object algorithmConstraints;
+ private List> sniHostNames;
+
+ // SSL Engine status variables
+ private boolean isInboundDone;
+ private boolean isOutboundDone;
+ private boolean engineClosed;
+
+ private final boolean clientMode;
+ private final ByteBufAllocator alloc;
+ private final OpenSslEngineMap engineMap;
+ private final OpenSslApplicationProtocolNegotiator apn;
+ private final boolean rejectRemoteInitiatedRenegation;
+ private final ReferenceCountedOpenSslEngine.OpenSslSession session;
+ 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;
+
+ /**
+ * Create a new instance.
+ * @param context Reference count release responsibility is not transferred! The callee still owns this object.
+ * @param alloc The allocator to use.
+ * @param peerHost The peer host name.
+ * @param peerPort The peer port.
+ * @param leakDetection {@code true} to enable leak detection of this object.
+ */
+ ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, ByteBufAllocator alloc, String peerHost,
+ int peerPort, boolean leakDetection) {
+ super(peerHost, peerPort);
+ OpenSsl.ensureAvailability();
+ leak = leakDetection ? leakDetector.open(this) : null;
+ this.alloc = checkNotNull(alloc, "alloc");
+ apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
+ ssl = SSL.newSSL(context.ctx, !context.isClient());
+ session = new ReferenceCountedOpenSslEngine.OpenSslSession(context.sessionContext());
+ networkBIO = SSL.makeNetworkBIO(ssl);
+ 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 : 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
+ public final int refCnt() {
+ return refCnt.refCnt();
+ }
+
+ @Override
+ public final ReferenceCounted retain() {
+ refCnt.retain();
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted retain(int increment) {
+ refCnt.retain(increment);
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted touch() {
+ refCnt.touch();
+ return this;
+ }
+
+ @Override
+ public final ReferenceCounted touch(Object hint) {
+ refCnt.touch(hint);
+ return this;
+ }
+
+ @Override
+ public final boolean release() {
+ return refCnt.release();
+ }
+
+ @Override
+ public final boolean release(int decrement) {
+ return refCnt.release(decrement);
+ }
+
+ @Override
+ public final synchronized SSLSession getHandshakeSession() {
+ // Javadocs state return value should be:
+ // null if this instance is not currently handshaking, or if the current handshake has not
+ // progressed far enough to create a basic SSLSession. Otherwise, this method returns the
+ // SSLSession currently being negotiated.
+ switch(handshakeState) {
+ case NOT_STARTED:
+ case FINISHED:
+ return null;
+ default:
+ return session;
+ }
+ }
+
+ /**
+ * Returns the pointer to the {@code SSL} object for this {@link ReferenceCountedOpenSslEngine}.
+ * Be aware that it is freed as soon as the {@link #release()} or {@link #shutdown()} methods are called.
+ * At this point {@code 0} will be returned.
+ */
+ public final synchronized long sslPointer() {
+ return ssl;
+ }
+
+ /**
+ * Destroys this engine.
+ */
+ public final synchronized void shutdown() {
+ if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
+ engineMap.remove(ssl);
+ SSL.freeSSL(ssl);
+ SSL.freeBIO(networkBIO);
+ ssl = networkBIO = 0;
+
+ // internal errors can cause shutdown without marking the engine closed
+ isInboundDone = isOutboundDone = engineClosed = true;
+ }
+
+ // On shutdown clear all errors
+ SSL.clearError();
+ }
+
+ /**
+ * Write plaintext data to the OpenSSL internal BIO
+ *
+ * Calling this function with src.remaining == 0 is undefined.
+ */
+ private int writePlaintextData(final ByteBuffer src) {
+ final int pos = src.position();
+ final int limit = src.limit();
+ final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH);
+ final int sslWrote;
+
+ if (src.isDirect()) {
+ final long addr = Buffer.address(src) + pos;
+ sslWrote = SSL.writeToSSL(ssl, addr, len);
+ if (sslWrote > 0) {
+ src.position(pos + sslWrote);
+ }
+ } else {
+ ByteBuf buf = alloc.directBuffer(len);
+ try {
+ final long addr = memoryAddress(buf);
+
+ src.limit(pos + len);
+
+ buf.setBytes(0, src);
+ src.limit(limit);
+
+ sslWrote = SSL.writeToSSL(ssl, addr, len);
+ if (sslWrote > 0) {
+ src.position(pos + sslWrote);
+ } else {
+ src.position(pos);
+ }
+ } finally {
+ buf.release();
+ }
+ }
+ return sslWrote;
+ }
+
+ /**
+ * Write encrypted data to the OpenSSL network BIO.
+ */
+ private int writeEncryptedData(final ByteBuffer src) {
+ final int pos = src.position();
+ final int len = src.remaining();
+ final int netWrote;
+ if (src.isDirect()) {
+ final long addr = Buffer.address(src) + pos;
+ netWrote = SSL.writeToBIO(networkBIO, addr, len);
+ if (netWrote >= 0) {
+ src.position(pos + netWrote);
+ }
+ } else {
+ final ByteBuf buf = alloc.directBuffer(len);
+ try {
+ final long addr = memoryAddress(buf);
+
+ buf.setBytes(0, src);
+
+ netWrote = SSL.writeToBIO(networkBIO, addr, len);
+ if (netWrote >= 0) {
+ src.position(pos + netWrote);
+ } else {
+ src.position(pos);
+ }
+ } finally {
+ buf.release();
+ }
+ }
+
+ return netWrote;
+ }
+
+ /**
+ * Read plaintext data from the OpenSSL internal BIO
+ */
+ private int readPlaintextData(final ByteBuffer dst) {
+ final int sslRead;
+ if (dst.isDirect()) {
+ final int pos = dst.position();
+ final long addr = Buffer.address(dst) + pos;
+ final int len = dst.limit() - pos;
+ sslRead = SSL.readFromSSL(ssl, addr, len);
+ if (sslRead > 0) {
+ dst.position(pos + sslRead);
+ }
+ } else {
+ final int pos = dst.position();
+ final int limit = dst.limit();
+ final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
+ final ByteBuf buf = alloc.directBuffer(len);
+ try {
+ final long addr = memoryAddress(buf);
+
+ sslRead = SSL.readFromSSL(ssl, addr, len);
+ if (sslRead > 0) {
+ dst.limit(pos + sslRead);
+ buf.getBytes(0, dst);
+ dst.limit(limit);
+ }
+ } finally {
+ buf.release();
+ }
+ }
+
+ return sslRead;
+ }
+
+ /**
+ * Read encrypted data from the OpenSSL network BIO
+ */
+ private int readEncryptedData(final ByteBuffer dst, final int pending) {
+ final int bioRead;
+
+ if (dst.isDirect() && dst.remaining() >= pending) {
+ final int pos = dst.position();
+ final long addr = Buffer.address(dst) + pos;
+ bioRead = SSL.readFromBIO(networkBIO, addr, pending);
+ if (bioRead > 0) {
+ dst.position(pos + bioRead);
+ return bioRead;
+ }
+ } else {
+ final ByteBuf buf = alloc.directBuffer(pending);
+ try {
+ final long addr = memoryAddress(buf);
+
+ bioRead = SSL.readFromBIO(networkBIO, addr, pending);
+ if (bioRead > 0) {
+ int oldLimit = dst.limit();
+ dst.limit(dst.position() + bioRead);
+ buf.getBytes(0, dst);
+ dst.limit(oldLimit);
+ return bioRead;
+ }
+ } finally {
+ buf.release();
+ }
+ }
+
+ return bioRead;
+ }
+
+ private SSLEngineResult readPendingBytesFromBIO(ByteBuffer dst, int bytesConsumed, int bytesProduced,
+ SSLEngineResult.HandshakeStatus status) throws SSLException {
+ // Check to see if the engine wrote data into the network BIO
+ int pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
+ if (pendingNet > 0) {
+
+ // Do we have enough room in dst to write encrypted data?
+ int capacity = dst.remaining();
+ if (capacity < pendingNet) {
+ return new SSLEngineResult(BUFFER_OVERFLOW,
+ mayFinishHandshake(status != FINISHED ? getHandshakeStatus(pendingNet) : status),
+ bytesConsumed, bytesProduced);
+ }
+
+ // Write the pending data from the network BIO into the dst buffer
+ int produced = readEncryptedData(dst, pendingNet);
+
+ if (produced <= 0) {
+ // We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call later
+ // on in which we will produce an exception in case of an error
+ SSL.clearError();
+ } else {
+ bytesProduced += produced;
+ pendingNet -= produced;
+ }
+ // If isOuboundDone is set, then the data from the network BIO
+ // was the close_notify message -- we are not required to wait
+ // for the receipt the peer's close_notify message -- shutdown.
+ if (isOutboundDone) {
+ shutdown();
+ }
+
+ return new SSLEngineResult(getEngineStatus(),
+ mayFinishHandshake(status != FINISHED ? getHandshakeStatus(pendingNet) : status),
+ bytesConsumed, bytesProduced);
+ }
+ return null;
+ }
+
+ @Override
+ public final SSLEngineResult wrap(
+ final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException {
+ // Throw required runtime exceptions
+ if (srcs == null) {
+ throw new IllegalArgumentException("srcs is null");
+ }
+ if (dst == null) {
+ throw new IllegalArgumentException("dst is null");
+ }
+
+ if (offset >= srcs.length || offset + length > srcs.length) {
+ throw new IndexOutOfBoundsException(
+ "offset: " + offset + ", length: " + length +
+ " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
+ }
+
+ if (dst.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+
+ synchronized (this) {
+ // Check to make sure the engine has not been closed
+ if (isDestroyed()) {
+ return CLOSED_NOT_HANDSHAKING;
+ }
+
+ SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING;
+ // Prepare OpenSSL to work in server mode and receive handshake
+ if (handshakeState != HandshakeState.FINISHED) {
+ if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
+ // Update accepted so we know we triggered the handshake via wrap
+ handshakeState = HandshakeState.STARTED_IMPLICITLY;
+ }
+
+ status = handshake();
+ if (status == NEED_UNWRAP) {
+ return NEED_UNWRAP_OK;
+ }
+
+ if (engineClosed) {
+ return NEED_UNWRAP_CLOSED;
+ }
+ }
+
+ // There was no pending data in the network BIO -- encrypt any application data
+ int bytesProduced = 0;
+ int bytesConsumed = 0;
+ int endOffset = offset + length;
+ for (int i = offset; i < endOffset; ++i) {
+ final ByteBuffer src = srcs[i];
+ if (src == null) {
+ throw new IllegalArgumentException("srcs[" + i + "] is null");
+ }
+ while (src.hasRemaining()) {
+ final SSLEngineResult pendingNetResult;
+ // Write plaintext application data to the SSL engine
+ int result = writePlaintextData(src);
+ if (result > 0) {
+ bytesConsumed += result;
+
+ pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
+ if (pendingNetResult != null) {
+ if (pendingNetResult.getStatus() != OK) {
+ return pendingNetResult;
+ }
+ bytesProduced = pendingNetResult.bytesProduced();
+ }
+ } else {
+ int sslError = SSL.getError(ssl, result);
+ switch (sslError) {
+ case SSL.SSL_ERROR_ZERO_RETURN:
+ // This means the connection was shutdown correctly, close inbound and outbound
+ if (!receivedShutdown) {
+ closeAll();
+ }
+ pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
+ return pendingNetResult != null ? pendingNetResult : CLOSED_NOT_HANDSHAKING;
+ case SSL.SSL_ERROR_WANT_READ:
+ // If there is no pending data to read from BIO we should go back to event loop and try
+ // to read more data [1]. It is also possible that event loop will detect the socket
+ // has been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
+ pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
+ return pendingNetResult != null ? pendingNetResult :
+ new SSLEngineResult(getEngineStatus(),
+ NEED_UNWRAP, bytesConsumed, bytesProduced);
+ case SSL.SSL_ERROR_WANT_WRITE:
+ // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable
+ // and we should set the "want write" flag on the selector and try again when the
+ // underlying transport is writable [1]. However we are not directly writing to the
+ // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation
+ // says we should do the following [1]:
+ //
+ // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved
+ // out of the BIO before being able to continue."
+ //
+ // So we attempt to drain the BIO buffer below, but if there is no data this condition
+ // is undefined and we assume their is a fatal error with the openssl engine and close.
+ // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
+ pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced, status);
+ return pendingNetResult != null ? pendingNetResult : NEED_WRAP_CLOSED;
+ default:
+ // Everything else is considered as error
+ throw shutdownWithError("SSL_write");
+ }
+ }
+ }
+ }
+ // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not checked if the srcs was
+ // empty, or only contained empty buffers.
+ if (bytesConsumed == 0) {
+ SSLEngineResult pendingNetResult = readPendingBytesFromBIO(dst, 0, bytesProduced, status);
+ if (pendingNetResult != null) {
+ return pendingNetResult;
+ }
+ }
+
+ return newResult(bytesConsumed, bytesProduced, status);
+ }
+ }
+
+ /**
+ * Log the error, shutdown the engine and throw an exception.
+ */
+ private SSLException shutdownWithError(String operations) {
+ String err = SSL.getLastError();
+ return shutdownWithError(operations, err);
+ }
+
+ private SSLException shutdownWithError(String operation, String err) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("{} failed: OpenSSL error: {}", operation, err);
+ }
+
+ // There was an internal error -- shutdown
+ shutdown();
+ if (handshakeState == HandshakeState.FINISHED) {
+ return new SSLException(err);
+ }
+ return new SSLHandshakeException(err);
+ }
+
+ public final SSLEngineResult unwrap(
+ final ByteBuffer[] srcs, int srcsOffset, final int srcsLength,
+ final ByteBuffer[] dsts, final int dstsOffset, final int dstsLength) throws SSLException {
+
+ // Throw required runtime exceptions
+ if (srcs == null) {
+ throw new NullPointerException("srcs");
+ }
+ if (srcsOffset >= srcs.length
+ || srcsOffset + srcsLength > srcs.length) {
+ throw new IndexOutOfBoundsException(
+ "offset: " + srcsOffset + ", length: " + srcsLength +
+ " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
+ }
+ if (dsts == null) {
+ throw new IllegalArgumentException("dsts is null");
+ }
+ if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) {
+ throw new IndexOutOfBoundsException(
+ "offset: " + dstsOffset + ", length: " + dstsLength +
+ " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
+ }
+ long capacity = 0;
+ final int endOffset = dstsOffset + dstsLength;
+ for (int i = dstsOffset; i < endOffset; i ++) {
+ ByteBuffer dst = dsts[i];
+ if (dst == null) {
+ throw new IllegalArgumentException("dsts[" + i + "] is null");
+ }
+ if (dst.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+ capacity += dst.remaining();
+ }
+
+ final int srcsEndOffset = srcsOffset + srcsLength;
+ long len = 0;
+ for (int i = srcsOffset; i < srcsEndOffset; i++) {
+ ByteBuffer src = srcs[i];
+ if (src == null) {
+ throw new IllegalArgumentException("srcs[" + i + "] is null");
+ }
+ len += src.remaining();
+ }
+
+ synchronized (this) {
+ // Check to make sure the engine has not been closed
+ if (isDestroyed()) {
+ return CLOSED_NOT_HANDSHAKING;
+ }
+
+ // protect against protocol overflow attack vector
+ if (len > MAX_ENCRYPTED_PACKET_LENGTH) {
+ isInboundDone = true;
+ isOutboundDone = true;
+ engineClosed = true;
+ shutdown();
+ throw ENCRYPTED_PACKET_OVERSIZED;
+ }
+
+ SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING;
+ // Prepare OpenSSL to work in server mode and receive handshake
+ if (handshakeState != HandshakeState.FINISHED) {
+ if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
+ // Update accepted so we know we triggered the handshake via wrap
+ handshakeState = HandshakeState.STARTED_IMPLICITLY;
+ }
+
+ status = handshake();
+ if (status == NEED_WRAP) {
+ return NEED_WRAP_OK;
+ }
+ if (engineClosed) {
+ return NEED_WRAP_CLOSED;
+ }
+ }
+
+ // Write encrypted data to network BIO
+ int bytesConsumed = 0;
+ if (srcsOffset < srcsEndOffset) {
+ do {
+ ByteBuffer src = srcs[srcsOffset];
+ int remaining = src.remaining();
+ if (remaining == 0) {
+ // We must skip empty buffers as BIO_write will return 0 if asked to write something
+ // with length 0.
+ srcsOffset++;
+ continue;
+ }
+ int written = writeEncryptedData(src);
+ if (written > 0) {
+ bytesConsumed += written;
+
+ if (written == remaining) {
+ srcsOffset++;
+ } else {
+ // We were not able to write everything into the BIO so break the write loop as otherwise
+ // we will produce an error on the next write attempt, which will trigger a SSL.clearError()
+ // later.
+ break;
+ }
+ } else {
+ // BIO_write returned a negative or zero number, this means we could not complete the write
+ // operation and should retry later.
+ // We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call
+ // later on in which we will produce an exception in case of an error
+ SSL.clearError();
+ break;
+ }
+ } while (srcsOffset < srcsEndOffset);
+ }
+
+ // Number of produced bytes
+ int bytesProduced = 0;
+
+ if (capacity > 0) {
+ // Write decrypted data to dsts buffers
+ int idx = dstsOffset;
+ while (idx < endOffset) {
+ ByteBuffer dst = dsts[idx];
+ if (!dst.hasRemaining()) {
+ idx++;
+ continue;
+ }
+
+ int bytesRead = readPlaintextData(dst);
+
+ // TODO: We may want to consider if we move this check and only do it in a less often called place
+ // at the price of not being 100% accurate, like for example when calling SSL.getError(...).
+ rejectRemoteInitiatedRenegation();
+
+ if (bytesRead > 0) {
+ bytesProduced += bytesRead;
+
+ if (!dst.hasRemaining()) {
+ idx++;
+ } else {
+ // We read everything return now.
+ return newResult(bytesConsumed, bytesProduced, status);
+ }
+ } else {
+ int sslError = SSL.getError(ssl, bytesRead);
+ switch (sslError) {
+ case SSL.SSL_ERROR_ZERO_RETURN:
+ // This means the connection was shutdown correctly, close inbound and outbound
+ if (!receivedShutdown) {
+ closeAll();
+ }
+ // fall-trough!
+ case SSL.SSL_ERROR_WANT_READ:
+ case SSL.SSL_ERROR_WANT_WRITE:
+ // break to the outer loop
+ return newResult(bytesConsumed, bytesProduced, status);
+ default:
+ return sslReadErrorResult(SSL.getLastErrorNumber(), bytesConsumed, bytesProduced);
+ }
+ }
+ }
+ } else {
+ // If the capacity of all destination buffers is 0 we need to trigger a SSL_read anyway to ensure
+ // everything is flushed in the BIO pair and so we can detect it in the pendingAppData() call.
+ if (SSL.readFromSSL(ssl, EMPTY_ADDR, 0) <= 0) {
+ // We do not check SSL_get_error as we are not interested in any error that is not fatal.
+ int err = SSL.getLastErrorNumber();
+ if (OpenSsl.isError(err)) {
+ return sslReadErrorResult(err, bytesConsumed, bytesProduced);
+ }
+ }
+ }
+ if (pendingAppData() > 0) {
+ // We filled all buffers but there is still some data pending in the BIO buffer, return BUFFER_OVERFLOW.
+ return new SSLEngineResult(
+ BUFFER_OVERFLOW, mayFinishHandshake(status != FINISHED ? getHandshakeStatus() : status),
+ bytesConsumed, bytesProduced);
+ }
+
+ // Check to see if we received a close_notify message from the peer.
+ if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
+ closeAll();
+ }
+
+ return newResult(bytesConsumed, bytesProduced, status);
+ }
+ }
+
+ private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced) throws SSLException {
+ String errStr = SSL.getErrorString(err);
+
+ // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the
+ // BIO first or can just shutdown and throw it now.
+ // This is needed so we ensure close_notify etc is correctly send to the remote peer.
+ // See https://github.com/netty/netty/issues/3900
+ if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) {
+ if (handshakeException == null && handshakeState != HandshakeState.FINISHED) {
+ // we seems to have data left that needs to be transfered and so the user needs
+ // call wrap(...). Store the error so we can pick it up later.
+ handshakeException = new SSLHandshakeException(errStr);
+ }
+ return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced);
+ }
+ throw shutdownWithError("SSL_read", errStr);
+ }
+
+ private int pendingAppData() {
+ // There won't be any application data until we're done handshaking.
+ // We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
+ return handshakeState == HandshakeState.FINISHED ? SSL.pendingReadableBytesInSSL(ssl) : 0;
+ }
+
+ private SSLEngineResult newResult(
+ int bytesConsumed, int bytesProduced, SSLEngineResult.HandshakeStatus status) throws SSLException {
+ return new SSLEngineResult(
+ getEngineStatus(), mayFinishHandshake(status != FINISHED ? getHandshakeStatus() : status)
+ , bytesConsumed, bytesProduced);
+ }
+
+ private void closeAll() throws SSLException {
+ receivedShutdown = true;
+ closeOutbound();
+ closeInbound();
+ }
+
+ private void rejectRemoteInitiatedRenegation() throws SSLHandshakeException {
+ if (rejectRemoteInitiatedRenegation && SSL.getHandshakeCount(ssl) > 1) {
+ // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it
+ // that the renegotiation failed.
+ shutdown();
+ throw new SSLHandshakeException("remote-initiated renegotation not allowed");
+ }
+ }
+
+ public final SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException {
+ return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length);
+ }
+
+ private ByteBuffer[] singleSrcBuffer(ByteBuffer src) {
+ singleSrcBuffer[0] = src;
+ return singleSrcBuffer;
+ }
+
+ private void resetSingleSrcBuffer() {
+ singleSrcBuffer[0] = null;
+ }
+
+ private ByteBuffer[] singleDstBuffer(ByteBuffer src) {
+ singleDstBuffer[0] = src;
+ return singleDstBuffer;
+ }
+
+ private void resetSingleDstBuffer() {
+ singleDstBuffer[0] = null;
+ }
+
+ @Override
+ public final synchronized SSLEngineResult unwrap(
+ final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
+ try {
+ return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length);
+ } finally {
+ resetSingleSrcBuffer();
+ }
+ }
+
+ @Override
+ public final synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+ try {
+ return wrap(singleSrcBuffer(src), dst);
+ } finally {
+ resetSingleSrcBuffer();
+ }
+ }
+
+ @Override
+ public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+ try {
+ return unwrap(singleSrcBuffer(src), singleDstBuffer(dst));
+ } finally {
+ resetSingleSrcBuffer();
+ resetSingleDstBuffer();
+ }
+ }
+
+ @Override
+ public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException {
+ try {
+ return unwrap(singleSrcBuffer(src), dsts);
+ } finally {
+ resetSingleSrcBuffer();
+ }
+ }
+
+ @Override
+ public final Runnable getDelegatedTask() {
+ // Currently, we do not delegate SSL computation tasks
+ // TODO: in the future, possibly create tasks to do encrypt / decrypt async
+
+ return null;
+ }
+
+ @Override
+ public final synchronized void closeInbound() throws SSLException {
+ if (isInboundDone) {
+ return;
+ }
+
+ isInboundDone = true;
+ engineClosed = true;
+
+ shutdown();
+
+ if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) {
+ throw new SSLException(
+ "Inbound closed before receiving peer's close_notify: possible truncation attack?");
+ }
+ }
+
+ @Override
+ public final synchronized boolean isInboundDone() {
+ return isInboundDone || engineClosed;
+ }
+
+ @Override
+ public final synchronized void closeOutbound() {
+ if (isOutboundDone) {
+ return;
+ }
+
+ isOutboundDone = true;
+ engineClosed = true;
+
+ if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) {
+ int mode = SSL.getShutdown(ssl);
+ if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
+ int err = SSL.shutdownSSL(ssl);
+ if (err < 0) {
+ int sslErr = SSL.getError(ssl, err);
+ switch (sslErr) {
+ case SSL.SSL_ERROR_NONE:
+ case SSL.SSL_ERROR_WANT_ACCEPT:
+ case SSL.SSL_ERROR_WANT_CONNECT:
+ case SSL.SSL_ERROR_WANT_WRITE:
+ case SSL.SSL_ERROR_WANT_READ:
+ case SSL.SSL_ERROR_WANT_X509_LOOKUP:
+ case SSL.SSL_ERROR_ZERO_RETURN:
+ // Nothing to do here
+ break;
+ case SSL.SSL_ERROR_SYSCALL:
+ case SSL.SSL_ERROR_SSL:
+ if (logger.isDebugEnabled()) {
+ logger.debug("SSL_shutdown failed: OpenSSL error: {}", SSL.getLastError());
+ }
+ // There was an internal error -- shutdown
+ shutdown();
+ break;
+ default:
+ SSL.clearError();
+ break;
+ }
+ }
+ }
+ } else {
+ // engine closing before initial handshake
+ shutdown();
+ }
+ }
+
+ @Override
+ public final synchronized boolean isOutboundDone() {
+ return isOutboundDone;
+ }
+
+ @Override
+ public final String[] getSupportedCipherSuites() {
+ return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(new String[OpenSsl.AVAILABLE_CIPHER_SUITES.size()]);
+ }
+
+ @Override
+ public final String[] getEnabledCipherSuites() {
+ final String[] enabled;
+ synchronized (this) {
+ if (!isDestroyed()) {
+ enabled = SSL.getCiphers(ssl);
+ } else {
+ return EmptyArrays.EMPTY_STRINGS;
+ }
+ }
+ if (enabled == null) {
+ return EmptyArrays.EMPTY_STRINGS;
+ } else {
+ synchronized (this) {
+ for (int i = 0; i < enabled.length; i++) {
+ String mapped = toJavaCipherSuite(enabled[i]);
+ if (mapped != null) {
+ enabled[i] = mapped;
+ }
+ }
+ }
+ return enabled;
+ }
+ }
+
+ @Override
+ public final void setEnabledCipherSuites(String[] cipherSuites) {
+ checkNotNull(cipherSuites, "cipherSuites");
+
+ final StringBuilder buf = new StringBuilder();
+ for (String c: cipherSuites) {
+ if (c == null) {
+ break;
+ }
+
+ String converted = CipherSuiteConverter.toOpenSsl(c);
+ if (converted == null) {
+ converted = c;
+ }
+
+ if (!OpenSsl.isCipherSuiteAvailable(converted)) {
+ throw new IllegalArgumentException("unsupported cipher suite: " + c + '(' + converted + ')');
+ }
+
+ buf.append(converted);
+ buf.append(':');
+ }
+
+ if (buf.length() == 0) {
+ throw new IllegalArgumentException("empty cipher suites");
+ }
+ buf.setLength(buf.length() - 1);
+
+ final String cipherSuiteSpec = buf.toString();
+
+ synchronized (this) {
+ if (!isDestroyed()) {
+ try {
+ SSL.setCipherSuites(ssl, cipherSuiteSpec);
+ } catch (Exception e) {
+ throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e);
+ }
+ } else {
+ throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec);
+ }
+ }
+ }
+
+ @Override
+ public final String[] getSupportedProtocols() {
+ return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(new String[OpenSsl.SUPPORTED_PROTOCOLS_SET.size()]);
+ }
+
+ @Override
+ public final String[] getEnabledProtocols() {
+ List enabled = InternalThreadLocalMap.get().arrayList();
+ // Seems like there is no way to explict disable SSLv2Hello in openssl so it is always enabled
+ enabled.add(OpenSsl.PROTOCOL_SSL_V2_HELLO);
+
+ int opts;
+ synchronized (this) {
+ if (!isDestroyed()) {
+ opts = SSL.getOptions(ssl);
+ } else {
+ return enabled.toArray(new String[1]);
+ }
+ }
+ if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) {
+ enabled.add(OpenSsl.PROTOCOL_TLS_V1);
+ }
+ if ((opts & SSL.SSL_OP_NO_TLSv1_1) == 0) {
+ enabled.add(OpenSsl.PROTOCOL_TLS_V1_1);
+ }
+ if ((opts & SSL.SSL_OP_NO_TLSv1_2) == 0) {
+ enabled.add(OpenSsl.PROTOCOL_TLS_V1_2);
+ }
+ if ((opts & SSL.SSL_OP_NO_SSLv2) == 0) {
+ enabled.add(OpenSsl.PROTOCOL_SSL_V2);
+ }
+ if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) {
+ enabled.add(OpenSsl.PROTOCOL_SSL_V3);
+ }
+ return enabled.toArray(new String[enabled.size()]);
+ }
+
+ @Override
+ public final void setEnabledProtocols(String[] protocols) {
+ if (protocols == null) {
+ // This is correct from the API docs
+ throw new IllegalArgumentException();
+ }
+ boolean sslv2 = false;
+ boolean sslv3 = false;
+ boolean tlsv1 = false;
+ boolean tlsv1_1 = false;
+ boolean tlsv1_2 = false;
+ for (String p: protocols) {
+ if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) {
+ throw new IllegalArgumentException("Protocol " + p + " is not supported.");
+ }
+ if (p.equals(OpenSsl.PROTOCOL_SSL_V2)) {
+ sslv2 = true;
+ } else if (p.equals(OpenSsl.PROTOCOL_SSL_V3)) {
+ sslv3 = true;
+ } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1)) {
+ tlsv1 = true;
+ } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_1)) {
+ tlsv1_1 = true;
+ } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_2)) {
+ tlsv1_2 = true;
+ }
+ }
+ synchronized (this) {
+ if (!isDestroyed()) {
+ // Enable all and then disable what we not want
+ SSL.setOptions(ssl, SSL.SSL_OP_ALL);
+
+ // Clear out options which disable protocols
+ SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 |
+ SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2);
+
+ int opts = 0;
+ if (!sslv2) {
+ opts |= SSL.SSL_OP_NO_SSLv2;
+ }
+ if (!sslv3) {
+ opts |= SSL.SSL_OP_NO_SSLv3;
+ }
+ if (!tlsv1) {
+ opts |= SSL.SSL_OP_NO_TLSv1;
+ }
+ if (!tlsv1_1) {
+ opts |= SSL.SSL_OP_NO_TLSv1_1;
+ }
+ if (!tlsv1_2) {
+ opts |= SSL.SSL_OP_NO_TLSv1_2;
+ }
+
+ // Disable protocols we do not want
+ SSL.setOptions(ssl, opts);
+ } else {
+ throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols));
+ }
+ }
+ }
+
+ @Override
+ public final SSLSession getSession() {
+ return session;
+ }
+
+ @Override
+ public final synchronized void beginHandshake() throws SSLException {
+ switch (handshakeState) {
+ case STARTED_IMPLICITLY:
+ checkEngineClosed(BEGIN_HANDSHAKE_ENGINE_CLOSED);
+
+ // A user did not start handshake by calling this method by him/herself,
+ // but handshake has been started already by wrap() or unwrap() implicitly.
+ // Because it's the user's first time to call this method, it is unfair to
+ // raise an exception. From the user's standpoint, he or she never asked
+ // for renegotiation.
+
+ handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user,
+ // we should raise an exception.
+ break;
+ case STARTED_EXPLICITLY:
+ // Nothing to do as the handshake is not done yet.
+ break;
+ case FINISHED:
+ if (clientMode) {
+ // Only supported for server mode at the moment.
+ throw RENEGOTIATION_UNSUPPORTED;
+ }
+ // For renegotiate on the server side we need to issue the following command sequence with openssl:
+ //
+ // SSL_renegotiate(ssl)
+ // SSL_do_handshake(ssl)
+ // ssl->state = SSL_ST_ACCEPT
+ // SSL_do_handshake(ssl)
+ //
+ // Bcause of this we fall-through to call handshake() after setting the state, as this will also take
+ // care of updating the internal OpenSslSession object.
+ //
+ // See also:
+ // https://github.com/apache/httpd/blob/2.4.16/modules/ssl/ssl_engine_kernel.c#L812
+ // http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04s03.html
+ if (SSL.renegotiate(ssl) != 1 || SSL.doHandshake(ssl) != 1) {
+ throw shutdownWithError("renegotiation failed");
+ }
+
+ SSL.setState(ssl, SSL.SSL_ST_ACCEPT);
+
+ lastAccessed = System.currentTimeMillis();
+
+ // fall-through
+ case NOT_STARTED:
+ handshakeState = HandshakeState.STARTED_EXPLICITLY;
+ handshake();
+ break;
+ default:
+ throw new Error();
+ }
+ }
+
+ private void checkEngineClosed(SSLException cause) throws SSLException {
+ if (engineClosed || isDestroyed()) {
+ throw cause;
+ }
+ }
+
+ private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingStatus) {
+ // Depending on if there is something left in the BIO we need to WRAP or UNWRAP
+ return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP;
+ }
+
+ private SSLEngineResult.HandshakeStatus handshake() throws SSLException {
+ if (handshakeState == HandshakeState.FINISHED) {
+ return FINISHED;
+ }
+ checkEngineClosed(HANDSHAKE_ENGINE_CLOSED);
+
+ // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the
+ // BIO first or can just shutdown and throw it now.
+ // This is needed so we ensure close_notify etc is correctly send to the remote peer.
+ // See https://github.com/netty/netty/issues/3900
+ SSLHandshakeException exception = handshakeException;
+ if (exception != null) {
+ if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) {
+ // There is something pending, we need to consume it first via a WRAP so we not loose anything.
+ return NEED_WRAP;
+ }
+ // No more data left to send to the remote peer, so null out the exception field, shutdown and throw
+ // the exception.
+ handshakeException = null;
+ shutdown();
+ throw exception;
+ }
+
+ // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier.
+ engineMap.add(this);
+ if (lastAccessed == -1) {
+ 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
+ // shutdown the connection.
+ if (handshakeException != null) {
+ exception = handshakeException;
+ handshakeException = null;
+ shutdown();
+ throw exception;
+ }
+
+ int sslError = SSL.getError(ssl, code);
+
+ switch (sslError) {
+ case SSL.SSL_ERROR_WANT_READ:
+ case SSL.SSL_ERROR_WANT_WRITE:
+ return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO));
+ default:
+ // Everything else is considered as error
+ throw shutdownWithError("SSL_do_handshake");
+ }
+ }
+ // 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;
+ }
+
+ private SSLEngineResult.Status getEngineStatus() {
+ return engineClosed? CLOSED : OK;
+ }
+
+ private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status)
+ throws SSLException {
+ if (status == NOT_HANDSHAKING && handshakeState != HandshakeState.FINISHED) {
+ // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call
+ // SSL_do_handshake() again
+ return handshake();
+ }
+ return status;
+ }
+
+ @Override
+ public final synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
+ // Check if we are in the initial handshake phase or shutdown phase
+ return needPendingStatus() ? pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO)) : NOT_HANDSHAKING;
+ }
+
+ private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) {
+ // Check if we are in the initial handshake phase or shutdown phase
+ return needPendingStatus() ? pendingStatus(pending) : NOT_HANDSHAKING;
+ }
+
+ private boolean needPendingStatus() {
+ return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()
+ && (handshakeState != HandshakeState.FINISHED || engineClosed);
+ }
+
+ /**
+ * Converts the specified OpenSSL cipher suite to the Java cipher suite.
+ */
+ private String toJavaCipherSuite(String openSslCipherSuite) {
+ if (openSslCipherSuite == null) {
+ return null;
+ }
+
+ String prefix = toJavaCipherSuitePrefix(SSL.getVersion(ssl));
+ return CipherSuiteConverter.toJava(openSslCipherSuite, prefix);
+ }
+
+ /**
+ * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string.
+ */
+ private static String toJavaCipherSuitePrefix(String protocolVersion) {
+ final char c;
+ if (protocolVersion == null || protocolVersion.length() == 0) {
+ c = 0;
+ } else {
+ c = protocolVersion.charAt(0);
+ }
+
+ switch (c) {
+ case 'T':
+ return "TLS";
+ case 'S':
+ return "SSL";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public final void setUseClientMode(boolean clientMode) {
+ if (clientMode != this.clientMode) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public final boolean getUseClientMode() {
+ return clientMode;
+ }
+
+ @Override
+ public final void setNeedClientAuth(boolean b) {
+ setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE);
+ }
+
+ @Override
+ public final boolean getNeedClientAuth() {
+ return clientAuth == ClientAuth.REQUIRE;
+ }
+
+ @Override
+ public final void setWantClientAuth(boolean b) {
+ setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE);
+ }
+
+ @Override
+ public final boolean getWantClientAuth() {
+ return clientAuth == ClientAuth.OPTIONAL;
+ }
+
+ private void setClientAuth(ClientAuth mode) {
+ if (clientMode) {
+ return;
+ }
+ synchronized (this) {
+ if (clientAuth == mode) {
+ // No need to issue any JNI calls if the mode is the same
+ return;
+ }
+ switch (mode) {
+ case NONE:
+ SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, OpenSslContext.VERIFY_DEPTH);
+ break;
+ case REQUIRE:
+ SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, OpenSslContext.VERIFY_DEPTH);
+ break;
+ case OPTIONAL:
+ SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, OpenSslContext.VERIFY_DEPTH);
+ break;
+ default:
+ throw new Error(mode.toString());
+ }
+ clientAuth = mode;
+ }
+ }
+
+ @Override
+ public final void setEnableSessionCreation(boolean b) {
+ if (b) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public final boolean getEnableSessionCreation() {
+ return false;
+ }
+
+ @Override
+ public final synchronized SSLParameters getSSLParameters() {
+ SSLParameters sslParameters = super.getSSLParameters();
+
+ int version = PlatformDependent.javaVersion();
+ if (version >= 7) {
+ sslParameters.setEndpointIdentificationAlgorithm(endPointIdentificationAlgorithm);
+ SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints);
+ if (version >= 8) {
+ if (SET_SERVER_NAMES_METHOD != null && sniHostNames != null) {
+ try {
+ SET_SERVER_NAMES_METHOD.invoke(sslParameters, sniHostNames);
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+ if (SET_USE_CIPHER_SUITES_ORDER_METHOD != null && !isDestroyed()) {
+ try {
+ SET_USE_CIPHER_SUITES_ORDER_METHOD.invoke(sslParameters,
+ (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0);
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+ }
+ }
+ return sslParameters;
+ }
+
+ @Override
+ public final synchronized void setSSLParameters(SSLParameters sslParameters) {
+ super.setSSLParameters(sslParameters);
+
+ int version = PlatformDependent.javaVersion();
+ if (version >= 7) {
+ endPointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm();
+ algorithmConstraints = sslParameters.getAlgorithmConstraints();
+ if (version >= 8) {
+ if (SNI_HOSTNAME_CLASS != null && clientMode && !isDestroyed()) {
+ assert GET_SERVER_NAMES_METHOD != null;
+ assert GET_ASCII_NAME_METHOD != null;
+ try {
+ List> servernames = (List>) GET_SERVER_NAMES_METHOD.invoke(sslParameters);
+ if (servernames != null) {
+ for (Object serverName : servernames) {
+ if (SNI_HOSTNAME_CLASS.isInstance(serverName)) {
+ SSL.setTlsExtHostName(ssl, (String) GET_ASCII_NAME_METHOD.invoke(serverName));
+ } else {
+ throw new IllegalArgumentException("Only " + SNI_HOSTNAME_CLASS.getName()
+ + " instances are supported, but found: " +
+ serverName);
+ }
+ }
+ }
+ sniHostNames = servernames;
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+ if (GET_USE_CIPHER_SUITES_ORDER_METHOD != null && !isDestroyed()) {
+ try {
+ if ((Boolean) GET_USE_CIPHER_SUITES_ORDER_METHOD.invoke(sslParameters)) {
+ SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ } else {
+ SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isDestroyed() {
+ return destroyed != 0;
+ }
+
+ private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor {
+ private final OpenSslSessionContext sessionContext;
+
+ // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any
+ // thread.
+ private X509Certificate[] x509PeerCerts;
+ private String protocol;
+ private String applicationProtocol;
+ private Certificate[] peerCerts;
+ private String cipher;
+ private byte[] id;
+ private long creationTime;
+
+ // lazy init for memory reasons
+ private Map values;
+
+ OpenSslSession(OpenSslSessionContext sessionContext) {
+ this.sessionContext = sessionContext;
+ }
+
+ @Override
+ public byte[] getId() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (id == null) {
+ return EmptyArrays.EMPTY_BYTES;
+ }
+ return id.clone();
+ }
+ }
+
+ @Override
+ public SSLSessionContext getSessionContext() {
+ return sessionContext;
+ }
+
+ @Override
+ public long getCreationTime() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (creationTime == 0 && !isDestroyed()) {
+ creationTime = SSL.getTime(ssl) * 1000L;
+ }
+ }
+ return creationTime;
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed;
+ // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet.
+ return lastAccessed == -1 ? getCreationTime() : lastAccessed;
+ }
+
+ @Override
+ public void invalidate() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (!isDestroyed()) {
+ SSL.setTimeout(ssl, 0);
+ }
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (!isDestroyed()) {
+ return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ if (value == null) {
+ throw new NullPointerException("value");
+ }
+ Map values = this.values;
+ if (values == null) {
+ // Use size of 2 to keep the memory overhead small
+ values = this.values = new HashMap(2);
+ }
+ Object old = values.put(name, value);
+ if (value instanceof SSLSessionBindingListener) {
+ ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
+ }
+ notifyUnbound(old, name);
+ }
+
+ @Override
+ public Object getValue(String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ if (values == null) {
+ return null;
+ }
+ return values.get(name);
+ }
+
+ @Override
+ public void removeValue(String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ Map values = this.values;
+ if (values == null) {
+ return;
+ }
+ Object old = values.remove(name);
+ notifyUnbound(old, name);
+ }
+
+ @Override
+ public String[] getValueNames() {
+ Map values = this.values;
+ if (values == null || values.isEmpty()) {
+ return EmptyArrays.EMPTY_STRINGS;
+ }
+ return values.keySet().toArray(new String[values.size()]);
+ }
+
+ private void notifyUnbound(Object value, String name) {
+ if (value instanceof SSLSessionBindingListener) {
+ ((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name));
+ }
+ }
+
+ /**
+ * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by
+ * the user.
+ */
+ void handshakeFinished() throws SSLException {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (!isDestroyed()) {
+ id = SSL.getSessionId(ssl);
+ cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
+ protocol = SSL.getVersion(ssl);
+
+ initPeerCerts();
+ selectApplicationProtocol();
+
+ handshakeState = HandshakeState.FINISHED;
+ } else {
+ throw new SSLException("Already closed");
+ }
+ }
+ }
+
+ /**
+ * Init peer certificates that can be obtained via {@link #getPeerCertificateChain()}
+ * and {@link #getPeerCertificates()}.
+ */
+ private void initPeerCerts() {
+ // Return the full chain from the JNI layer.
+ byte[][] chain = SSL.getPeerCertChain(ssl);
+ final byte[] clientCert;
+ if (!clientMode) {
+ // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer
+ // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our
+ // array later.
+ //
+ // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
+ clientCert = SSL.getPeerCertificate(ssl);
+ } else {
+ clientCert = null;
+ }
+
+ if (chain == null && clientCert == null) {
+ peerCerts = EMPTY_CERTIFICATES;
+ x509PeerCerts = EMPTY_X509_CERTIFICATES;
+ } else {
+ int len = chain != null ? chain.length : 0;
+
+ int i = 0;
+ Certificate[] peerCerts;
+ if (clientCert != null) {
+ len++;
+ peerCerts = new Certificate[len];
+ peerCerts[i++] = new OpenSslX509Certificate(clientCert);
+ } else {
+ peerCerts = new Certificate[len];
+ }
+ if (chain != null) {
+ X509Certificate[] pCerts = new X509Certificate[chain.length];
+
+ for (int a = 0; a < pCerts.length; ++i, ++a) {
+ byte[] bytes = chain[a];
+ pCerts[a] = new OpenSslJavaxX509Certificate(bytes);
+ peerCerts[i] = new OpenSslX509Certificate(bytes);
+ }
+ x509PeerCerts = pCerts;
+ } else {
+ x509PeerCerts = EMPTY_X509_CERTIFICATES;
+ }
+ this.peerCerts = peerCerts;
+ }
+ }
+
+ /**
+ * Select the application protocol used.
+ */
+ private void selectApplicationProtocol() throws SSLException {
+ ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior();
+ List protocols = apn.protocols();
+ String applicationProtocol;
+ switch (apn.protocol()) {
+ case NONE:
+ break;
+ // We always need to check for applicationProtocol == null as the remote peer may not support
+ // the TLS extension or may have returned an empty selection.
+ case ALPN:
+ applicationProtocol = SSL.getAlpnSelected(ssl);
+ if (applicationProtocol != null) {
+ this.applicationProtocol = selectApplicationProtocol(
+ protocols, behavior, applicationProtocol);
+ }
+ break;
+ case NPN:
+ applicationProtocol = SSL.getNextProtoNegotiated(ssl);
+ if (applicationProtocol != null) {
+ this.applicationProtocol = selectApplicationProtocol(
+ protocols, behavior, applicationProtocol);
+ }
+ break;
+ case NPN_AND_ALPN:
+ applicationProtocol = SSL.getAlpnSelected(ssl);
+ if (applicationProtocol == null) {
+ applicationProtocol = SSL.getNextProtoNegotiated(ssl);
+ }
+ if (applicationProtocol != null) {
+ this.applicationProtocol = selectApplicationProtocol(
+ protocols, behavior, applicationProtocol);
+ }
+ break;
+ default:
+ throw new Error();
+ }
+ }
+
+ private String selectApplicationProtocol(List protocols,
+ ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior,
+ String applicationProtocol) throws SSLException {
+ if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT) {
+ return applicationProtocol;
+ } else {
+ int size = protocols.size();
+ assert size > 0;
+ if (protocols.contains(applicationProtocol)) {
+ return applicationProtocol;
+ } else {
+ if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) {
+ return protocols.get(size - 1);
+ } else {
+ throw new SSLException("unknown protocol " + applicationProtocol);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (peerCerts == null || peerCerts.length == 0) {
+ throw new SSLPeerUnverifiedException("peer not verified");
+ }
+ return peerCerts;
+ }
+ }
+
+ @Override
+ public Certificate[] getLocalCertificates() {
+ if (localCerts == null) {
+ return null;
+ }
+ return localCerts.clone();
+ }
+
+ @Override
+ public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (x509PeerCerts == null || x509PeerCerts.length == 0) {
+ throw new SSLPeerUnverifiedException("peer not verified");
+ }
+ return x509PeerCerts;
+ }
+ }
+
+ @Override
+ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ Certificate[] peer = getPeerCertificates();
+ // No need for null or length > 0 is needed as this is done in getPeerCertificates()
+ // already.
+ return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
+ }
+
+ @Override
+ public Principal getLocalPrincipal() {
+ Certificate[] local = localCerts;
+ if (local == null || local.length == 0) {
+ return null;
+ }
+ return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
+ }
+
+ @Override
+ public String getCipherSuite() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (cipher == null) {
+ return INVALID_CIPHER;
+ }
+ return cipher;
+ }
+ }
+
+ @Override
+ public String getProtocol() {
+ String protocol = this.protocol;
+ if (protocol == null) {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ if (!isDestroyed()) {
+ protocol = SSL.getVersion(ssl);
+ } else {
+ protocol = StringUtil.EMPTY_STRING;
+ }
+ }
+ }
+ return protocol;
+ }
+
+ @Override
+ public String getApplicationProtocol() {
+ synchronized (ReferenceCountedOpenSslEngine.this) {
+ return applicationProtocol;
+ }
+ }
+
+ @Override
+ public String getPeerHost() {
+ return ReferenceCountedOpenSslEngine.this.getPeerHost();
+ }
+
+ @Override
+ public int getPeerPort() {
+ return ReferenceCountedOpenSslEngine.this.getPeerPort();
+ }
+
+ @Override
+ public int getPacketBufferSize() {
+ return MAX_ENCRYPTED_PACKET_LENGTH;
+ }
+
+ @Override
+ public int getApplicationBufferSize() {
+ return MAX_PLAINTEXT_LENGTH;
+ }
+ }
+}
+
diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java
new file mode 100644
index 0000000000..c0740298e6
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java
@@ -0,0 +1,191 @@
+/*
+ * 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 org.apache.tomcat.jni.SSLContext;
+
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+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 static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+/**
+ * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
+ * Instances of this class must be {@link #release() released} or else native memory will leak!
+ */
+public final class ReferenceCountedOpenSslServerContext extends ReferenceCountedOpenSslContext {
+ private static final byte[] ID = new byte[] {'n', 'e', 't', 't', 'y'};
+ private final OpenSslServerSessionContext sessionContext;
+ private final OpenSslKeyMaterialManager keyMaterialManager;
+
+ ReferenceCountedOpenSslServerContext(
+ X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
+ X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+ long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth) throws SSLException {
+ this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers,
+ cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth);
+ }
+
+ private ReferenceCountedOpenSslServerContext(
+ X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
+ X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
+ Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
+ long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth) throws SSLException {
+ super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
+ clientAuth, true);
+ // Create a new SSL_CTX and configure it.
+ boolean success = false;
+ try {
+ ServerContext context = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
+ keyCertChain, key, keyPassword, keyManagerFactory);
+ sessionContext = context.sessionContext;
+ keyMaterialManager = context.keyMaterialManager;
+ success = true;
+ } finally {
+ if (!success) {
+ release();
+ }
+ }
+ }
+
+ @Override
+ public OpenSslServerSessionContext sessionContext() {
+ return sessionContext;
+ }
+
+ @Override
+ OpenSslKeyMaterialManager keyMaterialManager() {
+ return keyMaterialManager;
+ }
+
+ static final class ServerContext {
+ OpenSslServerSessionContext sessionContext;
+ OpenSslKeyMaterialManager keyMaterialManager;
+ }
+
+ static ServerContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx, OpenSslEngineMap engineMap,
+ X509Certificate[] trustCertCollection,
+ TrustManagerFactory trustManagerFactory,
+ X509Certificate[] keyCertChain, PrivateKey key,
+ String keyPassword, KeyManagerFactory keyManagerFactory)
+ throws SSLException {
+ ServerContext result = new ServerContext();
+ synchronized (ReferenceCountedOpenSslContext.class) {
+ try {
+ SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
+ if (!OpenSsl.useKeyManagerFactory()) {
+ if (keyManagerFactory != null) {
+ throw new IllegalArgumentException(
+ "KeyManagerFactory not supported");
+ }
+ checkNotNull(keyCertChain, "keyCertChain");
+
+ /* Set certificate verification policy. */
+ SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
+
+ setKeyMaterial(ctx, keyCertChain, key, keyPassword);
+ } else {
+ // javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a
+ // keyManagerFactory for the server so build one if it is not specified.
+ if (keyManagerFactory == null) {
+ keyManagerFactory = buildKeyManagerFactory(
+ keyCertChain, key, keyPassword, keyManagerFactory);
+ }
+ X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
+ result.keyMaterialManager = useExtendedKeyManager(keyManager) ?
+ new OpenSslExtendedKeyMaterialManager(
+ (X509ExtendedKeyManager) keyManager, keyPassword) :
+ new OpenSslKeyMaterialManager(keyManager, keyPassword);
+ }
+ } catch (Exception e) {
+ throw new SSLException("failed to set certificate and key", e);
+ }
+ try {
+ if (trustCertCollection != null) {
+ trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
+ } else if (trustManagerFactory == null) {
+ // Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
+ 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);
+ }
+ }
+
+ result.sessionContext = new OpenSslServerSessionContext(thiz);
+ result.sessionContext.setSessionIdContext(ID);
+ return result;
+ }
+
+ private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
+ private final X509TrustManager manager;
+
+ TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) {
+ super(engineMap);
+ this.manager = manager;
+ }
+
+ @Override
+ void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth)
+ throws Exception {
+ manager.checkClientTrusted(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(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth)
+ throws Exception {
+ manager.checkClientTrusted(peerCerts, auth, engine);
+ }
+ }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
index fd7967d192..9e3986da9a 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
@@ -396,8 +396,7 @@ public abstract class SslContext {
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
- long sessionCacheSize, long sessionTimeout,
- ClientAuth clientAuth) throws SSLException {
+ long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth) throws SSLException {
if (provider == null) {
provider = defaultServerProvider();
@@ -414,6 +413,11 @@ public abstract class SslContext {
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
clientAuth);
+ case OPENSSL_REFCNT:
+ return new ReferenceCountedOpenSslServerContext(
+ trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
+ keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
+ clientAuth);
default:
throw new Error(provider.toString());
}
@@ -749,9 +753,13 @@ public abstract class SslContext {
return new OpenSslClientContext(
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+ case OPENSSL_REFCNT:
+ return new ReferenceCountedOpenSslClientContext(
+ trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
+ keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+ default:
+ throw new Error(provider.toString());
}
- // Should never happen!!
- throw new Error();
}
static ApplicationProtocolConfig toApplicationProtocolConfig(Iterable nextProtocols) {
@@ -813,14 +821,17 @@ public abstract class SslContext {
/**
* Creates a new {@link SSLEngine}.
- *
+ * If {@link SslProvider#OPENSSL_REFCNT} is used then the object must be released. One way to do this is to
+ * wrap in a {@link SslHandler} and insert it into a pipeline. See {@link #newHandler(ByteBufAllocator)}.
* @return a new {@link SSLEngine}
*/
public abstract SSLEngine newEngine(ByteBufAllocator alloc);
/**
* Creates a new {@link SSLEngine} using advisory peer information.
- *
+ *
If {@link SslProvider#OPENSSL_REFCNT} is used then the object must be released. One way to do this is to
+ * wrap in a {@link SslHandler} and insert it into a pipeline.
+ * See {@link #newHandler(ByteBufAllocator, String, int)}.
* @param peerHost the non-authoritative name of the host
* @param peerPort the non-authoritative port
*
@@ -835,7 +846,9 @@ public abstract class SslContext {
/**
* Creates a new {@link SslHandler}.
- *
+ *
If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine
+ * that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native
+ * memory!
* @return a new {@link SslHandler}
*/
public final SslHandler newHandler(ByteBufAllocator alloc) {
@@ -844,7 +857,9 @@ public abstract class SslContext {
/**
* Creates a new {@link SslHandler} with advisory peer information.
- *
+ *
If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine
+ * that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native
+ * memory!
* @param peerHost the non-authoritative name of the host
* @param peerPort the non-authoritative port
*
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java b/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java
index 969bea199c..9f70b07682 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java
@@ -385,6 +385,8 @@ public final class SslContextBuilder {
/**
* Create new {@code SslContext} instance with configured settings.
+ *
If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
+ * responsible for releasing this object, or else native memory may leak.
*/
public SslContext build() throws SSLException {
if (forServer) {
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
index 5eb4cd8bd3..7e815673f6 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
@@ -421,9 +421,8 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
// Check if queue is not empty first because create a new ChannelException is expensive
pendingUnencryptedWrites.removeAndFailAll(new ChannelException("Pending write on removal of SslHandler"));
}
- if (engine instanceof OpenSslEngine) {
- // Call shutdown so we ensure all the native memory is released asap
- ((OpenSslEngine) engine).shutdown();
+ if (engine instanceof ReferenceCountedOpenSslEngine) {
+ ((ReferenceCountedOpenSslEngine) engine).release();
}
}
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslProvider.java b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java
index 3d4f08bfa9..00fc2aac93 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslProvider.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java
@@ -16,6 +16,9 @@
package io.netty.handler.ssl;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.internal.UnstableApi;
+
/**
* An enumeration of SSL/TLS protocol providers.
*/
@@ -27,5 +30,10 @@ public enum SslProvider {
/**
* OpenSSL-based implementation.
*/
- OPENSSL
+ OPENSSL,
+ /**
+ * OpenSSL-based implementation which does not have finalizers and instead implements {@link ReferenceCounted}.
+ */
+ @UnstableApi
+ OPENSSL_REFCNT
}
diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java
index d0620c67f3..4dab24bca3 100644
--- a/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java
@@ -21,6 +21,7 @@ import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBeh
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
+import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.ThreadLocalRandom;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -79,26 +80,33 @@ public class OpenSslEngineTest extends SSLEngineTest {
}
@Test
public void testWrapHeapBuffersNoWritePendingError() throws Exception {
- final SslContext clientContext = SslContextBuilder.forClient()
+ clientSslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslProvider())
.build();
SelfSignedCertificate ssc = new SelfSignedCertificate();
- SslContext serverContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
+ serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslProvider())
.build();
- SSLEngine clientEngine = clientContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
- SSLEngine serverEngine = serverContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
- handshake(clientEngine, serverEngine);
+ SSLEngine clientEngine = null;
+ SSLEngine serverEngine = null;
+ try {
+ clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ handshake(clientEngine, serverEngine);
- ByteBuffer src = ByteBuffer.allocate(1024 * 10);
- ThreadLocalRandom.current().nextBytes(src.array());
- ByteBuffer dst = ByteBuffer.allocate(1);
- // Try to wrap multiple times so we are more likely to hit the issue.
- for (int i = 0; i < 100; i++) {
- src.position(0);
- dst.position(0);
- assertSame(SSLEngineResult.Status.BUFFER_OVERFLOW, clientEngine.wrap(src, dst).getStatus());
+ ByteBuffer src = ByteBuffer.allocate(1024 * 10);
+ ThreadLocalRandom.current().nextBytes(src.array());
+ ByteBuffer dst = ByteBuffer.allocate(1);
+ // Try to wrap multiple times so we are more likely to hit the issue.
+ for (int i = 0; i < 100; i++) {
+ src.position(0);
+ dst.position(0);
+ assertSame(SSLEngineResult.Status.BUFFER_OVERFLOW, clientEngine.wrap(src, dst).getStatus());
+ }
+ } finally {
+ cleanupSslEngine(clientEngine);
+ cleanupSslEngine(serverEngine);
}
}
diff --git a/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java
new file mode 100644
index 0000000000..6d2bae7a97
--- /dev/null
+++ b/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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 io.netty.util.ReferenceCountUtil;
+
+import javax.net.ssl.SSLEngine;
+
+public class ReferenceCountedOpenSslEngineTest extends OpenSslEngineTest {
+ @Override
+ protected SslProvider sslProvider() {
+ return SslProvider.OPENSSL_REFCNT;
+ }
+
+ @Override
+ protected void cleanupSslContext(SslContext ctx) {
+ ReferenceCountUtil.release(ctx);
+ }
+
+ @Override
+ protected void cleanupSslEngine(SSLEngine engine) {
+ ReferenceCountUtil.release(engine);
+ }
+}
diff --git a/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java
index 2b1c9067d8..6a7bc0c497 100644
--- a/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java
@@ -130,6 +130,14 @@ public abstract class SSLEngineTest {
serverChannel.close().sync();
serverChannel = null;
}
+ if (serverSslCtx != null) {
+ cleanupSslContext(serverSslCtx);
+ serverSslCtx = null;
+ }
+ if (clientSslCtx != null) {
+ cleanupSslContext(clientSslCtx);
+ clientSslCtx = null;
+ }
Future> serverGroupShutdownFuture = null;
Future> serverChildGroupShutdownFuture = null;
Future> clientGroupShutdownFuture = null;
@@ -333,54 +341,73 @@ public abstract class SSLEngineTest {
@Test
public void testGetCreationTime() throws Exception {
- SslContext context = SslContextBuilder.forClient().sslProvider(sslProvider()).build();
- SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT);
- assertTrue(engine.getSession().getCreationTime() <= System.currentTimeMillis());
+ clientSslCtx = SslContextBuilder.forClient().sslProvider(sslProvider()).build();
+ SSLEngine engine = null;
+ try {
+ engine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ assertTrue(engine.getSession().getCreationTime() <= System.currentTimeMillis());
+ } finally {
+ cleanupSslEngine(engine);
+ }
}
@Test
public void testSessionInvalidate() throws Exception {
- final SslContext clientContext = SslContextBuilder.forClient()
+ clientSslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslProvider())
.build();
SelfSignedCertificate ssc = new SelfSignedCertificate();
- SslContext serverContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
+ serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslProvider())
.build();
- SSLEngine clientEngine = clientContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
- SSLEngine serverEngine = serverContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
- handshake(clientEngine, serverEngine);
+ SSLEngine clientEngine = null;
+ SSLEngine serverEngine = null;
+ try {
+ clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ handshake(clientEngine, serverEngine);
- SSLSession session = serverEngine.getSession();
- assertTrue(session.isValid());
- session.invalidate();
- assertFalse(session.isValid());
+ SSLSession session = serverEngine.getSession();
+ assertTrue(session.isValid());
+ session.invalidate();
+ assertFalse(session.isValid());
+ } finally {
+ cleanupSslEngine(clientEngine);
+ cleanupSslEngine(serverEngine);
+ }
}
@Test
public void testSSLSessionId() throws Exception {
- final SslContext clientContext = SslContextBuilder.forClient()
+ clientSslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslProvider())
.build();
SelfSignedCertificate ssc = new SelfSignedCertificate();
- SslContext serverContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
+ serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslProvider())
.build();
- SSLEngine clientEngine = clientContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
- SSLEngine serverEngine = serverContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ SSLEngine clientEngine = null;
+ SSLEngine serverEngine = null;
+ try {
+ clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
- // Before the handshake the id should have length == 0
- assertEquals(0, clientEngine.getSession().getId().length);
- assertEquals(0, serverEngine.getSession().getId().length);
+ // Before the handshake the id should have length == 0
+ assertEquals(0, clientEngine.getSession().getId().length);
+ assertEquals(0, serverEngine.getSession().getId().length);
- handshake(clientEngine, serverEngine);
+ handshake(clientEngine, serverEngine);
- // After the handshake the id should have length > 0
- assertNotEquals(0, clientEngine.getSession().getId().length);
- assertNotEquals(0, serverEngine.getSession().getId().length);
- assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId());
+ // After the handshake the id should have length > 0
+ assertNotEquals(0, clientEngine.getSession().getId().length);
+ assertNotEquals(0, serverEngine.getSession().getId().length);
+ assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId());
+ } finally {
+ cleanupSslEngine(clientEngine);
+ cleanupSslEngine(serverEngine);
+ }
}
@Test(timeout = 3000)
@@ -493,11 +520,11 @@ public abstract class SSLEngineTest {
try {
File serverKeyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());
File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
- SslContext sslContext = SslContextBuilder.forServer(serverCrtFile, serverKeyFile)
+ serverSslCtx = SslContextBuilder.forServer(serverCrtFile, serverKeyFile)
.sslProvider(sslProvider())
.build();
- sslEngine = sslContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
+ sslEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
// Disable all protocols
sslEngine.setEnabledProtocols(new String[]{});
@@ -518,6 +545,7 @@ public abstract class SSLEngineTest {
if (sslEngine != null) {
sslEngine.closeInbound();
sslEngine.closeOutbound();
+ cleanupSslEngine(sslEngine);
}
}
}
@@ -575,6 +603,18 @@ public abstract class SSLEngineTest {
protected abstract SslProvider sslProvider();
+ /**
+ * Called from the test cleanup code and can be used to release the {@code ctx} if it must be done manually.
+ */
+ protected void cleanupSslContext(SslContext ctx) {
+ }
+
+ /**
+ * Called when ever an SSLEngine is not wrapped by a {@link SslHandler} and inserted into a pipeline.
+ */
+ protected void cleanupSslEngine(SSLEngine engine) {
+ }
+
protected void setupHandlers(ApplicationProtocolConfig apn) throws InterruptedException, SSLException,
CertificateException {
setupHandlers(apn, apn);