From e597756a56398a73f851f97ac667f3ae198f4545 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 14 Jun 2017 20:18:50 +0200 Subject: [PATCH] Remove synchronized (ReferenceCountedOpenSslContext.class) blocks Motivation: We had some useless synchronized (ReferenceCountedOpenSslContext.class) blocks in our code which could slow down concurrent collecting and creating of ReferenceCountedOpenSslContext instances. Beside this we missed a few guards. Modifications: Use ReadWriteLock to correctly guard. A ReadWriteLock was choosen as SSL.newSSL(...) will be called from multiple threads all the time so using synchronized would be worse and there would be no way for the JIT to optimize it away Result: Faster concurrent creating and collecting of ReferenceCountedOpenSslContext instances and correctly guard in all cases. --- .../ssl/OpenSslServerSessionContext.java | 59 +++++- .../handler/ssl/OpenSslSessionContext.java | 21 +- .../handler/ssl/OpenSslSessionStats.java | 130 ++++++++++-- .../ReferenceCountedOpenSslClientContext.java | 106 +++++----- .../ssl/ReferenceCountedOpenSslContext.java | 186 ++++++++++-------- .../ssl/ReferenceCountedOpenSslEngine.java | 11 +- .../ReferenceCountedOpenSslServerContext.java | 120 ++++++----- 7 files changed, 408 insertions(+), 225 deletions(-) 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 a5c5efe39c..8c92debfec 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java @@ -18,6 +18,8 @@ package io.netty.handler.ssl; import io.netty.internal.tcnative.SSL; import io.netty.internal.tcnative.SSLContext; +import java.util.concurrent.locks.Lock; + /** * {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side. @@ -32,12 +34,24 @@ public final class OpenSslServerSessionContext extends OpenSslSessionContext { if (seconds < 0) { throw new IllegalArgumentException(); } - SSLContext.setSessionCacheTimeout(context.ctx, seconds); + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + SSLContext.setSessionCacheTimeout(context.ctx, seconds); + } finally { + writerLock.unlock(); + } } @Override public int getSessionTimeout() { - return (int) SSLContext.getSessionCacheTimeout(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return (int) SSLContext.getSessionCacheTimeout(context.ctx); + } finally { + readerLock.unlock(); + } } @Override @@ -45,23 +59,48 @@ public final class OpenSslServerSessionContext extends OpenSslSessionContext { if (size < 0) { throw new IllegalArgumentException(); } - SSLContext.setSessionCacheSize(context.ctx, size); + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + SSLContext.setSessionCacheSize(context.ctx, size); + } finally { + writerLock.unlock(); + } } @Override public int getSessionCacheSize() { - return (int) SSLContext.getSessionCacheSize(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return (int) SSLContext.getSessionCacheSize(context.ctx); + } finally { + readerLock.unlock(); + } } @Override public void setSessionCacheEnabled(boolean enabled) { long mode = enabled ? SSL.SSL_SESS_CACHE_SERVER : SSL.SSL_SESS_CACHE_OFF; - SSLContext.setSessionCacheMode(context.ctx, mode); + + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + SSLContext.setSessionCacheMode(context.ctx, mode); + } finally { + writerLock.unlock(); + } } @Override public boolean isSessionCacheEnabled() { - return SSLContext.getSessionCacheMode(context.ctx) == SSL.SSL_SESS_CACHE_SERVER; + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.getSessionCacheMode(context.ctx) == SSL.SSL_SESS_CACHE_SERVER; + } finally { + readerLock.unlock(); + } } /** @@ -74,6 +113,12 @@ public final class OpenSslServerSessionContext extends OpenSslSessionContext { * @return {@code true} if success, {@code false} otherwise. */ public boolean setSessionIdContext(byte[] sidCtx) { - return SSLContext.setSessionIdContext(context.ctx, sidCtx); + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + return SSLContext.setSessionIdContext(context.ctx, sidCtx); + } finally { + writerLock.unlock(); + } } } 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 4de787c3a6..846a968735 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java @@ -25,6 +25,7 @@ import javax.net.ssl.SSLSessionContext; import java.util.Arrays; import java.util.Enumeration; import java.util.NoSuchElementException; +import java.util.concurrent.locks.Lock; /** * OpenSSL specific {@link SSLSessionContext} implementation. @@ -76,8 +77,14 @@ public abstract class OpenSslSessionContext implements SSLSessionContext { a += SessionTicketKey.AES_KEY_SIZE; tickets[i] = new SessionTicketKey(name, hmacKey, aesKey); } - SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); - SSLContext.setSessionTicketKeys(context.ctx, tickets); + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); + SSLContext.setSessionTicketKeys(context.ctx, tickets); + } finally { + writerLock.unlock(); + } } /** @@ -85,12 +92,18 @@ public abstract class OpenSslSessionContext implements SSLSessionContext { */ public void setTicketKeys(OpenSslSessionTicketKey... keys) { ObjectUtil.checkNotNull(keys, "keys"); - SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); SessionTicketKey[] ticketKeys = new SessionTicketKey[keys.length]; for (int i = 0; i < ticketKeys.length; i++) { ticketKeys[i] = keys[i].key; } - SSLContext.setSessionTicketKeys(context.ctx, ticketKeys); + Lock writerLock = context.ctxLock.writeLock(); + writerLock.lock(); + try { + SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); + SSLContext.setSessionTicketKeys(context.ctx, ticketKeys); + } finally { + writerLock.unlock(); + } } /** 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 0c88332a64..f49b95f3ba 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java @@ -18,6 +18,8 @@ package io.netty.handler.ssl; import io.netty.internal.tcnative.SSLContext; +import java.util.concurrent.locks.Lock; + /** * Stats exposed by an OpenSSL session context. * @@ -39,49 +41,91 @@ public final class OpenSslSessionStats { * Returns the current number of sessions in the internal session cache. */ public long number() { - return SSLContext.sessionNumber(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionNumber(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of started SSL/TLS handshakes in client mode. */ public long connect() { - return SSLContext.sessionConnect(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionConnect(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of successfully established SSL/TLS sessions in client mode. */ public long connectGood() { - return SSLContext.sessionConnectGood(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionConnectGood(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of start renegotiations in client mode. */ public long connectRenegotiate() { - return SSLContext.sessionConnectRenegotiate(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionConnectRenegotiate(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of started SSL/TLS handshakes in server mode. */ public long accept() { - return SSLContext.sessionAccept(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionAccept(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of successfully established SSL/TLS sessions in server mode. */ public long acceptGood() { - return SSLContext.sessionAcceptGood(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionAcceptGood(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of start renegotiations in server mode. */ public long acceptRenegotiate() { - return SSLContext.sessionAcceptRenegotiate(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionAcceptRenegotiate(context.ctx); + } finally { + readerLock.unlock(); + } } /** @@ -90,14 +134,26 @@ public final class OpenSslSessionStats { * external cache is counted as a hit. */ public long hits() { - return SSLContext.sessionHits(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionHits(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of successfully retrieved sessions from the external session cache in server mode. */ public long cbHits() { - return SSLContext.sessionCbHits(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionCbHits(context.ctx); + } finally { + readerLock.unlock(); + } } /** @@ -105,7 +161,13 @@ public final class OpenSslSessionStats { * in server mode. */ public long misses() { - return SSLContext.sessionMisses(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionMisses(context.ctx); + } finally { + readerLock.unlock(); + } } /** @@ -114,28 +176,52 @@ public final class OpenSslSessionStats { * count. */ public long timeouts() { - return SSLContext.sessionTimeouts(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionTimeouts(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of sessions that were removed because the maximum session cache size was exceeded. */ public long cacheFull() { - return SSLContext.sessionCacheFull(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionCacheFull(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of times a client presented a ticket that did not match any key in the list. */ public long ticketKeyFail() { - return SSLContext.sessionTicketKeyFail(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionTicketKeyFail(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of times a client did not present a ticket and we issued a new one */ public long ticketKeyNew() { - return SSLContext.sessionTicketKeyNew(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionTicketKeyNew(context.ctx); + } finally { + readerLock.unlock(); + } } /** @@ -143,13 +229,25 @@ public final class OpenSslSessionStats { * and we upgraded to the primary key. */ public long ticketKeyRenew() { - return SSLContext.sessionTicketKeyRenew(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionTicketKeyRenew(context.ctx); + } finally { + readerLock.unlock(); + } } /** * Returns the number of times a client presented a ticket derived from the primary key. */ public long ticketKeyResume() { - return SSLContext.sessionTicketKeyResume(context.ctx); + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + return SSLContext.sessionTicketKeyResume(context.ctx); + } finally { + readerLock.unlock(); + } } } diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java index f41c38b65a..b2135734e9 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java @@ -90,65 +90,63 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted 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)); - } + 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); } + } catch (Exception e) { + throw new SSLException("failed to set certificate and key", e); + } - SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); + SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_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); + 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); } diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java index d537986937..ee049ab19d 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java @@ -46,6 +46,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; @@ -106,11 +109,11 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen protected static final int VERIFY_DEPTH = 10; /** - * The OpenSSL SSL_CTX object + * The OpenSSL SSL_CTX object. + * + * {@link #ctxLock} must be hold while using ctx! */ - protected volatile long ctx; - @SuppressWarnings({ "unused", "FieldMayBeFinal" }) - private volatile int aprPoolDestroyed; + protected long ctx; private final List unmodifiableCiphers; private final long sessionCacheSize; private final long sessionTimeout; @@ -144,6 +147,8 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen final String[] protocols; final boolean enableOcsp; final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap(); + final ReadWriteLock ctxLock = new ReentrantReadWriteLock(); + private volatile boolean rejectRemoteInitiatedRenegotiation; private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE; @@ -273,92 +278,90 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen // Create a new SSL_CTX and configure it. boolean success = false; try { - synchronized (ReferenceCountedOpenSslContext.class) { - try { - ctx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, mode); - } catch (Exception e) { - throw new SSLException("failed to create an SSL_CTX", e); - } + try { + ctx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, mode); + } catch (Exception e) { + throw new SSLException("failed to create an SSL_CTX", e); + } - SSLContext.setOptions(ctx, SSLContext.getOptions(ctx) | - SSL.SSL_OP_NO_SSLv2 | - SSL.SSL_OP_NO_SSLv3 | - SSL.SSL_OP_CIPHER_SERVER_PREFERENCE | + SSLContext.setOptions(ctx, SSLContext.getOptions(ctx) | + SSL.SSL_OP_NO_SSLv2 | + SSL.SSL_OP_NO_SSLv3 | + SSL.SSL_OP_CIPHER_SERVER_PREFERENCE | - // We do not support compression at the moment so we should explicitly disable it. - SSL.SSL_OP_NO_COMPRESSION | + // We do not support compression at the moment so we should explicitly disable it. + SSL.SSL_OP_NO_COMPRESSION | - // 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. - SSL.SSL_OP_NO_TICKET); + // 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. + 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); + // 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); - } + 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); - } + 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(); + List nextProtoList = apn.protocols(); /* Set next protocols for next protocol negotiation extension, if specified */ - if (!nextProtoList.isEmpty()) { - String[] appProtocols = nextProtoList.toArray(new String[nextProtoList.size()]); - int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); + if (!nextProtoList.isEmpty()) { + String[] appProtocols = nextProtoList.toArray(new String[nextProtoList.size()]); + int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); - switch (apn.protocol()) { - case NPN: - SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); - break; - case ALPN: - SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); - break; - case NPN_AND_ALPN: - SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); - SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); - break; - default: - throw new Error(); - } + switch (apn.protocol()) { + case NPN: + SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); + break; + case ALPN: + SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); + break; + case NPN_AND_ALPN: + SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); + SSLContext.setAlpnProtos(ctx, appProtocols, 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 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); - } + /* 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); + } - if (enableOcsp) { - SSLContext.enableOcsp(ctx, isClient()); - } + if (enableOcsp) { + SSLContext.enableOcsp(ctx, isClient()); } success = true; } finally { @@ -428,11 +431,17 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen * 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 this method is considered unsafe as the returned pointer may be released later. Dont use it! */ @Deprecated public final long context() { - return ctx; + Lock readerLock = ctxLock.readLock(); + readerLock.lock(); + try { + return ctx; + } finally { + readerLock.unlock(); + } } /** @@ -491,18 +500,29 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen /** * 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. + * Be aware that it is freed as soon as the {@link #release()} method is called. * At this point {@code 0} will be returned. + * + * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! */ + @Deprecated public final long sslCtxPointer() { - return ctx; + Lock readerLock = ctxLock.readLock(); + readerLock.lock(); + try { + return ctx; + } finally { + readerLock.unlock(); + } } // 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) { + private void destroy() { + Lock writerLock = ctxLock.writeLock(); + writerLock.lock(); + try { if (ctx != 0) { if (enableOcsp) { SSLContext.disableOcsp(ctx); @@ -511,6 +531,8 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen SSLContext.free(ctx); ctx = 0; } + } finally { + writerLock.unlock(); } } diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java index 8f0ec83534..27460c7149 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java @@ -43,6 +43,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import java.util.concurrent.locks.Lock; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; @@ -234,7 +236,14 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc localCerts = context.keyCertChain; keyMaterialManager = context.keyMaterialManager(); enableOcsp = context.enableOcsp; - ssl = SSL.newSSL(context.ctx, !context.isClient()); + + Lock readerLock = context.ctxLock.readLock(); + readerLock.lock(); + try { + ssl = SSL.newSSL(context.ctx, !context.isClient()); + } finally { + readerLock.unlock(); + } try { networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java index aed85d25ca..4c9df3148c 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java @@ -106,75 +106,69 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted 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"); - - 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); + try { + SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); + if (!OpenSsl.useKeyManagerFactory()) { + if (keyManagerFactory != null) { + throw new IllegalArgumentException( + "KeyManagerFactory not supported"); } - } catch (Exception e) { - throw new SSLException("failed to set certificate and key", e); + checkNotNull(keyCertChain, "keyCertChain"); + + 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); } - 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()); + 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 + // 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)); - } + // 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)); + } - X509Certificate[] issuers = manager.getAcceptedIssuers(); - if (issuers != null && issuers.length > 0) { - long bio = 0; - try { - bio = toBIO(issuers); - if (!SSLContext.setCACertificateBio(ctx, bio)) { - throw new SSLException("unable to setup accepted issuers for trustmanager " + manager); - } - } finally { - freeBio(bio); + X509Certificate[] issuers = manager.getAcceptedIssuers(); + if (issuers != null && issuers.length > 0) { + long bio = 0; + try { + bio = toBIO(issuers); + if (!SSLContext.setCACertificateBio(ctx, bio)) { + throw new SSLException("unable to setup accepted issuers for trustmanager " + manager); } + } finally { + freeBio(bio); } - } catch (SSLException e) { - throw e; - } catch (Exception e) { - throw new SSLException("unable to setup trustmanager", e); } if (PlatformDependent.javaVersion() >= 8) { @@ -184,6 +178,10 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted // a global reference to the matcher. SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap)); } + } catch (SSLException e) { + throw e; + } catch (Exception e) { + throw new SSLException("unable to setup trustmanager", e); } result.sessionContext = new OpenSslServerSessionContext(thiz);