Support session cache for client and server when using native SSLEngine implementation (#10994)
Motivation: At the moment we don't support session caching on the client side at all when using the native SSL implementation. We should at least allow to enable it. Modification: Allow to enable session cache for client side but disable ti by default due a JDK bug atm. Result: Be able to cache sessions on the client side when using native SSL implementation .
This commit is contained in:
parent
d209eb0e18
commit
8a9320161c
@ -15,10 +15,13 @@
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
|
||||
import javax.net.ssl.ExtendedSSLSession;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.SSLSessionBindingEvent;
|
||||
import javax.net.ssl.SSLSessionBindingListener;
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
@ -58,8 +61,23 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void handshakeFinished() throws SSLException {
|
||||
wrapped.handshakeFinished();
|
||||
public OpenSslSessionId sessionId() {
|
||||
return wrapped.sessionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionId(OpenSslSessionId id) {
|
||||
wrapped.setSessionId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setLocalCertificate(Certificate[] localCertificate) {
|
||||
wrapped.setLocalCertificate(localCertificate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPeerSupportedSignatureAlgorithms() {
|
||||
return EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -78,7 +96,7 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SSLSessionContext getSessionContext() {
|
||||
public final OpenSslSessionContext getSessionContext() {
|
||||
return wrapped.getSessionContext();
|
||||
}
|
||||
|
||||
@ -103,13 +121,22 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void putValue(String s, Object o) {
|
||||
wrapped.putValue(s, o);
|
||||
public final void putValue(String name, Object value) {
|
||||
if (value instanceof SSLSessionBindingListener) {
|
||||
// Decorate the value if needed so we submit the correct SSLSession instance
|
||||
value = new SSLSessionBindingListenerDecorator((SSLSessionBindingListener) value);
|
||||
}
|
||||
wrapped.putValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object getValue(String s) {
|
||||
return wrapped.getValue(s);
|
||||
Object value = wrapped.getValue(s);
|
||||
if (value instanceof SSLSessionBindingListenerDecorator) {
|
||||
// Unwrap as needed so we return the original value
|
||||
return ((SSLSessionBindingListenerDecorator) value).delegate;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -176,4 +203,36 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
||||
public final int getApplicationBufferSize() {
|
||||
return wrapped.getApplicationBufferSize();
|
||||
}
|
||||
|
||||
private final class SSLSessionBindingListenerDecorator implements SSLSessionBindingListener {
|
||||
|
||||
final SSLSessionBindingListener delegate;
|
||||
|
||||
SSLSessionBindingListenerDecorator(SSLSessionBindingListener delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueBound(SSLSessionBindingEvent event) {
|
||||
delegate.valueBound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueUnbound(SSLSessionBindingEvent event) {
|
||||
delegate.valueUnbound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException {
|
||||
wrapped.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExtendedOpenSslSession{" +
|
||||
"wrapped=" + wrapped +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -316,16 +316,6 @@ public class JdkSslContext extends SslContext {
|
||||
return unmodifiableCipherSuites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionCacheSize() {
|
||||
return sessionContext().getSessionCacheSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionTimeout() {
|
||||
return sessionContext().getSessionTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||
return configureAndWrapEngine(context().createSSLEngine(), alloc);
|
||||
|
@ -42,13 +42,14 @@ final class OpenSslClientContext extends OpenSslContext {
|
||||
long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStore,
|
||||
Map.Entry<SslContextOption<?>, Object>... options)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||
ClientAuth.NONE, protocols, false, enableOcsp, options);
|
||||
boolean success = false;
|
||||
try {
|
||||
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
|
||||
sessionCacheSize, sessionTimeout);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2021 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:
|
||||
*
|
||||
* https://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.internal.tcnative.SSL;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link OpenSslSessionCache} that is used by the client-side.
|
||||
*/
|
||||
final class OpenSslClientSessionCache extends OpenSslSessionCache {
|
||||
// TODO: Should we support to have a List of OpenSslSessions for a Host/Port key and so be able to
|
||||
// support sessions for different protocols / ciphers to the same remote peer ?
|
||||
private final Map<HostPort, NativeSslSession> sessions = new HashMap<HostPort, NativeSslSession>();
|
||||
|
||||
OpenSslClientSessionCache(OpenSslEngineMap engineMap) {
|
||||
super(engineMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean sessionCreated(NativeSslSession session) {
|
||||
assert Thread.holdsLock(this);
|
||||
HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
|
||||
if (hostPort == null || sessions.containsKey(hostPort)) {
|
||||
return false;
|
||||
}
|
||||
sessions.put(hostPort, session);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sessionRemoved(NativeSslSession session) {
|
||||
assert Thread.holdsLock(this);
|
||||
HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
|
||||
if (hostPort == null) {
|
||||
return;
|
||||
}
|
||||
sessions.remove(hostPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSession(long ssl, String host, int port) {
|
||||
HostPort hostPort = keyFor(host, port);
|
||||
if (hostPort == null) {
|
||||
return;
|
||||
}
|
||||
final NativeSslSession session;
|
||||
final boolean reused;
|
||||
synchronized (this) {
|
||||
session = sessions.get(hostPort);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
if (!session.isValid()) {
|
||||
removeSessionWithId(session.sessionId());
|
||||
return;
|
||||
}
|
||||
// Try to set the session, if true is returned OpenSSL incremented the reference count
|
||||
// of the underlying SSL_SESSION*.
|
||||
reused = SSL.setSession(ssl, session.session());
|
||||
}
|
||||
|
||||
if (reused) {
|
||||
if (session.shouldBeSingleUse()) {
|
||||
// Should only be used once
|
||||
session.invalidate();
|
||||
}
|
||||
session.updateLastAccessedTime();
|
||||
}
|
||||
}
|
||||
|
||||
private static HostPort keyFor(String host, int port) {
|
||||
if (host == null && port < 1) {
|
||||
return null;
|
||||
}
|
||||
return new HostPort(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void clear() {
|
||||
super.clear();
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Host / Port tuple used to find a {@link OpenSslSession} in the cache.
|
||||
*/
|
||||
private static final class HostPort {
|
||||
private final int hash;
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
HostPort(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
// Calculate a hashCode that does ignore case.
|
||||
this.hash = 31 * AsciiString.hashCode(host) + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof HostPort)) {
|
||||
return false;
|
||||
}
|
||||
HostPort other = (HostPort) obj;
|
||||
return port == other.port && host.equalsIgnoreCase(other.host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HostPort{" +
|
||||
"host='" + host + '\'' +
|
||||
", port=" + port +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -29,21 +29,21 @@ import javax.net.ssl.SSLException;
|
||||
*/
|
||||
public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
|
||||
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg,
|
||||
long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
|
||||
Map.Entry<SslContextOption<?>, Object>... options)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
|
||||
super(ciphers, cipherFilter, toNegotiator(apnCfg), mode, keyCertChain,
|
||||
clientAuth, protocols, startTls, enableOcsp, false, options);
|
||||
}
|
||||
|
||||
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
|
||||
long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp, Map.Entry<SslContextOption<?>, Object>... options) throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols,
|
||||
startTls, enableOcsp, false, options);
|
||||
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
|
||||
int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
|
||||
Map.Entry<SslContextOption<?>, Object>... options)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, mode, keyCertChain,
|
||||
clientAuth, protocols, startTls, enableOcsp, false, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,14 +55,15 @@ final class OpenSslServerContext extends OpenSslContext {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp, String keyStore, Map.Entry<SslContextOption<?>, Object>... options)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
clientAuth, protocols, startTls, enableOcsp, options);
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
|
||||
sessionCacheSize, sessionTimeout);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
|
@ -26,81 +26,7 @@ import java.util.concurrent.locks.Lock;
|
||||
*/
|
||||
public final class OpenSslServerSessionContext extends OpenSslSessionContext {
|
||||
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||
super(context, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTimeout(int seconds) {
|
||||
if (seconds < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Lock writerLock = context.ctxLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
SSLContext.setSessionCacheTimeout(context.ctx, seconds);
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionTimeout() {
|
||||
Lock readerLock = context.ctxLock.readLock();
|
||||
readerLock.lock();
|
||||
try {
|
||||
return (int) SSLContext.getSessionCacheTimeout(context.ctx);
|
||||
} finally {
|
||||
readerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionCacheSize(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Lock writerLock = context.ctxLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
SSLContext.setSessionCacheSize(context.ctx, size);
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionCacheSize() {
|
||||
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;
|
||||
|
||||
Lock writerLock = context.ctxLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
SSLContext.setSessionCacheMode(context.ctx, mode);
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSessionCacheEnabled() {
|
||||
Lock readerLock = context.ctxLock.readLock();
|
||||
readerLock.lock();
|
||||
try {
|
||||
return SSLContext.getSessionCacheMode(context.ctx) == SSL.SSL_SESS_CACHE_SERVER;
|
||||
} finally {
|
||||
readerLock.unlock();
|
||||
}
|
||||
super(context, provider, SSL.SSL_SESS_CACHE_SERVER, new OpenSslSessionCache(context.engineMap));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,16 +15,36 @@
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.util.ReferenceCounted;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/**
|
||||
* {@link SSLSession} that is specific to our native implementation and {@link ReferenceCounted} to track native
|
||||
* resources.
|
||||
*/
|
||||
interface OpenSslSession extends SSLSession {
|
||||
|
||||
/**
|
||||
* Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by
|
||||
* the user.
|
||||
* Return the {@link OpenSslSessionId} that can be used to identify this session.
|
||||
*/
|
||||
void handshakeFinished() throws SSLException;
|
||||
OpenSslSessionId sessionId();
|
||||
|
||||
/**
|
||||
* Set the local certificate chain that is used. It is not expected that this array will be changed at all
|
||||
* and so its ok to not copy the array.
|
||||
*/
|
||||
void setLocalCertificate(Certificate[] localCertificate);
|
||||
|
||||
/**
|
||||
* Set the {@link OpenSslSessionId} for the {@link OpenSslSession}.
|
||||
*/
|
||||
void setSessionId(OpenSslSessionId id);
|
||||
|
||||
@Override
|
||||
OpenSslSessionContext getSessionContext();
|
||||
|
||||
/**
|
||||
* Expand (or increase) the value returned by {@link #getApplicationBufferSize()} if necessary.
|
||||
@ -33,4 +53,10 @@ interface OpenSslSession extends SSLSession {
|
||||
* @param packetLengthDataOnly The packet size which exceeds the current {@link #getApplicationBufferSize()}.
|
||||
*/
|
||||
void tryExpandApplicationBufferSize(int packetLengthDataOnly);
|
||||
|
||||
/**
|
||||
* Called once the handshake has completed.
|
||||
*/
|
||||
void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException;
|
||||
}
|
||||
|
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright 2021 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:
|
||||
*
|
||||
* https://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.internal.tcnative.SSLSession;
|
||||
import io.netty.internal.tcnative.SSLSessionCache;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import io.netty.util.ResourceLeakDetectorFactory;
|
||||
import io.netty.util.ResourceLeakTracker;
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* {@link SSLSessionCache} implementation for our native SSL implementation.
|
||||
*/
|
||||
class OpenSslSessionCache implements SSLSessionCache {
|
||||
private static final OpenSslSession[] EMPTY_SESSIONS = new OpenSslSession[0];
|
||||
|
||||
private static final int DEFAULT_CACHE_SIZE;
|
||||
static {
|
||||
// Respect the same system property as the JDK implementation to make it easy to switch between implementations.
|
||||
int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480);
|
||||
if (cacheSize >= 0) {
|
||||
DEFAULT_CACHE_SIZE = cacheSize;
|
||||
} else {
|
||||
DEFAULT_CACHE_SIZE = 20480;
|
||||
}
|
||||
}
|
||||
private final OpenSslEngineMap engineMap;
|
||||
|
||||
private final Map<OpenSslSessionId, NativeSslSession> sessions =
|
||||
new LinkedHashMap<OpenSslSessionId, NativeSslSession>() {
|
||||
|
||||
private static final long serialVersionUID = -7773696788135734448L;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<OpenSslSessionId, NativeSslSession> eldest) {
|
||||
int maxSize = maximumCacheSize.get();
|
||||
if (maxSize >= 0 && size() > maxSize) {
|
||||
removeSessionWithId(eldest.getKey());
|
||||
}
|
||||
// We always need to return false as we modify the map directly.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private final AtomicInteger maximumCacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
|
||||
|
||||
// Let's use the same default value as OpenSSL does.
|
||||
// See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_default_timeout.html
|
||||
private final AtomicInteger sessionTimeout = new AtomicInteger(300);
|
||||
private int sessionCounter;
|
||||
|
||||
OpenSslSessionCache(OpenSslEngineMap engineMap) {
|
||||
this.engineMap = engineMap;
|
||||
}
|
||||
|
||||
final void setSessionTimeout(int seconds) {
|
||||
int oldTimeout = sessionTimeout.getAndSet(seconds);
|
||||
if (oldTimeout > seconds) {
|
||||
// Drain the whole cache as this way we can use the ordering of the LinkedHashMap to detect early
|
||||
// if there are any other sessions left that are invalid.
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
final int getSessionTimeout() {
|
||||
return sessionTimeout.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a new {@link OpenSslSession} was created.
|
||||
*
|
||||
* @param session the new session.
|
||||
* @return {@code true} if the session should be cached, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean sessionCreated(NativeSslSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once an {@link OpenSslSession} was removed from the cache.
|
||||
*
|
||||
* @param session the session to remove.
|
||||
*/
|
||||
protected void sessionRemoved(NativeSslSession session) { }
|
||||
|
||||
final void setSessionCacheSize(int size) {
|
||||
long oldSize = maximumCacheSize.getAndSet(size);
|
||||
if (oldSize > size || size == 0) {
|
||||
// Just keep it simple for now and drain the whole cache.
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
final int getSessionCacheSize() {
|
||||
return maximumCacheSize.get();
|
||||
}
|
||||
|
||||
private void expungeInvalidSessions() {
|
||||
if (sessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<OpenSslSessionId, NativeSslSession>> iterator = sessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
NativeSslSession session = iterator.next().getValue();
|
||||
// As we use a LinkedHashMap we can break the while loop as soon as we find a valid session.
|
||||
// This is true as we always drain the cache as soon as we change the timeout to a smaller value as
|
||||
// it was set before. This way its true that the insertation order matches the timeout order.
|
||||
if (session.isValid(now)) {
|
||||
break;
|
||||
}
|
||||
iterator.remove();
|
||||
|
||||
notifyRemovalAndFree(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean sessionCreated(long ssl, long sslSession) {
|
||||
ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
// We couldn't find the engine itself.
|
||||
return false;
|
||||
}
|
||||
NativeSslSession session = new NativeSslSession(sslSession, engine.getPeerHost(), engine.getPeerPort(),
|
||||
getSessionTimeout() * 1000L);
|
||||
engine.setSessionId(session.sessionId());
|
||||
synchronized (this) {
|
||||
// Mimic what OpenSSL is doing and expunge every 255 new sessions
|
||||
// See https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_flush_sessions.html
|
||||
if (++sessionCounter == 255) {
|
||||
sessionCounter = 0;
|
||||
expungeInvalidSessions();
|
||||
}
|
||||
|
||||
if (!sessionCreated(session)) {
|
||||
// Should not be cached, return false. In this case we also need to call close() to ensure we
|
||||
// close the ResourceLeakTracker.
|
||||
session.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
final NativeSslSession old = sessions.put(session.sessionId(), session);
|
||||
if (old != null) {
|
||||
notifyRemovalAndFree(old);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long getSession(long ssl, byte[] sessionId) {
|
||||
OpenSslSessionId id = new OpenSslSessionId(sessionId);
|
||||
final NativeSslSession session;
|
||||
synchronized (this) {
|
||||
session = sessions.get(id);
|
||||
if (session == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the session is not valid anymore we should remove it from the cache and just signal back
|
||||
// that we couldn't find a session that is re-usable.
|
||||
if (!session.isValid() ||
|
||||
// This needs to happen in the synchronized block so we ensure we never destroy it before we
|
||||
// incremented the reference count. If we cant increment the reference count there is something
|
||||
// wrong. In this case just remove the session from the cache and signal back that we couldn't
|
||||
// find a session for re-use.
|
||||
!session.upRef()) {
|
||||
// Remove the session from the cache. This will also take care of calling SSL_SESSION_free(...)
|
||||
removeSessionWithId(session.sessionId());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// At this point we already incremented the reference count via SSL_SESSION_up_ref(...).
|
||||
if (session.shouldBeSingleUse()) {
|
||||
// Should only be used once. In this case invalidate the session which will also ensure we remove it
|
||||
// from the cache and call SSL_SESSION_free(...).
|
||||
removeSessionWithId(session.sessionId());
|
||||
}
|
||||
}
|
||||
session.updateLastAccessedTime();
|
||||
return session.session();
|
||||
}
|
||||
|
||||
void setSession(long ssl, String host, int port) {
|
||||
// Do nothing by default as this needs special handling for the client side.
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the session with the given id from the cache
|
||||
*/
|
||||
final synchronized void removeSessionWithId(OpenSslSessionId id) {
|
||||
NativeSslSession sslSession = sessions.remove(id);
|
||||
if (sslSession != null) {
|
||||
notifyRemovalAndFree(sslSession);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if there is a session for the given id in the cache.
|
||||
*/
|
||||
final synchronized boolean containsSessionWithId(OpenSslSessionId id) {
|
||||
return sessions.containsKey(id);
|
||||
}
|
||||
|
||||
private void notifyRemovalAndFree(NativeSslSession session) {
|
||||
sessionRemoved(session);
|
||||
session.free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenSslSession} which is cached for the given id.
|
||||
*/
|
||||
final synchronized OpenSslSession getSession(OpenSslSessionId id) {
|
||||
NativeSslSession session = sessions.get(id);
|
||||
if (session != null && !session.isValid()) {
|
||||
// The session is not valid anymore, let's remove it and just signal back that there is no session
|
||||
// with the given ID in the cache anymore. This also takes care of calling SSL_SESSION_free(...)
|
||||
removeSessionWithId(session.sessionId());
|
||||
return null;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the session ids of the current valid sessions.
|
||||
*/
|
||||
final List<OpenSslSessionId> getIds() {
|
||||
final OpenSslSession[] sessionsArray;
|
||||
synchronized (this) {
|
||||
sessionsArray = sessions.values().toArray(EMPTY_SESSIONS);
|
||||
}
|
||||
List<OpenSslSessionId> ids = new ArrayList<OpenSslSessionId>(sessionsArray.length);
|
||||
for (OpenSslSession session: sessionsArray) {
|
||||
if (session.isValid()) {
|
||||
ids.add(session.sessionId());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache and free all cached SSL_SESSION*.
|
||||
*/
|
||||
synchronized void clear() {
|
||||
Iterator<Map.Entry<OpenSslSessionId, NativeSslSession>> iterator = sessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
NativeSslSession session = iterator.next().getValue();
|
||||
iterator.remove();
|
||||
|
||||
// Notify about removal. This also takes care of calling SSL_SESSION_free(...).
|
||||
notifyRemovalAndFree(session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link OpenSslSession} implementation which wraps the native SSL_SESSION* while in cache.
|
||||
*/
|
||||
static final class NativeSslSession implements OpenSslSession {
|
||||
static final ResourceLeakDetector<NativeSslSession> LEAK_DETECTOR = ResourceLeakDetectorFactory.instance()
|
||||
.newResourceLeakDetector(NativeSslSession.class);
|
||||
private final ResourceLeakTracker<NativeSslSession> leakTracker;
|
||||
private final long session;
|
||||
private final String peerHost;
|
||||
private final int peerPort;
|
||||
private final OpenSslSessionId id;
|
||||
private final long timeout;
|
||||
private final long creationTime = System.currentTimeMillis();
|
||||
private volatile long lastAccessedTime = creationTime;
|
||||
private volatile boolean valid = true;
|
||||
private boolean freed;
|
||||
|
||||
NativeSslSession(long session, String peerHost, int peerPort, long timeout) {
|
||||
this.session = session;
|
||||
this.peerHost = peerHost;
|
||||
this.peerPort = peerPort;
|
||||
this.timeout = timeout;
|
||||
this.id = new OpenSslSessionId(io.netty.internal.tcnative.SSLSession.getSessionId(session));
|
||||
leakTracker = LEAK_DETECTOR.track(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionId(OpenSslSessionId id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
boolean shouldBeSingleUse() {
|
||||
assert !freed;
|
||||
return SSLSession.shouldBeSingleUse(session);
|
||||
}
|
||||
|
||||
long session() {
|
||||
assert !freed;
|
||||
return session;
|
||||
}
|
||||
|
||||
boolean upRef() {
|
||||
assert !freed;
|
||||
return SSLSession.upRef(session);
|
||||
}
|
||||
|
||||
synchronized void free() {
|
||||
close();
|
||||
SSLSession.free(session);
|
||||
}
|
||||
|
||||
void close() {
|
||||
assert !freed;
|
||||
freed = true;
|
||||
invalidate();
|
||||
if (leakTracker != null) {
|
||||
leakTracker.close(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenSslSessionId sessionId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
boolean isValid(long now) {
|
||||
return creationTime + timeout >= now && valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalCertificate(Certificate[] localCertificate) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenSslSessionContext getSessionContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tryExpandApplicationBufferSize(int packetLengthDataOnly) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getId() {
|
||||
return id.cloneBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
void updateLastAccessedTime() {
|
||||
lastAccessedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
return lastAccessedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return isValid(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putValue(String name, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(String name) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
return EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getPeerCertificates() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getLocalCertificates() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getPeerCertificateChain() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPeerPrincipal() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getLocalPrincipal() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherSuite() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPeerHost() {
|
||||
return peerHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeerPort() {
|
||||
return peerPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPacketBufferSize() {
|
||||
return ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApplicationBufferSize() {
|
||||
return ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof OpenSslSession)) {
|
||||
return false;
|
||||
}
|
||||
OpenSslSession session1 = (OpenSslSession) o;
|
||||
return id.equals(session1.sessionId());
|
||||
}
|
||||
}
|
||||
}
|
@ -20,19 +20,19 @@ import static java.util.Objects.requireNonNull;
|
||||
import io.netty.internal.tcnative.SSL;
|
||||
import io.netty.internal.tcnative.SSLContext;
|
||||
import io.netty.internal.tcnative.SessionTicketKey;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* OpenSSL specific {@link SSLSessionContext} implementation.
|
||||
*/
|
||||
public abstract class OpenSslSessionContext implements SSLSessionContext {
|
||||
private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
|
||||
|
||||
private final OpenSslSessionStats stats;
|
||||
|
||||
@ -43,29 +43,76 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
||||
|
||||
final ReferenceCountedOpenSslContext context;
|
||||
|
||||
private final OpenSslSessionCache sessionCache;
|
||||
private final long mask;
|
||||
|
||||
// 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(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||
OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider, long mask,
|
||||
OpenSslSessionCache cache) {
|
||||
this.context = context;
|
||||
this.provider = provider;
|
||||
this.mask = mask;
|
||||
stats = new OpenSslSessionStats(context);
|
||||
sessionCache = cache;
|
||||
SSLContext.setSSLSessionCache(context.ctx, cache);
|
||||
}
|
||||
|
||||
final boolean useKeyManager() {
|
||||
return provider != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionCacheSize(int size) {
|
||||
ObjectUtil.checkPositiveOrZero(size, "size");
|
||||
sessionCache.setSessionCacheSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionCacheSize() {
|
||||
return sessionCache.getSessionCacheSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTimeout(int seconds) {
|
||||
ObjectUtil.checkPositiveOrZero(seconds, "seconds");
|
||||
|
||||
Lock writerLock = context.ctxLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
SSLContext.setSessionCacheTimeout(context.ctx, seconds);
|
||||
sessionCache.setSessionTimeout(seconds);
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionTimeout() {
|
||||
return sessionCache.getSessionTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession(byte[] bytes) {
|
||||
requireNonNull(bytes, "bytes");
|
||||
return null;
|
||||
return sessionCache.getSession(new OpenSslSessionId(bytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<byte[]> getIds() {
|
||||
return EMPTY;
|
||||
return new Enumeration<byte[]>() {
|
||||
private final Iterator<OpenSslSessionId> ids = sessionCache.getIds().iterator();
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return ids.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextElement() {
|
||||
return ids.next().cloneBytes();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,12 +171,33 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
||||
/**
|
||||
* Enable or disable caching of SSL sessions.
|
||||
*/
|
||||
public abstract void setSessionCacheEnabled(boolean enabled);
|
||||
public void setSessionCacheEnabled(boolean enabled) {
|
||||
long mode = enabled ? mask | SSL.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP |
|
||||
SSL.SSL_SESS_CACHE_NO_INTERNAL_STORE : SSL.SSL_SESS_CACHE_OFF;
|
||||
Lock writerLock = context.ctxLock.writeLock();
|
||||
writerLock.lock();
|
||||
try {
|
||||
SSLContext.setSessionCacheMode(context.ctx, mode);
|
||||
if (!enabled) {
|
||||
sessionCache.clear();
|
||||
}
|
||||
} finally {
|
||||
writerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise.
|
||||
*/
|
||||
public abstract boolean isSessionCacheEnabled();
|
||||
public boolean isSessionCacheEnabled() {
|
||||
Lock readerLock = context.ctxLock.readLock();
|
||||
readerLock.lock();
|
||||
try {
|
||||
return (SSLContext.getSessionCacheMode(context.ctx) & mask) != 0;
|
||||
} finally {
|
||||
readerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats of this context.
|
||||
@ -138,21 +206,25 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given {@link OpenSslSession} from the cache, and so not re-use it for new connections.
|
||||
*/
|
||||
final void removeFromCache(OpenSslSessionId id) {
|
||||
sessionCache.removeSessionWithId(id);
|
||||
}
|
||||
|
||||
final boolean isInCache(OpenSslSessionId id) {
|
||||
return sessionCache.containsSessionWithId(id);
|
||||
}
|
||||
|
||||
void setSessionFromCache(String host, int port, long ssl) {
|
||||
sessionCache.setSession(ssl, host, port);
|
||||
}
|
||||
|
||||
final void destroy() {
|
||||
if (provider != null) {
|
||||
provider.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EmptyEnumeration implements Enumeration<byte[]> {
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextElement() {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
sessionCache.clear();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2021 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:
|
||||
*
|
||||
* https://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.EmptyArrays;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represent the session ID used by an {@link OpenSslSession}.
|
||||
*/
|
||||
final class OpenSslSessionId {
|
||||
|
||||
private final byte[] id;
|
||||
private final int hashCode;
|
||||
|
||||
static final OpenSslSessionId NULL_ID = new OpenSslSessionId(EmptyArrays.EMPTY_BYTES);
|
||||
|
||||
OpenSslSessionId(byte[] id) {
|
||||
// We take ownership if the byte[] and so there is no need to clone it.
|
||||
this.id = id;
|
||||
// cache the hashCode as the byte[] array will never change
|
||||
this.hashCode = Arrays.hashCode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof OpenSslSessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(id, ((OpenSslSessionId) o).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OpenSslSessionId{" +
|
||||
"id=" + Arrays.toString(id) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
byte[] cloneBytes() {
|
||||
return id.clone();
|
||||
}
|
||||
}
|
@ -63,12 +63,13 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
||||
String[] protocols, long sessionCacheSize, long sessionTimeout,
|
||||
boolean enableOcsp, String keyStore,
|
||||
Map.Entry<SslContextOption<?>, Object>... options) throws SSLException {
|
||||
super(ciphers, cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT,
|
||||
keyCertChain, ClientAuth.NONE, protocols, false, enableOcsp, true, options);
|
||||
super(ciphers, cipherFilter, toNegotiator(apn), SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||
ClientAuth.NONE, protocols, false, enableOcsp, true, options);
|
||||
boolean success = false;
|
||||
try {
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
|
||||
sessionCacheSize, sessionTimeout);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
@ -88,7 +89,8 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
||||
TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key,
|
||||
String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
String keyStore) throws SSLException {
|
||||
String keyStore, long sessionCacheSize, long sessionTimeout)
|
||||
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");
|
||||
@ -169,9 +171,18 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
||||
throw new SSLException("unable to setup trustmanager", e);
|
||||
}
|
||||
OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider);
|
||||
context.setSessionCacheEnabled(CLIENT_ENABLE_SESSION_CACHE);
|
||||
if (sessionCacheSize > 0) {
|
||||
context.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE));
|
||||
}
|
||||
if (sessionTimeout > 0) {
|
||||
context.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
if (CLIENT_ENABLE_SESSION_TICKET) {
|
||||
context.setTicketKeys();
|
||||
}
|
||||
|
||||
keyMaterialProvider = null;
|
||||
return context;
|
||||
} finally {
|
||||
@ -181,44 +192,9 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
||||
}
|
||||
}
|
||||
|
||||
// No cache is currently supported for client side mode.
|
||||
static final class OpenSslClientSessionContext extends OpenSslSessionContext {
|
||||
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||
super(context, provider);
|
||||
}
|
||||
|
||||
@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;
|
||||
super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,12 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
static final boolean SERVER_ENABLE_SESSION_TICKET_TLSV13 =
|
||||
SystemPropertyUtil.getBoolean("jdk.tls.server.enableSessionTicketExtension", true);
|
||||
|
||||
static final boolean SERVER_ENABLE_SESSION_CACHE =
|
||||
SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheServer", true);
|
||||
// session caching is disabled by default on the client side due a JDK bug:
|
||||
// https://mail.openjdk.java.net/pipermail/security-dev/2021-March/024758.html
|
||||
static final boolean CLIENT_ENABLE_SESSION_CACHE =
|
||||
SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheClient", false);
|
||||
/**
|
||||
* The OpenSSL SSL_CTX object.
|
||||
*
|
||||
@ -111,8 +117,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
*/
|
||||
protected long ctx;
|
||||
private final List<String> unmodifiableCiphers;
|
||||
private final long sessionCacheSize;
|
||||
private final long sessionTimeout;
|
||||
private final OpenSslApplicationProtocolNegotiator apn;
|
||||
private final int mode;
|
||||
|
||||
@ -193,8 +197,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
final boolean tlsFalseStart;
|
||||
|
||||
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
|
||||
long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
|
||||
boolean leakDetection, Map.Entry<SslContextOption<?>, Object>... ctxOptions)
|
||||
throws SSLException {
|
||||
@ -344,22 +347,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
}
|
||||
}
|
||||
|
||||
/* Set session cache size, if specified */
|
||||
if (sessionCacheSize <= 0) {
|
||||
// Get the default session cache size using SSLContext.setSessionCacheSize()
|
||||
sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
|
||||
}
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
||||
|
||||
/* Set session timeout, if specified */
|
||||
if (sessionTimeout <= 0) {
|
||||
// Get the default session timeout using SSLContext.setSessionCacheTimeout()
|
||||
sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
|
||||
}
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
|
||||
if (enableOcsp) {
|
||||
SSLContext.enableOcsp(ctx, isClient());
|
||||
}
|
||||
@ -392,16 +379,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
return unmodifiableCiphers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionCacheSize() {
|
||||
return sessionCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
|
||||
return apn;
|
||||
|
@ -44,7 +44,6 @@ 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 java.nio.ByteBuffer;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
@ -121,7 +120,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
/**
|
||||
* Depends upon tcnative ... only use if tcnative is available!
|
||||
*/
|
||||
private static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH;
|
||||
static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH;
|
||||
|
||||
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);
|
||||
@ -158,6 +157,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
private volatile String applicationProtocol;
|
||||
private volatile boolean needTask;
|
||||
private String[] explicitlyEnabledProtocols;
|
||||
private boolean sessionSet;
|
||||
|
||||
// Reference Counting
|
||||
private final ResourceLeakTracker<ReferenceCountedOpenSslEngine> leak;
|
||||
@ -183,7 +183,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
};
|
||||
|
||||
private volatile ClientAuth clientAuth = ClientAuth.NONE;
|
||||
private volatile Certificate[] localCertificateChain;
|
||||
|
||||
// Updated once a new handshake is started and so the SSLSession reused.
|
||||
private volatile long lastAccessed = -1;
|
||||
@ -309,9 +308,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
engineMap = context.engineMap;
|
||||
enableOcsp = context.enableOcsp;
|
||||
if (!context.sessionContext().useKeyManager()) {
|
||||
// If we do not use the KeyManagerFactory we need to set localCertificateChain now.
|
||||
// When we use a KeyManagerFactory it will be set during setKeyMaterial(...).
|
||||
localCertificateChain = context.keyCertChain;
|
||||
session.setLocalCertificate(context.keyCertChain);
|
||||
}
|
||||
|
||||
this.jdkCompatibilityMode = jdkCompatibilityMode;
|
||||
@ -349,15 +346,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
SSL.enableOcsp(ssl);
|
||||
}
|
||||
|
||||
int mode = SSL.getMode(ssl);
|
||||
if (context.tlsFalseStart) {
|
||||
mode |= SSL.SSL_MODE_ENABLE_FALSE_START;
|
||||
}
|
||||
|
||||
if (!jdkCompatibilityMode) {
|
||||
mode |= SSL.SSL_MODE_ENABLE_PARTIAL_WRITE;
|
||||
SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
}
|
||||
SSL.setMode(ssl, mode);
|
||||
|
||||
if (isProtocolEnabled(SSL.getOptions(ssl), SSL.SSL_OP_NO_TLSv1_3, PROTOCOL_TLS_V1_3)) {
|
||||
final boolean enableTickets = clientMode ?
|
||||
@ -409,7 +400,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
}
|
||||
SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
|
||||
}
|
||||
localCertificateChain = keyMaterial.certificateChain();
|
||||
session.setLocalCertificate(keyMaterial.certificateChain());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -420,6 +411,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
return new SecretKeySpec(SSL.getMasterKey(ssl), "AES");
|
||||
}
|
||||
|
||||
synchronized boolean isSessionReused() {
|
||||
if (isDestroyed()) {
|
||||
return false;
|
||||
}
|
||||
return SSL.isSessionReused(ssl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OCSP response.
|
||||
*/
|
||||
@ -1892,6 +1890,12 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
|
||||
// Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier.
|
||||
engineMap.add(this);
|
||||
|
||||
if (!sessionSet) {
|
||||
parentContext.sessionContext().setSessionFromCache(getPeerHost(), getPeerPort(), ssl);
|
||||
sessionSet = true;
|
||||
}
|
||||
|
||||
if (lastAccessed == -1) {
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
@ -1923,7 +1927,10 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
return NEED_WRAP;
|
||||
}
|
||||
// if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished.
|
||||
session.handshakeFinished();
|
||||
session.handshakeFinished(SSL.getSessionId(ssl), SSL.getCipherForSSL(ssl), SSL.getVersion(ssl),
|
||||
SSL.getPeerCertificate(ssl), SSL.getPeerCertChain(ssl),
|
||||
SSL.getTime(ssl) * 1000L, parentContext.sessionTimeout() * 1000L);
|
||||
selectApplicationProtocol();
|
||||
return FINISHED;
|
||||
}
|
||||
|
||||
@ -2173,6 +2180,71 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
return Buffer.address(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the application protocol used.
|
||||
*/
|
||||
private void selectApplicationProtocol() throws SSLException {
|
||||
ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior();
|
||||
List<String> 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) {
|
||||
ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
|
||||
protocols, behavior, applicationProtocol);
|
||||
}
|
||||
break;
|
||||
case NPN:
|
||||
applicationProtocol = SSL.getNextProtoNegotiated(ssl);
|
||||
if (applicationProtocol != null) {
|
||||
ReferenceCountedOpenSslEngine.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) {
|
||||
ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
|
||||
protocols, behavior, applicationProtocol);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
private String selectApplicationProtocol(List<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final void setSessionId(OpenSslSessionId id) {
|
||||
session.setSessionId(id);
|
||||
}
|
||||
|
||||
private final class DefaultOpenSslSession implements OpenSslSession {
|
||||
private final OpenSslSessionContext sessionContext;
|
||||
|
||||
@ -2181,12 +2253,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
private X509Certificate[] x509PeerCerts;
|
||||
private Certificate[] peerCerts;
|
||||
|
||||
private boolean valid = true;
|
||||
private String protocol;
|
||||
private String cipher;
|
||||
private byte[] id;
|
||||
private long creationTime;
|
||||
private OpenSslSessionId id = OpenSslSessionId.NULL_ID;
|
||||
private volatile long creationTime;
|
||||
private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH;
|
||||
|
||||
private volatile Certificate[] localCertificateChain;
|
||||
// lazy init for memory reasons
|
||||
private Map<String, Object> values;
|
||||
|
||||
@ -2199,28 +2272,49 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getId() {
|
||||
public void setSessionId(OpenSslSessionId sessionId) {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (id == null) {
|
||||
return EmptyArrays.EMPTY_BYTES;
|
||||
if (this.id == OpenSslSessionId.NULL_ID) {
|
||||
this.id = sessionId;
|
||||
creationTime = System.currentTimeMillis();
|
||||
}
|
||||
return id.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSessionContext getSessionContext() {
|
||||
public OpenSslSessionId sessionId() {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (this.id == OpenSslSessionId.NULL_ID && !isDestroyed()) {
|
||||
byte[] sessionId = SSL.getSessionId(ssl);
|
||||
if (sessionId != null) {
|
||||
id = new OpenSslSessionId(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalCertificate(Certificate[] localCertificate) {
|
||||
this.localCertificateChain = localCertificate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getId() {
|
||||
return sessionId().cloneBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenSslSessionContext getSessionContext() {
|
||||
return sessionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (creationTime == 0 && !isDestroyed()) {
|
||||
creationTime = SSL.getTime(ssl) * 1000L;
|
||||
}
|
||||
return creationTime;
|
||||
}
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -2233,20 +2327,16 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
@Override
|
||||
public void invalidate() {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (!isDestroyed()) {
|
||||
SSL.setTimeout(ssl, 0);
|
||||
}
|
||||
valid = false;
|
||||
sessionContext.removeFromCache(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (!isDestroyed()) {
|
||||
return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L);
|
||||
}
|
||||
return valid || sessionContext.isInCache(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -2320,15 +2410,50 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
* the user.
|
||||
*/
|
||||
@Override
|
||||
public void handshakeFinished() throws SSLException {
|
||||
public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout)
|
||||
throws SSLException {
|
||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||
if (!isDestroyed()) {
|
||||
id = SSL.getSessionId(ssl);
|
||||
cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
|
||||
protocol = SSL.getVersion(ssl);
|
||||
this.creationTime = creationTime;
|
||||
if (this.id == OpenSslSessionId.NULL_ID) {
|
||||
this.id = id == null ? OpenSslSessionId.NULL_ID : new OpenSslSessionId(id);
|
||||
}
|
||||
this.cipher = toJavaCipherSuite(cipher);
|
||||
this.protocol = protocol;
|
||||
|
||||
if (clientMode) {
|
||||
if (isEmpty(peerCertificateChain)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
peerCerts = new Certificate[peerCertificateChain.length];
|
||||
x509PeerCerts = new X509Certificate[peerCertificateChain.length];
|
||||
initCerts(peerCertificateChain, 0);
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
if (isEmpty(peerCertificate)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
if (isEmpty(peerCertificateChain)) {
|
||||
peerCerts = new Certificate[] {new LazyX509Certificate(peerCertificate)};
|
||||
x509PeerCerts = new X509Certificate[] {new LazyJavaxX509Certificate(peerCertificate)};
|
||||
} else {
|
||||
peerCerts = new Certificate[peerCertificateChain.length + 1];
|
||||
x509PeerCerts = new X509Certificate[peerCertificateChain.length + 1];
|
||||
peerCerts[0] = new LazyX509Certificate(peerCertificate);
|
||||
x509PeerCerts[0] = new LazyJavaxX509Certificate(peerCertificate);
|
||||
initCerts(peerCertificateChain, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initPeerCerts();
|
||||
selectApplicationProtocol();
|
||||
calculateMaxWrapOverhead();
|
||||
|
||||
handshakeState = HandshakeState.FINISHED;
|
||||
@ -2338,47 +2463,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (clientMode) {
|
||||
if (isEmpty(chain)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
peerCerts = new Certificate[chain.length];
|
||||
x509PeerCerts = new X509Certificate[chain.length];
|
||||
initCerts(chain, 0);
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
byte[] clientCert = SSL.getPeerCertificate(ssl);
|
||||
if (isEmpty(clientCert)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
if (isEmpty(chain)) {
|
||||
peerCerts = new Certificate[] {new LazyX509Certificate(clientCert)};
|
||||
x509PeerCerts = new X509Certificate[] {new LazyJavaxX509Certificate(clientCert)};
|
||||
} else {
|
||||
peerCerts = new Certificate[chain.length + 1];
|
||||
x509PeerCerts = new X509Certificate[chain.length + 1];
|
||||
peerCerts[0] = new LazyX509Certificate(clientCert);
|
||||
x509PeerCerts[0] = new LazyJavaxX509Certificate(clientCert);
|
||||
initCerts(chain, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initCerts(byte[][] chain, int startPos) {
|
||||
for (int i = 0; i < chain.length; i++) {
|
||||
int certPos = startPos + i;
|
||||
@ -2387,67 +2471,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the application protocol used.
|
||||
*/
|
||||
private void selectApplicationProtocol() throws SSLException {
|
||||
ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior();
|
||||
List<String> 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) {
|
||||
ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
|
||||
protocols, behavior, applicationProtocol);
|
||||
}
|
||||
break;
|
||||
case NPN:
|
||||
applicationProtocol = SSL.getNextProtoNegotiated(ssl);
|
||||
if (applicationProtocol != null) {
|
||||
ReferenceCountedOpenSslEngine.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) {
|
||||
ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
|
||||
protocols, behavior, applicationProtocol);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
private String selectApplicationProtocol(List<String> 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) {
|
||||
@ -2460,7 +2483,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
|
||||
@Override
|
||||
public Certificate[] getLocalCertificates() {
|
||||
Certificate[] localCerts = ReferenceCountedOpenSslEngine.this.localCertificateChain;
|
||||
Certificate[] localCerts = this.localCertificateChain;
|
||||
if (localCerts == null) {
|
||||
return null;
|
||||
}
|
||||
@ -2487,7 +2510,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
|
||||
@Override
|
||||
public Principal getLocalPrincipal() {
|
||||
Certificate[] local = ReferenceCountedOpenSslEngine.this.localCertificateChain;
|
||||
Certificate[] local = this.localCertificateChain;
|
||||
if (local == null || local.length == 0) {
|
||||
return null;
|
||||
}
|
||||
@ -2545,5 +2568,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
applicationBufferSize = MAX_RECORD_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultOpenSslSession{" +
|
||||
"sessionContext=" + sessionContext +
|
||||
", id=" + id +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import io.netty.internal.tcnative.SSL;
|
||||
import io.netty.internal.tcnative.SSLContext;
|
||||
import io.netty.internal.tcnative.SniHostNameMatcher;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
@ -70,13 +69,14 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp, String keyStore, Map.Entry<SslContextOption<?>, Object>... options)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
clientAuth, protocols, startTls, enableOcsp, true, options);
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
|
||||
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
|
||||
sessionCacheSize, sessionTimeout);
|
||||
if (SERVER_ENABLE_SESSION_TICKET) {
|
||||
sessionContext.setTicketKeys();
|
||||
}
|
||||
@ -99,7 +99,7 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
|
||||
TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key,
|
||||
String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
String keyStore)
|
||||
String keyStore, long sessionCacheSize, long sessionTimeout)
|
||||
throws SSLException {
|
||||
OpenSslKeyMaterialProvider keyMaterialProvider = null;
|
||||
try {
|
||||
@ -186,6 +186,14 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
|
||||
|
||||
OpenSslServerSessionContext sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider);
|
||||
sessionContext.setSessionIdContext(ID);
|
||||
// Enable session caching by default
|
||||
sessionContext.setSessionCacheEnabled(SERVER_ENABLE_SESSION_CACHE);
|
||||
if (sessionCacheSize > 0) {
|
||||
sessionContext.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE));
|
||||
}
|
||||
if (sessionTimeout > 0) {
|
||||
sessionContext.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
keyMaterialProvider = null;
|
||||
|
||||
|
@ -895,12 +895,16 @@ public abstract class SslContext {
|
||||
/**
|
||||
* Returns the size of the cache used for storing SSL session objects.
|
||||
*/
|
||||
public abstract long sessionCacheSize();
|
||||
public long sessionCacheSize() {
|
||||
return sessionContext().getSessionCacheSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timeout for the cached SSL session objects, in seconds.
|
||||
*/
|
||||
public abstract long sessionTimeout();
|
||||
public long sessionTimeout() {
|
||||
return sessionContext().getSessionTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #applicationProtocolNegotiator()} instead.
|
||||
|
@ -472,6 +472,10 @@ final class SslUtils {
|
||||
return TLSV13_CIPHERS.contains(cipher);
|
||||
}
|
||||
|
||||
static boolean isEmpty(Object[] arr) {
|
||||
return arr == null || arr.length == 0;
|
||||
}
|
||||
|
||||
private SslUtils() {
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +101,14 @@ public final class LazyJavaxX509Certificate extends X509Certificate {
|
||||
return bytes.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underyling {@code byte[]} without cloning it first. This {@code byte[]} <strong>must</strong> never
|
||||
* be mutated.
|
||||
*/
|
||||
byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key)
|
||||
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
|
||||
|
@ -22,6 +22,7 @@ import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -80,4 +81,9 @@ public class ConscryptJdkSslEngineInteropTest extends SSLEngineTest {
|
||||
// TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException.
|
||||
return super.mySetupMutualAuthServerIsValidServerException(cause) || causedBySSLException(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
// Not supported by conscrypt
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.util.ArrayList;
|
||||
@ -150,6 +151,11 @@ public class ConscryptOpenSslEngineInteropTest extends ConscryptSslEngineTest {
|
||||
super.testSessionLocalWhenNonMutualWithKeyManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
// Not supported by conscrypt
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSLEngine wrapEngine(SSLEngine engine) {
|
||||
return Java8SslTestUtils.wrapSSLEngineForTesting(engine);
|
||||
@ -160,6 +166,8 @@ public class ConscryptOpenSslEngineInteropTest extends ConscryptSslEngineTest {
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof OpenSslContext) {
|
||||
((OpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((OpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.security.Provider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -78,4 +79,15 @@ public class ConscryptSslEngineTest extends SSLEngineTest {
|
||||
@Override
|
||||
public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
// Not supported by conscrypt
|
||||
}
|
||||
|
||||
@Ignore("Possible Conscrypt bug")
|
||||
public void testSessionCacheTimeout() throws Exception {
|
||||
// Skip
|
||||
// https://github.com/google/conscrypt/issues/851
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -92,4 +93,15 @@ public class JdkConscryptSslEngineInteropTest extends SSLEngineTest {
|
||||
// Ignore as Conscrypt does not correctly return the local certificates while the TrustManager is invoked.
|
||||
// See https://github.com/google/conscrypt/issues/634
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
// Not supported by conscrypt
|
||||
}
|
||||
|
||||
@Ignore("Possible Conscrypt bug")
|
||||
public void testSessionCacheTimeout() {
|
||||
// Skip
|
||||
// https://github.com/google/conscrypt/issues/851
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,8 @@ public class JdkOpenSslEngineInteroptTest extends SSLEngineTest {
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof OpenSslContext) {
|
||||
((OpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((OpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.security.Provider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -142,6 +143,11 @@ public class OpenSslConscryptSslEngineInteropTest extends ConscryptSslEngineTest
|
||||
super.testSessionLocalWhenNonMutualWithKeyManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
// Not supported by conscrypt
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSLEngine wrapEngine(SSLEngine engine) {
|
||||
return Java8SslTestUtils.wrapSSLEngineForTesting(engine);
|
||||
@ -152,6 +158,8 @@ public class OpenSslConscryptSslEngineInteropTest extends ConscryptSslEngineTest
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof OpenSslContext) {
|
||||
((OpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((OpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -1415,7 +1415,41 @@ public class OpenSslEngineTest extends SSLEngineTest {
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof OpenSslContext) {
|
||||
((OpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((OpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testSessionCache() throws Exception {
|
||||
super.testSessionCache();
|
||||
assertSessionContext(clientSslCtx);
|
||||
assertSessionContext(serverSslCtx);
|
||||
}
|
||||
|
||||
private static void assertSessionContext(SslContext context) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
OpenSslSessionContext serverSessionCtx = (OpenSslSessionContext) context.sessionContext();
|
||||
assertTrue(serverSessionCtx.isSessionCacheEnabled());
|
||||
if (serverSessionCtx.getIds().hasMoreElements()) {
|
||||
serverSessionCtx.setSessionCacheEnabled(false);
|
||||
assertFalse(serverSessionCtx.getIds().hasMoreElements());
|
||||
assertFalse(serverSessionCtx.isSessionCacheEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertSessionReusedForEngine(SSLEngine clientEngine, SSLEngine serverEngine, boolean reuse) {
|
||||
assertEquals(reuse, unwrapEngine(clientEngine).isSessionReused());
|
||||
assertEquals(reuse, unwrapEngine(serverEngine).isSessionReused());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSessionMaybeReused(SSLEngine engine) {
|
||||
return unwrapEngine(engine).isSessionReused();
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@ -153,6 +152,8 @@ public class OpenSslJdkSslEngineInteroptTest extends SSLEngineTest {
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof OpenSslContext) {
|
||||
((OpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((OpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ public class ReferenceCountedOpenSslEngineTest extends OpenSslEngineTest {
|
||||
protected SslContext wrapContext(SslContext context) {
|
||||
if (context instanceof ReferenceCountedOpenSslContext) {
|
||||
((ReferenceCountedOpenSslContext) context).setUseTasks(useTasks);
|
||||
// Explicit enable the session cache as its disabled by default on the client side.
|
||||
((ReferenceCountedOpenSslContext) context).sessionContext().setSessionCacheEnabled(true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ 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.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
@ -111,6 +112,7 @@ import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
@ -130,6 +132,7 @@ import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_1;
|
||||
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2;
|
||||
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_3;
|
||||
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@ -1297,14 +1300,14 @@ public abstract class SSLEngineTest {
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.sslProvider(sslClientProvider())
|
||||
// This test only works for non TLSv1.3 for now
|
||||
.protocols(PROTOCOL_TLS_V1_2)
|
||||
.protocols(protocols())
|
||||
.sslContextProvider(clientSslContextProvider())
|
||||
.build());
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslServerProvider())
|
||||
// This test only works for non TLSv1.3 for now
|
||||
.protocols(PROTOCOL_TLS_V1_2)
|
||||
.protocols(protocols())
|
||||
.sslContextProvider(serverSslContextProvider())
|
||||
.build());
|
||||
SSLEngine clientEngine = null;
|
||||
@ -1319,10 +1322,54 @@ public abstract class SSLEngineTest {
|
||||
|
||||
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());
|
||||
if (protocolCipherCombo == ProtocolCipherCombo.TLSV13) {
|
||||
// Allocate something which is big enough for sure
|
||||
ByteBuffer packetBuffer = allocateBuffer(32 * 1024);
|
||||
ByteBuffer appBuffer = allocateBuffer(32 * 1024);
|
||||
|
||||
appBuffer.clear().position(4).flip();
|
||||
packetBuffer.clear();
|
||||
|
||||
do {
|
||||
SSLEngineResult result;
|
||||
|
||||
do {
|
||||
result = serverEngine.wrap(appBuffer, packetBuffer);
|
||||
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
appBuffer.clear();
|
||||
packetBuffer.flip();
|
||||
do {
|
||||
result = clientEngine.unwrap(packetBuffer, appBuffer);
|
||||
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
packetBuffer.clear();
|
||||
appBuffer.clear().position(4).flip();
|
||||
|
||||
do {
|
||||
result = clientEngine.wrap(appBuffer, packetBuffer);
|
||||
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
appBuffer.clear();
|
||||
packetBuffer.flip();
|
||||
|
||||
do {
|
||||
result = serverEngine.unwrap(packetBuffer, appBuffer);
|
||||
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
packetBuffer.clear();
|
||||
appBuffer.clear().position(4).flip();
|
||||
} while (clientEngine.getSession().getId().length == 0);
|
||||
|
||||
// With TLS1.3 we should see pseudo IDs and so these should never match.
|
||||
assertFalse(Arrays.equals(clientEngine.getSession().getId(), serverEngine.getSession().getId()));
|
||||
} else {
|
||||
// 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 {
|
||||
cleanupClientSslEngine(clientEngine);
|
||||
cleanupServerSslEngine(serverEngine);
|
||||
@ -1496,8 +1543,7 @@ public abstract class SSLEngineTest {
|
||||
ByteBuffer cTOs = allocateBuffer(clientEngine.getSession().getPacketBufferSize());
|
||||
ByteBuffer sTOc = allocateBuffer(serverEngine.getSession().getPacketBufferSize());
|
||||
|
||||
ByteBuffer serverAppReadBuffer = allocateBuffer(
|
||||
serverEngine.getSession().getApplicationBufferSize());
|
||||
ByteBuffer serverAppReadBuffer = allocateBuffer(serverEngine.getSession().getApplicationBufferSize());
|
||||
ByteBuffer clientAppReadBuffer = allocateBuffer(
|
||||
clientEngine.getSession().getApplicationBufferSize());
|
||||
|
||||
@ -1511,7 +1557,6 @@ public abstract class SSLEngineTest {
|
||||
|
||||
boolean clientHandshakeFinished = false;
|
||||
boolean serverHandshakeFinished = false;
|
||||
|
||||
boolean cTOsHasRemaining;
|
||||
boolean sTOcHasRemaining;
|
||||
|
||||
@ -2937,6 +2982,270 @@ public abstract class SSLEngineTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionCache() throws Exception {
|
||||
clientSslCtx = wrapContext(SslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.sslProvider(sslClientProvider())
|
||||
.sslContextProvider(clientSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.build());
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslServerProvider())
|
||||
.sslContextProvider(serverSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.build());
|
||||
|
||||
try {
|
||||
doHandshakeVerifyReusedAndClose("a.netty.io", 9999, false);
|
||||
doHandshakeVerifyReusedAndClose("a.netty.io", 9999, true);
|
||||
doHandshakeVerifyReusedAndClose("b.netty.io", 9999, false);
|
||||
invalidateSessionsAndAssert(serverSslCtx.sessionContext());
|
||||
invalidateSessionsAndAssert(clientSslCtx.sessionContext());
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
|
||||
Enumeration<byte[]> ids = context.getIds();
|
||||
while (ids.hasMoreElements()) {
|
||||
byte[] id = ids.nextElement();
|
||||
SSLSession session = context.getSession(id);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
assertFalse(session.isValid());
|
||||
assertNull(context.getSession(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSessionCache(SSLSessionContext sessionContext, int numSessions) {
|
||||
Enumeration<byte[]> ids = sessionContext.getIds();
|
||||
int numIds = 0;
|
||||
while (ids.hasMoreElements()) {
|
||||
numIds++;
|
||||
byte[] id = ids.nextElement();
|
||||
assertNotEquals(0, id.length);
|
||||
SSLSession session = sessionContext.getSession(id);
|
||||
assertArrayEquals(id, session.getId());
|
||||
}
|
||||
assertEquals(numSessions, numIds);
|
||||
}
|
||||
|
||||
private void doHandshakeVerifyReusedAndClose(String host, int port, boolean reuse)
|
||||
throws Exception {
|
||||
SSLEngine clientEngine = null;
|
||||
SSLEngine serverEngine = null;
|
||||
try {
|
||||
clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, host, port));
|
||||
serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT));
|
||||
handshake(clientEngine, serverEngine);
|
||||
int clientSessions = currentSessionCacheSize(clientSslCtx.sessionContext());
|
||||
int serverSessions = currentSessionCacheSize(serverSslCtx.sessionContext());
|
||||
int nCSessions = clientSessions;
|
||||
int nSSessions = serverSessions;
|
||||
boolean clientSessionReused = false;
|
||||
boolean serverSessionReused = false;
|
||||
if (protocolCipherCombo == ProtocolCipherCombo.TLSV13) {
|
||||
// Allocate something which is big enough for sure
|
||||
ByteBuffer packetBuffer = allocateBuffer(32 * 1024);
|
||||
ByteBuffer appBuffer = allocateBuffer(32 * 1024);
|
||||
|
||||
appBuffer.clear().position(4).flip();
|
||||
packetBuffer.clear();
|
||||
|
||||
do {
|
||||
SSLEngineResult result;
|
||||
|
||||
do {
|
||||
result = serverEngine.wrap(appBuffer, packetBuffer);
|
||||
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
appBuffer.clear();
|
||||
packetBuffer.flip();
|
||||
do {
|
||||
result = clientEngine.unwrap(packetBuffer, appBuffer);
|
||||
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
packetBuffer.clear();
|
||||
appBuffer.clear().position(4).flip();
|
||||
|
||||
do {
|
||||
result = clientEngine.wrap(appBuffer, packetBuffer);
|
||||
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
appBuffer.clear();
|
||||
packetBuffer.flip();
|
||||
|
||||
do {
|
||||
result = serverEngine.unwrap(packetBuffer, appBuffer);
|
||||
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||
|
||||
packetBuffer.clear();
|
||||
appBuffer.clear().position(4).flip();
|
||||
nCSessions = currentSessionCacheSize(clientSslCtx.sessionContext());
|
||||
nSSessions = currentSessionCacheSize(serverSslCtx.sessionContext());
|
||||
clientSessionReused = isSessionMaybeReused(clientEngine);
|
||||
serverSessionReused = isSessionMaybeReused(serverEngine);
|
||||
} while ((reuse && (!clientSessionReused || !serverSessionReused))
|
||||
|| (!reuse && (nCSessions < clientSessions ||
|
||||
// server may use multiple sessions
|
||||
nSSessions < serverSessions)));
|
||||
}
|
||||
|
||||
assertSessionReusedForEngine(clientEngine, serverEngine, reuse);
|
||||
|
||||
closeOutboundAndInbound(clientEngine, serverEngine);
|
||||
} finally {
|
||||
cleanupClientSslEngine(clientEngine);
|
||||
cleanupServerSslEngine(serverEngine);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSessionMaybeReused(SSLEngine engine) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int currentSessionCacheSize(SSLSessionContext ctx) {
|
||||
Enumeration<byte[]> ids = ctx.getIds();
|
||||
int i = 0;
|
||||
while (ids.hasMoreElements()) {
|
||||
i++;
|
||||
ids.nextElement();
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private void closeOutboundAndInbound(SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException {
|
||||
assertFalse(clientEngine.isInboundDone());
|
||||
assertFalse(clientEngine.isOutboundDone());
|
||||
assertFalse(serverEngine.isInboundDone());
|
||||
assertFalse(serverEngine.isOutboundDone());
|
||||
|
||||
ByteBuffer empty = allocateBuffer(0);
|
||||
|
||||
// Ensure we allocate a bit more so we can fit in multiple packets. This is needed as we may call multiple
|
||||
// time wrap / unwrap in a for loop before we drain the buffer we are writing in.
|
||||
ByteBuffer cTOs = allocateBuffer(clientEngine.getSession().getPacketBufferSize() * 4);
|
||||
ByteBuffer sTOs = allocateBuffer(serverEngine.getSession().getPacketBufferSize() * 4);
|
||||
ByteBuffer cApps = allocateBuffer(clientEngine.getSession().getApplicationBufferSize() * 4);
|
||||
ByteBuffer sApps = allocateBuffer(serverEngine.getSession().getApplicationBufferSize() * 4);
|
||||
|
||||
clientEngine.closeOutbound();
|
||||
for (;;) {
|
||||
// call wrap till we produced all data
|
||||
SSLEngineResult result = clientEngine.wrap(empty, cTOs);
|
||||
if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) {
|
||||
break;
|
||||
}
|
||||
assertTrue(cTOs.hasRemaining());
|
||||
}
|
||||
cTOs.flip();
|
||||
|
||||
for (;;) {
|
||||
// call unwrap till we consumed all data
|
||||
SSLEngineResult result = serverEngine.unwrap(cTOs, sApps);
|
||||
if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) {
|
||||
break;
|
||||
}
|
||||
assertTrue(sApps.hasRemaining());
|
||||
}
|
||||
|
||||
serverEngine.closeOutbound();
|
||||
for (;;) {
|
||||
// call wrap till we produced all data
|
||||
SSLEngineResult result = serverEngine.wrap(empty, sTOs);
|
||||
if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) {
|
||||
break;
|
||||
}
|
||||
assertTrue(sTOs.hasRemaining());
|
||||
}
|
||||
sTOs.flip();
|
||||
|
||||
for (;;) {
|
||||
// call unwrap till we consumed all data
|
||||
SSLEngineResult result = clientEngine.unwrap(sTOs, cApps);
|
||||
if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) {
|
||||
break;
|
||||
}
|
||||
assertTrue(cApps.hasRemaining());
|
||||
}
|
||||
|
||||
// Now close the inbound as well
|
||||
clientEngine.closeInbound();
|
||||
serverEngine.closeInbound();
|
||||
}
|
||||
|
||||
protected void assertSessionReusedForEngine(SSLEngine clientEngine, SSLEngine serverEngine, boolean reuse) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionCacheTimeout() throws Exception {
|
||||
clientSslCtx = wrapContext(SslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.sslProvider(sslClientProvider())
|
||||
.sslContextProvider(clientSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.sessionTimeout(1)
|
||||
.build());
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslServerProvider())
|
||||
.sslContextProvider(serverSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.sessionTimeout(1)
|
||||
.build());
|
||||
|
||||
try {
|
||||
doHandshakeVerifyReusedAndClose("a.netty.io", 9999, false);
|
||||
|
||||
// Let's sleep for a bit more then 1 second so the cache should timeout the sessions.
|
||||
Thread.sleep(1500);
|
||||
|
||||
assertSessionCache(serverSslCtx.sessionContext(), 0);
|
||||
assertSessionCache(clientSslCtx.sessionContext(), 0);
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionCacheSize() throws Exception {
|
||||
clientSslCtx = wrapContext(SslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.sslProvider(sslClientProvider())
|
||||
.sslContextProvider(clientSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.sessionCacheSize(1)
|
||||
.build());
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslServerProvider())
|
||||
.sslContextProvider(serverSslContextProvider())
|
||||
.protocols(protocols())
|
||||
.ciphers(ciphers())
|
||||
.build());
|
||||
|
||||
try {
|
||||
doHandshakeVerifyReusedAndClose("a.netty.io", 9999, false);
|
||||
// As we have a cache size of 1 we should never have more then one session in the cache
|
||||
doHandshakeVerifyReusedAndClose("b.netty.io", 9999, false);
|
||||
|
||||
// We should at least reuse b.netty.io
|
||||
doHandshakeVerifyReusedAndClose("b.netty.io", 9999, true);
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionBindingEvent() throws Exception {
|
||||
clientSslCtx = wrapContext(SslContextBuilder.forClient()
|
||||
@ -3550,7 +3859,7 @@ public abstract class SSLEngineTest {
|
||||
final Promise<SecretKey> promise = sb.config().group().next().newPromise();
|
||||
serverChannel = sb.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
protected void initChannel(Channel ch) {
|
||||
ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type));
|
||||
|
||||
SslHandler sslHandler = delegatingExecutor == null ?
|
||||
@ -3608,7 +3917,7 @@ public abstract class SSLEngineTest {
|
||||
new java.security.cert.X509Certificate[] { ssc.cert() }, null, ssc.key(), null, null, null);
|
||||
}
|
||||
|
||||
private final class TestTrustManagerFactory extends X509ExtendedTrustManager {
|
||||
private static final class TestTrustManagerFactory extends X509ExtendedTrustManager {
|
||||
private final Certificate localCert;
|
||||
private volatile boolean verified;
|
||||
|
||||
|
@ -1118,6 +1118,10 @@ public class SslHandlerTest {
|
||||
.protocols(protocol)
|
||||
.build();
|
||||
|
||||
// Explicit enable session cache as it's disabled by default atm.
|
||||
((OpenSslContext) sslClientCtx).sessionContext()
|
||||
.setSessionCacheEnabled(true);
|
||||
|
||||
final SelfSignedCertificate cert = new SelfSignedCertificate();
|
||||
final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert())
|
||||
.sslProvider(provider)
|
||||
@ -1136,25 +1140,41 @@ public class SslHandlerTest {
|
||||
|
||||
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
||||
Channel sc = null;
|
||||
Channel cc = null;
|
||||
final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT);
|
||||
final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT);
|
||||
|
||||
final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
|
||||
final byte[] bytes = new byte[96];
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
try {
|
||||
final AtomicReference<AssertionError> assertErrorRef = new AtomicReference<AssertionError>();
|
||||
sc = new ServerBootstrap()
|
||||
.group(group)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) {
|
||||
ch.pipeline().addLast(serverSslHandler);
|
||||
final SslHandler sslHandler = sslServerCtx.newHandler(ch.alloc());
|
||||
ch.pipeline().addLast(sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT));
|
||||
ch.pipeline().addLast(new ChannelHandler() {
|
||||
|
||||
private int handshakeCount;
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||
handshakeCount++;
|
||||
ReferenceCountedOpenSslEngine engine =
|
||||
(ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
// This test only works for non TLSv1.3 as TLSv1.3 will establish sessions after
|
||||
// the handshake is done.
|
||||
// See https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_sess_set_get_cb.html
|
||||
if (!SslUtils.PROTOCOL_TLS_V1_3.equals(engine.getSession().getProtocol())) {
|
||||
// First should not re-use the session
|
||||
try {
|
||||
assertEquals(handshakeCount > 1, engine.isSessionReused());
|
||||
} catch (AssertionError error) {
|
||||
assertErrorRef.set(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer(bytes));
|
||||
}
|
||||
}
|
||||
@ -1163,6 +1183,31 @@ public class SslHandlerTest {
|
||||
})
|
||||
.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
|
||||
|
||||
InetSocketAddress serverAddr = (InetSocketAddress) sc.localAddress();
|
||||
testSessionTickets(serverAddr, group, sslClientCtx, bytes, false);
|
||||
testSessionTickets(serverAddr, group, sslClientCtx, bytes, true);
|
||||
AssertionError error = assertErrorRef.get();
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
if (sc != null) {
|
||||
sc.close().syncUninterruptibly();
|
||||
}
|
||||
group.shutdownGracefully();
|
||||
ReferenceCountUtil.release(sslClientCtx);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testSessionTickets(InetSocketAddress serverAddress, EventLoopGroup group,
|
||||
SslContext sslClientCtx, final byte[] bytes, boolean isReused)
|
||||
throws Throwable {
|
||||
Channel cc = null;
|
||||
final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
|
||||
try {
|
||||
final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT,
|
||||
serverAddress.getAddress().getHostAddress(), serverAddress.getPort());
|
||||
|
||||
ChannelFuture future = new Bootstrap()
|
||||
.group(group)
|
||||
.channel(NioSocketChannel.class)
|
||||
@ -1185,11 +1230,18 @@ public class SslHandlerTest {
|
||||
}
|
||||
});
|
||||
}
|
||||
}).connect(sc.localAddress());
|
||||
}).connect(serverAddress);
|
||||
cc = future.syncUninterruptibly().channel();
|
||||
|
||||
assertTrue(clientSslHandler.handshakeFuture().await().isSuccess());
|
||||
assertTrue(serverSslHandler.handshakeFuture().await().isSuccess());
|
||||
assertTrue(clientSslHandler.handshakeFuture().sync().isSuccess());
|
||||
|
||||
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) clientSslHandler.engine();
|
||||
// This test only works for non TLSv1.3 as TLSv1.3 will establish sessions after
|
||||
// the handshake is done.
|
||||
// See https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_sess_set_get_cb.html
|
||||
if (!SslUtils.PROTOCOL_TLS_V1_3.equals(engine.getSession().getProtocol())) {
|
||||
assertEquals(isReused, engine.isSessionReused());
|
||||
}
|
||||
Object obj = queue.take();
|
||||
if (obj instanceof ByteBuf) {
|
||||
ByteBuf buffer = (ByteBuf) obj;
|
||||
@ -1207,11 +1259,6 @@ public class SslHandlerTest {
|
||||
if (cc != null) {
|
||||
cc.close().syncUninterruptibly();
|
||||
}
|
||||
if (sc != null) {
|
||||
sc.close().syncUninterruptibly();
|
||||
}
|
||||
group.shutdownGracefully();
|
||||
ReferenceCountUtil.release(sslClientCtx);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user