Revert "Support session cache for client and server when using native SSLEngine implementation (#10331)" (#10528)

Motivation:

This reverts commit 825916c7f0 as it turns out it introduced a big performance regression.

Modifications:

Revert 825916c7f0

Result:

Performance of TLS is back to normal
This commit is contained in:
Norman Maurer 2020-09-03 08:31:57 +02:00 committed by GitHub
parent fb39fc450e
commit 5157d3b8e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 694 additions and 2053 deletions

View File

@ -1,349 +0,0 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.internal.tcnative.SSLSession;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.ReferenceCounted;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;
final class DefaultOpenSslSession extends AbstractReferenceCounted implements ReferenceCounted, OpenSslSession {
private final OpenSslSessionContext sessionContext;
private final String peerHost;
private final int peerPort;
private final OpenSslSessionId id;
private final X509Certificate[] x509PeerCerts;
private final Certificate[] peerCerts;
private final String protocol;
private final String cipher;
private final long sslSession;
private final long creationTime;
private final long timeout;
private volatile int applicationBufferSize = ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH;
private volatile int packetBufferSize = ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE;
private volatile long lastAccessed;
private volatile Certificate[] localCertificateChain;
private volatile boolean invalid;
// Guarded by synchronized(this)
// lazy init for memory reasons
private Map<String, Object> values;
DefaultOpenSslSession(OpenSslSessionContext sessionContext, String peerHost, int peerPort, long sslSession,
String version,
String cipher,
OpenSslJavaxX509Certificate[] x509PeerCerts,
long creationTime, long timeout) {
this.sessionContext = sessionContext;
this.peerHost = peerHost;
this.peerPort = peerPort;
this.id = new OpenSslSessionId(id(sslSession));
this.sslSession = sslSession;
this.cipher = cipher == null ? SslUtils.INVALID_CIPHER : cipher;
this.x509PeerCerts = x509PeerCerts;
if (x509PeerCerts != null) {
peerCerts = new Certificate[x509PeerCerts.length];
for (int i = 0; i < peerCerts.length; i++) {
peerCerts[i] = new OpenSslX509Certificate(x509PeerCerts[i].getBytes());
}
} else {
peerCerts = null;
}
this.protocol = version == null ? StringUtil.EMPTY_STRING : version;
this.creationTime = creationTime;
this.lastAccessed = creationTime;
this.timeout = timeout;
}
private static byte[] id(long sslSession) {
if (sslSession == -1) {
return EmptyArrays.EMPTY_BYTES;
}
byte[] id = io.netty.internal.tcnative.SSLSession.getSessionId(sslSession);
return id == null ? EmptyArrays.EMPTY_BYTES : id;
}
@Override
public OpenSslSessionContext getSessionContext() {
return sessionContext;
}
@Override
public void putValue(String name, Object value) {
ObjectUtil.checkNotNull(name, "name");
ObjectUtil.checkNotNull(value, "value");
final Object old;
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null) {
// Use size of 2 to keep the memory overhead small
values = this.values = new HashMap<String, Object>(2);
}
old = values.put(name, value);
}
if (value instanceof SSLSessionBindingListener) {
((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
}
notifyUnbound(old, name);
}
@Override
public Object getValue(String name) {
ObjectUtil.checkNotNull(name, "name");
synchronized (this) {
if (values == null) {
return null;
}
return values.get(name);
}
}
@Override
public void removeValue(String name) {
ObjectUtil.checkNotNull(name, "name");
final Object old;
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null) {
return;
}
old = values.remove(name);
}
notifyUnbound(old, name);
}
@Override
public String[] getValueNames() {
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null || values.isEmpty()) {
return EmptyArrays.EMPTY_STRINGS;
}
return values.keySet().toArray(EmptyArrays.EMPTY_STRINGS);
}
}
private void notifyUnbound(Object value, String name) {
if (value instanceof SSLSessionBindingListener) {
((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name));
}
}
@Override
public String getPeerHost() {
return peerHost;
}
@Override
public int getPeerPort() {
return peerPort;
}
@Override
public OpenSslSessionId sessionId() {
return id;
}
@Override
public byte[] getId() {
return sessionId().cloneBytes();
}
@Override
public int hashCode() {
return sessionId().hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof OpenSslSession)) {
return false;
}
return sessionId().equals(((OpenSslSession) obj).sessionId());
}
@Override
public boolean isNullSession() {
return false;
}
@Override
public long getCreationTime() {
return creationTime;
}
@Override
public long getLastAccessedTime() {
return lastAccessed;
}
@Override
public void invalidate() {
invalid = true;
}
@Override
public boolean isValid() {
if (sslSession == -1 || invalid) {
return false;
}
// Never timeout
if (timeout == 0) {
return true;
}
long current = System.currentTimeMillis();
return current - timeout < creationTime;
}
@Override
public long nativeAddr() {
return sslSession;
}
@Override
public void setLocalCertificate(Certificate[] localCertificate) {
this.localCertificateChain = localCertificate;
}
@Override
public void setPacketBufferSize(int packetBufferSize) {
this.packetBufferSize = packetBufferSize;
}
@Override
public void updateLastAccessedTime() {
lastAccessed = System.currentTimeMillis();
}
@Override
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
if (SslUtils.isEmpty(peerCerts)) {
throw new SSLPeerUnverifiedException("peer not verified");
}
return peerCerts.clone();
}
@Override
public Certificate[] getLocalCertificates() {
Certificate[] localCerts = localCertificateChain;
if (localCerts == null) {
return null;
}
return localCerts.clone();
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
if (SslUtils.isEmpty(x509PeerCerts)) {
throw new SSLPeerUnverifiedException("peer not verified");
}
return x509PeerCerts.clone();
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] peer = getPeerCertificates();
// No need for null or length > 0 is needed as this is done in getPeerCertificates()
// already.
return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
}
@Override
public Principal getLocalPrincipal() {
Certificate[] local = localCertificateChain;
if (SslUtils.isEmpty(local)) {
return null;
}
return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
}
@Override
public String getCipherSuite() {
return cipher;
}
@Override
public String getProtocol() {
return protocol;
}
@Override
public int getPacketBufferSize() {
return packetBufferSize;
}
@Override
public int getApplicationBufferSize() {
return applicationBufferSize;
}
@Override
public void tryExpandApplicationBufferSize(int packetLengthDataOnly) {
if (packetLengthDataOnly > ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH &&
applicationBufferSize != ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE) {
applicationBufferSize = ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE;
}
}
@Override
protected void deallocate() {
if (sslSession != -1) {
SSLSession.free(sslSession);
}
}
@Override
public DefaultOpenSslSession touch() {
super.touch();
return this;
}
@Override
public DefaultOpenSslSession touch(Object hint) {
return this;
}
@Override
public DefaultOpenSslSession retain() {
super.retain();
return this;
}
@Override
public DefaultOpenSslSession retain(int increment) {
super.retain(increment);
return this;
}
}

View File

@ -15,13 +15,12 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.SuppressJava6Requirement; import io.netty.util.internal.SuppressJava6Requirement;
import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSessionBindingListener;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import java.security.Principal; import java.security.Principal;
import java.security.cert.Certificate; import java.security.cert.Certificate;
@ -62,38 +61,8 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
} }
@Override @Override
public OpenSslSessionId sessionId() { public final void handshakeFinished() throws SSLException {
return wrapped.sessionId(); wrapped.handshakeFinished();
}
@Override
public final boolean isNullSession() {
return wrapped.isNullSession();
}
@Override
public final void setLocalCertificate(Certificate[] localCertificate) {
wrapped.setLocalCertificate(localCertificate);
}
@Override
public final void setPacketBufferSize(int packetBufferSize) {
wrapped.setPacketBufferSize(packetBufferSize);
}
@Override
public final long nativeAddr() {
return wrapped.nativeAddr();
}
@Override
public final void updateLastAccessedTime() {
wrapped.updateLastAccessedTime();
}
@Override
public String[] getPeerSupportedSignatureAlgorithms() {
return EmptyArrays.EMPTY_STRINGS;
} }
@Override @Override
@ -112,7 +81,7 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
} }
@Override @Override
public final OpenSslSessionContext getSessionContext() { public final SSLSessionContext getSessionContext() {
return wrapped.getSessionContext(); return wrapped.getSessionContext();
} }
@ -137,22 +106,13 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
} }
@Override @Override
public final void putValue(String name, Object value) { public final void putValue(String s, Object o) {
if (value instanceof SSLSessionBindingListener) { wrapped.putValue(s, o);
// Decorate the value if needed so we submit the correct SSLSession instance
value = new SSLSessionBindingListenerDecorator((SSLSessionBindingListener) value);
}
wrapped.putValue(name, value);
} }
@Override @Override
public final Object getValue(String s) { public final Object getValue(String s) {
Object value = wrapped.getValue(s); return wrapped.getValue(s);
if (value instanceof SSLSessionBindingListenerDecorator) {
// Unwrap as needed so we return the original value
return ((SSLSessionBindingListenerDecorator) value).delegate;
}
return value;
} }
@Override @Override
@ -219,62 +179,4 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
public final int getApplicationBufferSize() { public final int getApplicationBufferSize() {
return wrapped.getApplicationBufferSize(); return wrapped.getApplicationBufferSize();
} }
@Override
public ExtendedOpenSslSession retain() {
wrapped.retain();
return this;
}
@Override
public ExtendedOpenSslSession retain(int increment) {
wrapped.retain(increment);
return this;
}
@Override
public ExtendedOpenSslSession touch() {
wrapped.touch();
return this;
}
@Override
public ExtendedOpenSslSession touch(Object hint) {
wrapped.touch(hint);
return this;
}
@Override
public int refCnt() {
return wrapped.refCnt();
}
@Override
public boolean release() {
return wrapped.release();
}
@Override
public boolean release(int decrement) {
return wrapped.release(decrement);
}
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()));
}
}
} }

View File

@ -316,6 +316,16 @@ public class JdkSslContext extends SslContext {
return unmodifiableCipherSuites; return unmodifiableCipherSuites;
} }
@Override
public final long sessionCacheSize() {
return sessionContext().getSessionCacheSize();
}
@Override
public final long sessionTimeout() {
return sessionContext().getSessionTimeout();
}
@Override @Override
public final SSLEngine newEngine(ByteBufAllocator alloc) { public final SSLEngine newEngine(ByteBufAllocator alloc) {
return configureAndWrapEngine(context().createSSLEngine(), alloc); return configureAndWrapEngine(context().createSSLEngine(), alloc);

View File

@ -186,14 +186,13 @@ public final class OpenSslClientContext extends OpenSslContext {
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStore) long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStore)
throws SSLException { throws SSLException {
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain, super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
ClientAuth.NONE, protocols, false, enableOcsp); ClientAuth.NONE, protocols, false, enableOcsp);
boolean success = false; boolean success = false;
try { try {
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword); OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore, keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
sessionCacheSize, sessionTimeout);
success = true; success = true;
} finally { } finally {
if (!success) { if (!success) {

View File

@ -1,147 +0,0 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.util.AsciiString;
import javax.net.ssl.SSLException;
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, OpenSslSession> sessions = new HashMap<HostPort, OpenSslSession>();
OpenSslClientSessionCache(OpenSslEngineMap engineMap) {
super(engineMap);
}
@Override
protected boolean sessionCreated(OpenSslSession session) {
assert Thread.holdsLock(this);
String host = session.getPeerHost();
int port = session.getPeerPort();
if (host == null || port == -1) {
return false;
}
HostPort hostPort = new HostPort(host, port);
if (sessions.containsKey(hostPort)) {
return false;
}
sessions.put(hostPort, session);
return true;
}
@Override
protected void sessionRemoved(OpenSslSession session) {
assert Thread.holdsLock(this);
String host = session.getPeerHost();
int port = session.getPeerPort();
if (host == null || port == -1) {
return;
}
sessions.remove(new HostPort(host, port));
}
private static boolean isProtocolEnabled(OpenSslSession session, String[] enabledProtocols) {
return arrayContains(session.getProtocol(), enabledProtocols);
}
private static boolean isCipherSuiteEnabled(OpenSslSession session, String[] enabledCipherSuites) {
return arrayContains(session.getCipherSuite(), enabledCipherSuites);
}
private static boolean arrayContains(String expected, String[] array) {
for (int i = 0; i < array.length; ++i) {
String value = array[i];
if (value.equals(expected)) {
return true;
}
}
return false;
}
void setSession(ReferenceCountedOpenSslEngine engine) throws SSLException {
String host = engine.getPeerHost();
int port = engine.getPeerPort();
if (host == null || port == -1) {
return;
}
HostPort hostPort = new HostPort(host, port);
synchronized (this) {
OpenSslSession session = sessions.get(hostPort);
if (session == null) {
return;
}
if (!session.isValid()) {
removeSession(session);
return;
}
// Ensure the protocol and ciphersuite can be used.
if (!isProtocolEnabled(session, engine.getEnabledProtocols()) ||
!isCipherSuiteEnabled(session, engine.getEnabledCipherSuites())) {
return;
}
// Try to set the session, if true is returned we retained the session and incremented the reference count
// of the underlying SSL_SESSION*.
if (engine.setSession(session)) {
session.updateLastAccessedTime();
if (io.netty.internal.tcnative.SSLSession.shouldBeSingleUse(session.nativeAddr())) {
// Should only be re-used once so remove it from the cache
removeSession(session);
}
}
}
}
/**
* 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);
}
}
}

View File

@ -28,18 +28,19 @@ import javax.net.ssl.SSLException;
*/ */
public abstract class OpenSslContext extends ReferenceCountedOpenSslContext { public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg, OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg,
int mode, Certificate[] keyCertChain, long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp) ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp)
throws SSLException { throws SSLException {
super(ciphers, cipherFilter, apnCfg, mode, keyCertChain, super(ciphers, cipherFilter, apnCfg, sessionCacheSize, sessionTimeout, mode, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, false); clientAuth, protocols, startTls, enableOcsp, false);
} }
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls, ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp) throws SSLException { boolean enableOcsp) throws SSLException {
super(ciphers, cipherFilter, apn, mode, keyCertChain, clientAuth, protocols, super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols,
startTls, enableOcsp, false); startTls, enableOcsp, false);
} }

View File

@ -96,14 +96,6 @@ final class OpenSslJavaxX509Certificate extends X509Certificate {
return bytes.clone(); 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 @Override
public void verify(PublicKey key) public void verify(PublicKey key)
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,

View File

@ -1,218 +0,0 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.util.internal.EmptyArrays;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
/**
* Special {@link OpenSslSession} which represent a {@code NULL} session. This will be used prior the handshake is
* started.
*/
final class OpenSslNullSession implements OpenSslSession {
private final OpenSslSessionContext sessionContext;
private final Certificate[] localCertificate;
// The given array will never be mutated and so its ok to not clone it.
OpenSslNullSession(OpenSslSessionContext sessionContext, Certificate[] localCertificate) {
this.sessionContext = sessionContext;
this.localCertificate = localCertificate;
}
@Override
public OpenSslSessionId sessionId() {
return OpenSslSessionId.NULL_ID;
}
@Override
public OpenSslSessionContext getSessionContext() {
return sessionContext;
}
@Override
public byte[] getId() {
return sessionId().cloneBytes();
}
@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 String getPeerHost() {
return null;
}
@Override
public int getPeerPort() {
return -1;
}
@Override
public boolean isNullSession() {
return true;
}
@Override
public long nativeAddr() {
return -1;
}
@Override
public void setLocalCertificate(Certificate[] localCertificate) {
throw new UnsupportedOperationException();
}
@Override
public void tryExpandApplicationBufferSize(int packetLengthDataOnly) {
// NOOP
}
@Override
public void setPacketBufferSize(int packetBufferSize) {
// NOOP
}
@Override
public void updateLastAccessedTime() {
// NOOP
}
@Override
public long getCreationTime() {
return 0;
}
@Override
public long getLastAccessedTime() {
return 0;
}
@Override
public void invalidate() {
// NOOP
}
@Override
public boolean isValid() {
return false;
}
@Override
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
throw new SSLPeerUnverifiedException("NULL session");
}
@Override
public Certificate[] getLocalCertificates() {
return localCertificate == null ? null : localCertificate.clone();
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
throw new SSLPeerUnverifiedException("NULL session");
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
throw new SSLPeerUnverifiedException("NULL session");
}
@Override
public Principal getLocalPrincipal() {
Certificate[] local = getLocalCertificates();
if (SslUtils.isEmpty(local)) {
return null;
}
return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
}
@Override
public String getCipherSuite() {
return SslUtils.INVALID_CIPHER;
}
@Override
public String getProtocol() {
return "NONE";
}
@Override
public int getPacketBufferSize() {
return ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE;
}
@Override
public int getApplicationBufferSize() {
return ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH;
}
@Override
public OpenSslSession retain() {
return this;
}
@Override
public OpenSslSession retain(int increment) {
return this;
}
@Override
public OpenSslSession touch() {
return this;
}
@Override
public OpenSslSession touch(Object hint) {
return this;
}
@Override
public int refCnt() {
return 1;
}
@Override
public boolean release() {
return false;
}
@Override
public boolean release(int decrement) {
return false;
}
}

View File

@ -343,7 +343,7 @@ public final class OpenSslServerContext extends OpenSslContext {
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp, String keyStore) throws SSLException { boolean enableOcsp, String keyStore) throws SSLException {
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain, super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
clientAuth, protocols, startTls, enableOcsp); clientAuth, protocols, startTls, enableOcsp);
// Create a new SSL_CTX and configure it. // Create a new SSL_CTX and configure it.
@ -351,8 +351,7 @@ public final class OpenSslServerContext extends OpenSslContext {
try { try {
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword); OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore, keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
sessionCacheSize, sessionTimeout);
success = true; success = true;
} finally { } finally {
if (!success) { if (!success) {

View File

@ -26,7 +26,81 @@ import java.util.concurrent.locks.Lock;
*/ */
public final class OpenSslServerSessionContext extends OpenSslSessionContext { public final class OpenSslServerSessionContext extends OpenSslSessionContext {
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) { OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
super(context, provider, SSL.SSL_SESS_CACHE_SERVER, new OpenSslSessionCache(context.engineMap)); 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();
}
} }
/** /**

View File

@ -15,41 +15,16 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
import io.netty.util.ReferenceCounted; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
/** interface OpenSslSession extends SSLSession {
* {@link SSLSession} that is specific to our native implementation and {@link ReferenceCounted} to track native
* resources.
*/
interface OpenSslSession extends SSLSession, ReferenceCounted {
/** /**
* Return the {@link OpenSslSessionId} that can be used to identify this session. * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by
* the user.
*/ */
OpenSslSessionId sessionId(); void handshakeFinished() throws SSLException;
/**
* Returns {@code true} if this is a {@code NULL} session and so should be replaced once the
* handshake is in progress / finished, {@code false} otherwise.
*/
boolean isNullSession();
/**
* Returns the native address (pointer) to {@code SSL_SESSION*} if any. Otherwise returns {@code -1}.
*/
long nativeAddr();
/**
* 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);
@Override
OpenSslSessionContext getSessionContext();
/** /**
* Expand (or increase) the value returned by {@link #getApplicationBufferSize()} if necessary. * Expand (or increase) the value returned by {@link #getApplicationBufferSize()} if necessary.
@ -58,26 +33,4 @@ interface OpenSslSession extends SSLSession, ReferenceCounted {
* @param packetLengthDataOnly The packet size which exceeds the current {@link #getApplicationBufferSize()}. * @param packetLengthDataOnly The packet size which exceeds the current {@link #getApplicationBufferSize()}.
*/ */
void tryExpandApplicationBufferSize(int packetLengthDataOnly); void tryExpandApplicationBufferSize(int packetLengthDataOnly);
/**
* Set the buffer size that is needed as a minimum to encrypt data.
*/
void setPacketBufferSize(int packetBufferSize);
/**
* Update the last accessed time to the current {@link System#currentTimeMillis()}.
*/
void updateLastAccessedTime();
@Override
OpenSslSession retain();
@Override
OpenSslSession retain(int increment);
@Override
OpenSslSession touch();
@Override
OpenSslSession touch(Object hint);
} }

View File

@ -1,250 +0,0 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.internal.tcnative.SSLSessionCache;
import io.netty.util.internal.SystemPropertyUtil;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
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 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, OpenSslSession> sessions =
new LinkedHashMap<OpenSslSessionId, OpenSslSession>() {
private static final long serialVersionUID = -7773696788135734448L;
@Override
protected boolean removeEldestEntry(Map.Entry<OpenSslSessionId, OpenSslSession> eldest) {
int maxSize = maximumCacheSize.get();
if (maxSize >= 0 && this.size() > maxSize) {
OpenSslSession session = eldest.getValue();
sessionRemoved(session);
session.release();
return true;
}
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 volatile int sessionTimeout = 300;
private int sessionCounter;
OpenSslSessionCache(OpenSslEngineMap engineMap) {
this.engineMap = engineMap;
}
void setSessionTimeout(int seconds) {
sessionTimeout = seconds;
}
int getSessionTimeout() {
return sessionTimeout;
}
/**
* 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(OpenSslSession session) {
return true;
}
/**
* Called once an {@link OpenSslSession} was removed from the cache.
*
* @param session the session to remove.
*/
protected void sessionRemoved(OpenSslSession session) { }
final void setSessionCacheSize(int size) {
long oldSize = maximumCacheSize.getAndSet(size);
if (oldSize > size) {
// Just keep it simple for now and drain the whole cache.
freeSessions();
}
}
final int getSessionCacheSize() {
return maximumCacheSize.get();
}
DefaultOpenSslSession newOpenSslSession(long sslSession, OpenSslSessionContext context, String peerHost,
int peerPort, String protocol, String cipher,
OpenSslJavaxX509Certificate[] peerCertificateChain,
long creationTime) {
if (sslSession != -1) {
synchronized (this) {
if (!io.netty.internal.tcnative.SSLSession.upRef(sslSession)) {
throw new IllegalStateException("Unable to update reference count of SSL_SESSION*");
}
}
}
return new DefaultOpenSslSession(context, peerHost, peerPort, sslSession, protocol, cipher,
peerCertificateChain, creationTime, context.getSessionTimeout() * 1000L);
}
private void expungeInvalidSessions() {
Iterator<Map.Entry<OpenSslSessionId, OpenSslSession>> iterator = sessions.entrySet().iterator();
while (iterator.hasNext()) {
OpenSslSession session = iterator.next().getValue();
if (!session.isValid()) {
iterator.remove();
sessionRemoved(session);
session.release();
}
}
}
@Override
public final boolean sessionCreated(long ssl, long sslSession) {
ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
if (engine == null) {
return false;
}
synchronized (this) {
// Mimic what OpenSSL is duing 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();
}
final OpenSslSession session;
try {
session = engine.sessionCreated(sslSession);
} catch (SSLException e) {
// TODO: Should we log this ?
return false;
}
if (!sessionCreated(session)) {
return false;
}
final OpenSslSession old = sessions.put(session.sessionId(), session.retain());
if (old != null) {
sessionRemoved(old);
old.release();
}
}
return true;
}
@Override
public final long getSession(long ssl, byte[] sessionId) {
OpenSslSessionId id = new OpenSslSessionId(sessionId);
synchronized (this) {
OpenSslSession session = sessions.get(id);
if (session == null) {
return -1;
}
long nativeAddr = session.nativeAddr();
if (nativeAddr == -1 || !session.isValid()) {
removeSession(session);
return -1;
}
// This needs to happen in the synchronized block so we ensure we never destroy it before we incremented
// the reference count.
if (!io.netty.internal.tcnative.SSLSession.upRef(nativeAddr)) {
// we could not increment the reference count, something is wrong. Let's just drop the session.
removeSession(session);
return -1;
}
session.retain();
if (io.netty.internal.tcnative.SSLSession.shouldBeSingleUse(nativeAddr)) {
// Should only be used once
removeSession(session);
}
session.updateLastAccessedTime();
return nativeAddr;
}
}
protected void removeSession(OpenSslSession session) {
sessions.remove(session.sessionId());
sessionRemoved(session);
session.release();
}
final SSLSession getSession(byte[] bytes) {
OpenSslSessionId id = new OpenSslSessionId(bytes);
synchronized (this) {
OpenSslSession session = sessions.get(id);
if (session == null) {
return null;
}
if (!session.isValid()) {
removeSession(session);
return null;
}
return session;
}
}
final synchronized List<byte[]> getIds() {
List<byte[]> ids = new ArrayList<byte[]>(sessions.size());
for (OpenSslSession session: sessions.values()) {
if (session.isValid()) {
ids.add(session.getId());
}
}
return ids;
}
final synchronized void freeSessions() {
final OpenSslSession[] sessionsArray = sessions.values().toArray(new OpenSslSession[0]);
sessions.clear();
for (OpenSslSession session: sessionsArray) {
sessionRemoved(session);
session.release();
}
}
}

View File

@ -23,14 +23,15 @@ import io.netty.util.internal.ObjectUtil;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSessionContext;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
/** /**
* OpenSSL specific {@link SSLSessionContext} implementation. * OpenSSL specific {@link SSLSessionContext} implementation.
*/ */
public abstract class OpenSslSessionContext implements SSLSessionContext { public abstract class OpenSslSessionContext implements SSLSessionContext {
private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
private final OpenSslSessionStats stats; private final OpenSslSessionStats stats;
@ -40,73 +41,30 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
private final OpenSslKeyMaterialProvider provider; private final OpenSslKeyMaterialProvider provider;
final ReferenceCountedOpenSslContext context; final ReferenceCountedOpenSslContext context;
final OpenSslNullSession nullSession;
final OpenSslSessionCache sessionCache;
private final long mask;
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent // 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 // 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 // segfault when the user calls any of the methods here that try to pass the pointer down to the native
// level. // level.
OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider, long mask, OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
OpenSslSessionCache cache) {
this.context = context; this.context = context;
this.provider = provider; this.provider = provider;
this.mask = mask;
stats = new OpenSslSessionStats(context); stats = new OpenSslSessionStats(context);
sessionCache = cache;
// If we do not use the KeyManagerFactory we need to set localCertificateChain now.
// When we use a KeyManagerFactory it will be set during setKeyMaterial(...).
nullSession = new OpenSslNullSession(this, provider == null ? context.keyCertChain : null);
SSLContext.setSSLSessionCache(context.ctx, cache);
} }
final DefaultOpenSslSession newOpenSslSession(long sslSession, String peerHost, final boolean useKeyManager() {
int peerPort, String protocol, String cipher, return provider != null;
OpenSslJavaxX509Certificate[] peerCertificateChain, long creationTime) {
return sessionCache.newOpenSslSession(sslSession, this, peerHost, peerPort, protocol, cipher,
peerCertificateChain, creationTime);
}
@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 @Override
public SSLSession getSession(byte[] bytes) { public SSLSession getSession(byte[] bytes) {
return sessionCache.getSession(bytes); ObjectUtil.checkNotNull(bytes, "bytes");
return null;
} }
@Override @Override
public Enumeration<byte[]> getIds() { public Enumeration<byte[]> getIds() {
return Collections.enumeration(sessionCache.getIds()); return EMPTY;
} }
/** /**
@ -166,33 +124,12 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
/** /**
* Enable or disable caching of SSL sessions. * Enable or disable caching of SSL sessions.
*/ */
public void setSessionCacheEnabled(boolean enabled) { public abstract 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.freeSessions();
}
} finally {
writerLock.unlock();
}
}
/** /**
* Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise. * Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise.
*/ */
public boolean isSessionCacheEnabled() { public abstract 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. * Returns the stats of this context.
@ -205,6 +142,17 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
if (provider != null) { if (provider != null) {
provider.destroy(); provider.destroy();
} }
sessionCache.freeSessions(); }
private static final class EmptyEnumeration implements Enumeration<byte[]> {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public byte[] nextElement() {
throw new NoSuchElementException();
}
} }
} }

View File

@ -1,59 +0,0 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.util.internal.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 int hashCode() {
return hashCode;
}
byte[] cloneBytes() {
return id.clone();
}
}

View File

@ -67,13 +67,12 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
String[] protocols, long sessionCacheSize, long sessionTimeout, String[] protocols, long sessionCacheSize, long sessionTimeout,
boolean enableOcsp, String keyStore) throws SSLException { boolean enableOcsp, String keyStore) throws SSLException {
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain, super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
ClientAuth.NONE, protocols, false, enableOcsp, true); ClientAuth.NONE, protocols, false, enableOcsp, true);
boolean success = false; boolean success = false;
try { try {
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore, keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
sessionCacheSize, sessionTimeout);
success = true; success = true;
} finally { } finally {
if (!success) { if (!success) {
@ -93,8 +92,7 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
TrustManagerFactory trustManagerFactory, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, X509Certificate[] keyCertChain, PrivateKey key,
String keyPassword, KeyManagerFactory keyManagerFactory, String keyPassword, KeyManagerFactory keyManagerFactory,
String keyStore, long sessionCacheSize, long sessionTimeout) String keyStore) throws SSLException {
throws SSLException {
if (key == null && keyCertChain != null || key != null && keyCertChain == null) { if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Either both keyCertChain and key needs to be null or none of them"); "Either both keyCertChain and key needs to be null or none of them");
@ -169,20 +167,9 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
throw new SSLException("unable to setup trustmanager", e); throw new SSLException("unable to setup trustmanager", e);
} }
OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider); OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider);
// Enable session caching by default
context.setSessionCacheEnabled(true);
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 (ENABLE_SESSION_TICKET) { if (ENABLE_SESSION_TICKET) {
context.setTicketKeys(); context.setTicketKeys();
} }
keyMaterialProvider = null; keyMaterialProvider = null;
return context; return context;
} finally { } finally {
@ -203,13 +190,44 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
} }
} }
// No cache is currently supported for client side mode.
static final class OpenSslClientSessionContext extends OpenSslSessionContext { static final class OpenSslClientSessionContext extends OpenSslSessionContext {
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) { OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap)); super(context, provider);
} }
void setSession(ReferenceCountedOpenSslEngine engine) throws SSLException { @Override
((OpenSslClientSessionCache) sessionCache).setSession(engine); 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;
} }
} }

View File

@ -99,6 +99,8 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
*/ */
protected long ctx; protected long ctx;
private final List<String> unmodifiableCiphers; private final List<String> unmodifiableCiphers;
private final long sessionCacheSize;
private final long sessionTimeout;
private final OpenSslApplicationProtocolNegotiator apn; private final OpenSslApplicationProtocolNegotiator apn;
private final int mode; private final int mode;
@ -177,15 +179,16 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
} }
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
ApplicationProtocolConfig apnCfg, ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout,
int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols, int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols,
boolean startTls, boolean enableOcsp, boolean leakDetection) throws SSLException { boolean startTls, boolean enableOcsp, boolean leakDetection) throws SSLException {
this(ciphers, cipherFilter, toNegotiator(apnCfg), mode, keyCertChain, this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, leakDetection); clientAuth, protocols, startTls, enableOcsp, leakDetection);
} }
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp, ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
boolean leakDetection) throws SSLException { boolean leakDetection) throws SSLException {
super(startTls); super(startTls);
@ -311,6 +314,22 @@ 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) { if (enableOcsp) {
SSLContext.enableOcsp(ctx, isClient()); SSLContext.enableOcsp(ctx, isClient());
} }
@ -340,6 +359,16 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
return unmodifiableCiphers; return unmodifiableCiphers;
} }
@Override
public final long sessionCacheSize() {
return sessionCacheSize;
}
@Override
public final long sessionTimeout() {
return sessionTimeout;
}
@Override @Override
public ApplicationProtocolNegotiator applicationProtocolNegotiator() { public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
return apn; return apn;
@ -664,7 +693,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
// May be null if it was destroyed in the meantime. // May be null if it was destroyed in the meantime.
return CertificateVerifier.X509_V_ERR_UNSPECIFIED; return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
} }
engine.setupHandshakeSession();
X509Certificate[] peerCerts = certificates(chain); X509Certificate[] peerCerts = certificates(chain);
try { try {
verify(engine, peerCerts, auth); verify(engine, peerCerts, auth);

View File

@ -28,6 +28,7 @@ import io.netty.util.ResourceLeakTracker;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SuppressJava6Requirement; import io.netty.util.internal.SuppressJava6Requirement;
import io.netty.util.internal.ThrowableUtil; import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
@ -36,13 +37,16 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException; import java.nio.ReadOnlyBufferException;
import java.security.Principal;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@ -52,7 +56,12 @@ import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionContext;
import javax.security.cert.X509Certificate;
import static io.netty.handler.ssl.OpenSsl.memoryAddress; import static io.netty.handler.ssl.OpenSsl.memoryAddress;
import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2; import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2;
@ -113,7 +122,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
/** /**
* Depends upon tcnative ... only use if tcnative is available! * Depends upon tcnative ... only use if tcnative is available!
*/ */
static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH; private 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_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0);
private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0); private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0);
@ -174,6 +183,10 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
}; };
private volatile ClientAuth clientAuth = ClientAuth.NONE; 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;
private String endPointIdentificationAlgorithm; private String endPointIdentificationAlgorithm;
// Store as object as AlgorithmConstraints only exists since java 7. // Store as object as AlgorithmConstraints only exists since java 7.
@ -194,15 +207,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
private final OpenSslEngineMap engineMap; private final OpenSslEngineMap engineMap;
private final OpenSslApplicationProtocolNegotiator apn; private final OpenSslApplicationProtocolNegotiator apn;
private final ReferenceCountedOpenSslContext parentContext; private final ReferenceCountedOpenSslContext parentContext;
private volatile OpenSslSession session; private final OpenSslSession session;
private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1];
private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1];
private final boolean enableOcsp; private final boolean enableOcsp;
private int maxWrapOverhead; private int maxWrapOverhead;
private int maxWrapBufferSize; private int maxWrapBufferSize;
private Throwable pendingException; private Throwable pendingException;
private boolean sessionSet;
/** /**
* Create a new instance. * Create a new instance.
@ -223,9 +234,89 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
this.alloc = checkNotNull(alloc, "alloc"); this.alloc = checkNotNull(alloc, "alloc");
apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator(); apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
clientMode = context.isClient(); clientMode = context.isClient();
session = wrapSessionIfNeeded(context.sessionContext().nullSession); if (PlatformDependent.javaVersion() >= 7) {
session = new ExtendedOpenSslSession(new DefaultOpenSslSession(context.sessionContext())) {
private String[] peerSupportedSignatureAlgorithms;
private List requestedServerNames;
@Override
public List getRequestedServerNames() {
if (clientMode) {
return Java8SslUtils.getSniHostNames(sniHostNames);
} else {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (requestedServerNames == null) {
if (isDestroyed()) {
requestedServerNames = Collections.emptyList();
} else {
String name = SSL.getSniHostname(ssl);
if (name == null) {
requestedServerNames = Collections.emptyList();
} else {
// Convert to bytes as we do not want to do any strict validation of the
// SNIHostName while creating it.
requestedServerNames =
Java8SslUtils.getSniHostName(
SSL.getSniHostname(ssl).getBytes(CharsetUtil.UTF_8));
}
}
}
return requestedServerNames;
}
}
}
@Override
public String[] getPeerSupportedSignatureAlgorithms() {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (peerSupportedSignatureAlgorithms == null) {
if (isDestroyed()) {
peerSupportedSignatureAlgorithms = EmptyArrays.EMPTY_STRINGS;
} else {
String[] algs = SSL.getSigAlgs(ssl);
if (algs == null) {
peerSupportedSignatureAlgorithms = EmptyArrays.EMPTY_STRINGS;
} else {
Set<String> algorithmList = new LinkedHashSet<String>(algs.length);
for (String alg: algs) {
String converted = SignatureAlgorithmConverter.toJavaName(alg);
if (converted != null) {
algorithmList.add(converted);
}
}
peerSupportedSignatureAlgorithms = algorithmList.toArray(new String[0]);
}
}
}
return peerSupportedSignatureAlgorithms.clone();
}
}
@Override
public List<byte[]> getStatusResponses() {
byte[] ocspResponse = null;
if (enableOcsp && clientMode) {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (!isDestroyed()) {
ocspResponse = SSL.getOcspResponse(ssl);
}
}
}
return ocspResponse == null ?
Collections.<byte[]>emptyList() : Collections.singletonList(ocspResponse);
}
};
} else {
session = new DefaultOpenSslSession(context.sessionContext());
}
engineMap = context.engineMap; engineMap = context.engineMap;
enableOcsp = context.enableOcsp; 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;
}
this.jdkCompatibilityMode = jdkCompatibilityMode; this.jdkCompatibilityMode = jdkCompatibilityMode;
Lock readerLock = context.ctxLock.readLock(); Lock readerLock = context.ctxLock.readLock();
@ -265,11 +356,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
| SSL.SSL_MODE_ENABLE_FALSE_START); | SSL.SSL_MODE_ENABLE_FALSE_START);
} }
int opts = SSL.getOptions(ssl);
if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, PROTOCOL_TLS_V1_3)) {
// We always support tickets by default when TLSv1.3 is used
SSL.clearOptions(ssl, SSL.SSL_OP_NO_TICKET);
}
// setMode may impact the overhead. // setMode may impact the overhead.
calculateMaxWrapOverhead(); calculateMaxWrapOverhead();
} catch (Throwable cause) { } catch (Throwable cause) {
@ -291,13 +377,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
leak = leakDetection ? leakDetector.track(this) : null; leak = leakDetection ? leakDetector.track(this) : null;
} }
private OpenSslSession wrapSessionIfNeeded(OpenSslSession session) {
if (PlatformDependent.javaVersion() >= 7) {
return new DefaultExtendedOpenSslSession(session);
}
return session;
}
final synchronized String[] authMethods() { final synchronized String[] authMethods() {
if (isDestroyed()) { if (isDestroyed()) {
return EmptyArrays.EMPTY_STRINGS; return EmptyArrays.EMPTY_STRINGS;
@ -305,58 +384,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return SSL.authenticationMethods(ssl); return SSL.authenticationMethods(ssl);
} }
final synchronized void setupHandshakeSession() {
OpenSslSession old = session;
if (old.isNullSession()) {
session = wrapSessionIfNeeded(newOpenSslSession(SSL.getSession(ssl)));
old.release();
}
}
/**
* A new {@code SSL_SESSION*} is created and should be used for this {@link ReferenceCountedOpenSslEngine}.
* The new {@link OpenSslSession} will be returned that is created (wrapping the {@code SSL_SESSION*}) and attached
* to this engine.
*/
synchronized OpenSslSession sessionCreated(long sslSession) throws SSLException {
if (isDestroyed()) {
throw new SSLException("Already closed");
}
OpenSslSession oldSession = this.session;
OpenSslSession session = wrapSessionIfNeeded(newOpenSslSession(sslSession));
session.setLocalCertificate(oldSession.getLocalCertificates());
session.setPacketBufferSize(oldSession.getPacketBufferSize());
this.session = session;
// Release the old used session
oldSession.release();
return session;
}
/**
* Set a {@link OpenSslSession} to re-use. This will return {@code true} if we were able to re-use the session and
* {@code false} otherwise. If {@code true} is returned we also incremented the reference count of the underlying
* {@code SSL_SESSION*} and called {@link OpenSslSession#retain()}.
*/
synchronized boolean setSession(OpenSslSession session) throws SSLException {
assert getUseClientMode();
if (isDestroyed()) {
throw new SSLException("Already closed");
}
long addr = session.nativeAddr();
if (addr != -1) {
if (SSL.setSession(ssl, addr)) {
session.retain();
OpenSslSession oldSession = this.session;
this.session = session;
oldSession.release();
return true;
}
}
return false;
}
final boolean setKeyMaterial(OpenSslKeyMaterial keyMaterial) throws Exception { final boolean setKeyMaterial(OpenSslKeyMaterial keyMaterial) throws Exception {
synchronized (this) { synchronized (this) {
if (isDestroyed()) { if (isDestroyed()) {
@ -364,7 +391,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress()); SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
} }
session.setLocalCertificate(keyMaterial.certificateChain()); localCertificateChain = keyMaterial.certificateChain();
return true; return true;
} }
@ -490,8 +517,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
ssl = networkBIO = 0; ssl = networkBIO = 0;
isInboundDone = outboundClosed = true; isInboundDone = outboundClosed = true;
session.release();
} }
// On shutdown clear all errors // On shutdown clear all errors
@ -631,7 +656,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
*/ */
private void calculateMaxWrapOverhead() { private void calculateMaxWrapOverhead() {
maxWrapOverhead = SSL.getMaxWrapOverhead(ssl); maxWrapOverhead = SSL.getMaxWrapOverhead(ssl);
session.setPacketBufferSize(maxEncryptedPacketLength());
// maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value. // maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value.
// If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be // If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be
// configurable in the future if necessary. // configurable in the future if necessary.
@ -819,6 +844,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs.
bytesProduced = SSL.bioFlushByteBuffer(networkBIO); bytesProduced = SSL.bioFlushByteBuffer(networkBIO);
if (bytesProduced > 0) { if (bytesProduced > 0) {
return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced);
} }
@ -1196,7 +1222,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status,
bytesConsumed, bytesProduced); bytesConsumed, bytesProduced);
} }
} else if (packetLength == 0) { } else if (packetLength == 0 || jdkCompatibilityMode) {
// We either consumed all data or we are in jdkCompatibilityMode and have consumed // We either consumed all data or we are in jdkCompatibilityMode and have consumed
// a single TLS packet and should stop consuming until this method is called again. // a single TLS packet and should stop consuming until this method is called again.
break srcLoop; break srcLoop;
@ -1487,7 +1513,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return EmptyArrays.EMPTY_STRINGS; return EmptyArrays.EMPTY_STRINGS;
} else { } else {
Set<String> enabledSet = new LinkedHashSet<String>(enabled.length + extraCiphers.length); Set<String> enabledSet = new LinkedHashSet<String>(enabled.length + extraCiphers.length);
synchronized (this) { synchronized (this) {
for (int i = 0; i < enabled.length; i++) { for (int i = 0; i < enabled.length; i++) {
String mapped = toJavaCipherSuite(enabled[i]); String mapped = toJavaCipherSuite(enabled[i]);
@ -1667,11 +1692,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// Disable protocols we do not want // Disable protocols we do not want
SSL.setOptions(ssl, opts); SSL.setOptions(ssl, opts);
if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, PROTOCOL_TLS_V1_3)) {
// We always support tickets by default when TLSv1.3 is used
SSL.clearOptions(ssl, SSL.SSL_OP_NO_TICKET);
}
} else { } else {
throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols)); throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols));
} }
@ -1728,6 +1748,10 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP; return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP;
} }
private static boolean isEmpty(Object[] arr) {
return arr == null || arr.length == 0;
}
private static boolean isEmpty(byte[] cert) { private static boolean isEmpty(byte[] cert) {
return cert == null || cert.length == 0; return cert == null || cert.length == 0;
} }
@ -1784,20 +1808,15 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier.
engineMap.add(this); engineMap.add(this);
if (lastAccessed == -1) {
if (!sessionSet && getUseClientMode()) { lastAccessed = System.currentTimeMillis();
(((ReferenceCountedOpenSslClientContext.OpenSslClientSessionContext) session.getSessionContext()))
.setSession(this);
sessionSet = true;
} }
session.updateLastAccessedTime();
int code = SSL.doHandshake(ssl); int code = SSL.doHandshake(ssl);
int nonApp = SSL.bioLengthNonApplication(networkBIO);
if (code <= 0) { if (code <= 0) {
int sslError = SSL.getError(ssl, code); int sslError = SSL.getError(ssl, code);
if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) {
return pendingStatus(nonApp); return pendingStatus(SSL.bioLengthNonApplication(networkBIO));
} }
if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
@ -1816,134 +1835,12 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
throw shutdownWithError("SSL_do_handshake", sslError); throw shutdownWithError("SSL_do_handshake", sslError);
} }
// We have produced more data as part of the handshake if this is the case the user should call wrap(...) // We have produced more data as part of the handshake if this is the case the user should call wrap(...)
if (nonApp > 0) { if (SSL.bioLengthNonApplication(networkBIO) > 0) {
return NEED_WRAP; return NEED_WRAP;
} }
// if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished.
OpenSslSession oldSession = session; session.handshakeFinished();
Certificate[] local = oldSession.getLocalCertificates(); return FINISHED;
try {
session = wrapSessionIfNeeded(newOpenSslSession(SSL.getSession(ssl)));
session.setLocalCertificate(local);
selectApplicationProtocol();
calculateMaxWrapOverhead();
handshakeState = HandshakeState.FINISHED;
return FINISHED;
} finally {
oldSession.release();
}
}
private DefaultOpenSslSession newOpenSslSession(long sslSession) {
return parentContext.sessionContext().newOpenSslSession(sslSession, getPeerHost(), getPeerPort(),
SSL.getVersion(ssl), toJavaCipherSuite(SSL.getCipherForSSL(ssl)), generatePeerCertificateChain(),
SSL.getTime(ssl) * 1000L);
}
private OpenSslJavaxX509Certificate[] generatePeerCertificateChain() {
// Return the full chain from the JNI layer.
byte[][] chain = SSL.getPeerCertChain(ssl);
if (clientMode) {
if (SslUtils.isEmpty(chain)) {
return new OpenSslJavaxX509Certificate[0];
} else {
OpenSslJavaxX509Certificate[] peerCerts = new OpenSslJavaxX509Certificate[chain.length];
initCerts(peerCerts, chain, 0);
return peerCerts;
}
} 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)) {
return new OpenSslJavaxX509Certificate[0];
} else {
OpenSslJavaxX509Certificate clientX509Cert = new OpenSslJavaxX509Certificate(clientCert);
if (SslUtils.isEmpty(chain)) {
return new OpenSslJavaxX509Certificate[] { clientX509Cert };
} else {
OpenSslJavaxX509Certificate[] peerCerts = new OpenSslJavaxX509Certificate[chain.length + 1];
peerCerts[0] = clientX509Cert;
initCerts(peerCerts, chain, 1);
return peerCerts;
}
}
}
}
private void initCerts(OpenSslJavaxX509Certificate[] peerCerts, byte[][] chain, int startPos) {
for (int i = 0; i < chain.length; i++) {
int certPos = startPos + i;
peerCerts[certPos] = new OpenSslJavaxX509Certificate(chain[i]);
}
}
/**
* 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);
}
}
}
} }
private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status)
@ -2169,13 +2066,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
super.setSSLParameters(sslParameters); super.setSSLParameters(sslParameters);
} }
synchronized boolean isSessionReused() {
if (!isDestroyed()) {
return SSL.isSessionReused(ssl);
}
return false;
}
private static boolean isEndPointVerificationEnabled(String endPointIdentificationAlgorithm) { private static boolean isEndPointVerificationEnabled(String endPointIdentificationAlgorithm) {
return endPointIdentificationAlgorithm != null && !endPointIdentificationAlgorithm.isEmpty(); return endPointIdentificationAlgorithm != null && !endPointIdentificationAlgorithm.isEmpty();
} }
@ -2201,82 +2091,378 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return Buffer.address(b); return Buffer.address(b);
} }
private final class DefaultExtendedOpenSslSession extends ExtendedOpenSslSession { private final class DefaultOpenSslSession implements OpenSslSession {
private String[] peerSupportedSignatureAlgorithms; private final OpenSslSessionContext sessionContext;
@SuppressWarnings("rawtypes")
private List requestedServerNames;
DefaultExtendedOpenSslSession(OpenSslSession wrapped) { // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any
super(wrapped); // thread.
private X509Certificate[] x509PeerCerts;
private Certificate[] peerCerts;
private String protocol;
private String cipher;
private byte[] id;
private long creationTime;
private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH;
// lazy init for memory reasons
private Map<String, Object> values;
DefaultOpenSslSession(OpenSslSessionContext sessionContext) {
this.sessionContext = sessionContext;
} }
@SuppressWarnings("rawtypes") private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) {
@Override return new SSLSessionBindingEvent(session, name);
public List getRequestedServerNames() {
if (clientMode) {
return Java8SslUtils.getSniHostNames(sniHostNames);
} else {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (requestedServerNames == null) {
if (isDestroyed()) {
requestedServerNames = Collections.emptyList();
} else {
String name = SSL.getSniHostname(ssl);
if (name == null) {
requestedServerNames = Collections.emptyList();
} else {
// Convert to bytes as we do not want to do any strict validation of the
// SNIHostName while creating it.
requestedServerNames =
Java8SslUtils.getSniHostName(
SSL.getSniHostname(ssl).getBytes(CharsetUtil.UTF_8));
}
}
}
return requestedServerNames;
}
}
} }
@Override @Override
public String[] getPeerSupportedSignatureAlgorithms() { public byte[] getId() {
synchronized (ReferenceCountedOpenSslEngine.this) { synchronized (ReferenceCountedOpenSslEngine.this) {
if (peerSupportedSignatureAlgorithms == null) { if (id == null) {
if (isDestroyed()) { return EmptyArrays.EMPTY_BYTES;
peerSupportedSignatureAlgorithms = EmptyArrays.EMPTY_STRINGS;
} else {
String[] algs = SSL.getSigAlgs(ssl);
if (algs == null) {
peerSupportedSignatureAlgorithms = EmptyArrays.EMPTY_STRINGS;
} else {
Set<String> algorithmList = new LinkedHashSet<String>(algs.length);
for (String alg: algs) {
String converted = SignatureAlgorithmConverter.toJavaName(alg);
if (converted != null) {
algorithmList.add(converted);
}
}
peerSupportedSignatureAlgorithms = algorithmList.toArray(new String[0]);
}
}
} }
return peerSupportedSignatureAlgorithms.clone(); return id.clone();
} }
} }
@Override @Override
public List<byte[]> getStatusResponses() { public SSLSessionContext getSessionContext() {
byte[] ocspResponse = null; return sessionContext;
if (enableOcsp && clientMode) { }
@Override
public long getCreationTime() {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (creationTime == 0 && !isDestroyed()) {
creationTime = SSL.getTime(ssl) * 1000L;
}
}
return creationTime;
}
@Override
public long getLastAccessedTime() {
long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed;
// if lastAccessed is -1 we will just return the creation time as the handshake was not started yet.
return lastAccessed == -1 ? getCreationTime() : lastAccessed;
}
@Override
public void invalidate() {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (!isDestroyed()) {
SSL.setTimeout(ssl, 0);
}
}
}
@Override
public boolean isValid() {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (!isDestroyed()) {
return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L);
}
}
return false;
}
@Override
public void putValue(String name, Object value) {
ObjectUtil.checkNotNull(name, "name");
ObjectUtil.checkNotNull(value, "value");
final Object old;
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null) {
// Use size of 2 to keep the memory overhead small
values = this.values = new HashMap<String, Object>(2);
}
old = values.put(name, value);
}
if (value instanceof SSLSessionBindingListener) {
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name));
}
notifyUnbound(old, name);
}
@Override
public Object getValue(String name) {
ObjectUtil.checkNotNull(name, "name");
synchronized (this) {
if (values == null) {
return null;
}
return values.get(name);
}
}
@Override
public void removeValue(String name) {
ObjectUtil.checkNotNull(name, "name");
final Object old;
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null) {
return;
}
old = values.remove(name);
}
notifyUnbound(old, name);
}
@Override
public String[] getValueNames() {
synchronized (this) {
Map<String, Object> values = this.values;
if (values == null || values.isEmpty()) {
return EmptyArrays.EMPTY_STRINGS;
}
return values.keySet().toArray(new String[0]);
}
}
private void notifyUnbound(Object value, String name) {
if (value instanceof SSLSessionBindingListener) {
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name));
}
}
/**
* Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by
* the user.
*/
@Override
public void handshakeFinished() throws SSLException {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (!isDestroyed()) {
id = SSL.getSessionId(ssl);
cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
protocol = SSL.getVersion(ssl);
initPeerCerts();
selectApplicationProtocol();
calculateMaxWrapOverhead();
handshakeState = HandshakeState.FINISHED;
} else {
throw new SSLException("Already closed");
}
}
}
/**
* Init peer certificates that can be obtained via {@link #getPeerCertificateChain()}
* and {@link #getPeerCertificates()}.
*/
private void initPeerCerts() {
// Return the full chain from the JNI layer.
byte[][] chain = SSL.getPeerCertChain(ssl);
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 OpenSslX509Certificate(clientCert)};
x509PeerCerts = new X509Certificate[] {new OpenSslJavaxX509Certificate(clientCert)};
} else {
peerCerts = new Certificate[chain.length + 1];
x509PeerCerts = new X509Certificate[chain.length + 1];
peerCerts[0] = new OpenSslX509Certificate(clientCert);
x509PeerCerts[0] = new OpenSslJavaxX509Certificate(clientCert);
initCerts(chain, 1);
}
}
}
}
private void initCerts(byte[][] chain, int startPos) {
for (int i = 0; i < chain.length; i++) {
int certPos = startPos + i;
peerCerts[certPos] = new OpenSslX509Certificate(chain[i]);
x509PeerCerts[certPos] = new OpenSslJavaxX509Certificate(chain[i]);
}
}
/**
* 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) {
if (isEmpty(peerCerts)) {
throw new SSLPeerUnverifiedException("peer not verified");
}
return peerCerts.clone();
}
}
@Override
public Certificate[] getLocalCertificates() {
Certificate[] localCerts = ReferenceCountedOpenSslEngine.this.localCertificateChain;
if (localCerts == null) {
return null;
}
return localCerts.clone();
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (isEmpty(x509PeerCerts)) {
throw new SSLPeerUnverifiedException("peer not verified");
}
return x509PeerCerts.clone();
}
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] peer = getPeerCertificates();
// No need for null or length > 0 is needed as this is done in getPeerCertificates()
// already.
return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
}
@Override
public Principal getLocalPrincipal() {
Certificate[] local = ReferenceCountedOpenSslEngine.this.localCertificateChain;
if (local == null || local.length == 0) {
return null;
}
return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
}
@Override
public String getCipherSuite() {
synchronized (ReferenceCountedOpenSslEngine.this) {
if (cipher == null) {
return SslUtils.INVALID_CIPHER;
}
return cipher;
}
}
@Override
public String getProtocol() {
String protocol = this.protocol;
if (protocol == null) {
synchronized (ReferenceCountedOpenSslEngine.this) { synchronized (ReferenceCountedOpenSslEngine.this) {
if (!isDestroyed()) { if (!isDestroyed()) {
ocspResponse = SSL.getOcspResponse(ssl); protocol = SSL.getVersion(ssl);
} else {
protocol = StringUtil.EMPTY_STRING;
} }
} }
} }
return ocspResponse == null ? return protocol;
Collections.<byte[]>emptyList() : Collections.singletonList(ocspResponse.clone());
} }
};
@Override
public String getPeerHost() {
return ReferenceCountedOpenSslEngine.this.getPeerHost();
}
@Override
public int getPeerPort() {
return ReferenceCountedOpenSslEngine.this.getPeerPort();
}
@Override
public int getPacketBufferSize() {
return maxEncryptedPacketLength();
}
@Override
public int getApplicationBufferSize() {
return applicationBufferSize;
}
@Override
public void tryExpandApplicationBufferSize(int packetLengthDataOnly) {
if (packetLengthDataOnly > MAX_PLAINTEXT_LENGTH && applicationBufferSize != MAX_RECORD_SIZE) {
applicationBufferSize = MAX_RECORD_SIZE;
}
}
}
} }

View File

@ -72,14 +72,13 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp, String keyStore) throws SSLException { boolean enableOcsp, String keyStore) throws SSLException {
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain, super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, true); clientAuth, protocols, startTls, enableOcsp, true);
// Create a new SSL_CTX and configure it. // Create a new SSL_CTX and configure it.
boolean success = false; boolean success = false;
try { try {
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore, keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
sessionCacheSize, sessionTimeout);
if (ENABLE_SESSION_TICKET) { if (ENABLE_SESSION_TICKET) {
sessionContext.setTicketKeys(); sessionContext.setTicketKeys();
} }
@ -102,7 +101,7 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
TrustManagerFactory trustManagerFactory, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, X509Certificate[] keyCertChain, PrivateKey key,
String keyPassword, KeyManagerFactory keyManagerFactory, String keyPassword, KeyManagerFactory keyManagerFactory,
String keyStore, long sessionCacheSize, long sessionTimeout) String keyStore)
throws SSLException { throws SSLException {
OpenSslKeyMaterialProvider keyMaterialProvider = null; OpenSslKeyMaterialProvider keyMaterialProvider = null;
try { try {
@ -186,14 +185,6 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
OpenSslServerSessionContext sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider); OpenSslServerSessionContext sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider);
sessionContext.setSessionIdContext(ID); sessionContext.setSessionIdContext(ID);
// Enable session caching by default
sessionContext.setSessionCacheEnabled(true);
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; keyMaterialProvider = null;
@ -232,7 +223,6 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
// Maybe null if destroyed in the meantime. // Maybe null if destroyed in the meantime.
return; return;
} }
engine.setupHandshakeSession();
try { try {
// For now we just ignore the asn1DerEncodedPrincipals as this is kind of inline with what the // For now we just ignore the asn1DerEncodedPrincipals as this is kind of inline with what the
// OpenJDK SSLEngineImpl does. // OpenJDK SSLEngineImpl does.

View File

@ -892,16 +892,12 @@ public abstract class SslContext {
/** /**
* Returns the size of the cache used for storing SSL session objects. * Returns the size of the cache used for storing SSL session objects.
*/ */
public long sessionCacheSize() { public abstract long sessionCacheSize();
return sessionContext().getSessionCacheSize();
}
/** /**
* Returns the timeout for the cached SSL session objects, in seconds. * Returns the timeout for the cached SSL session objects, in seconds.
*/ */
public long sessionTimeout() { public abstract long sessionTimeout();
return sessionContext().getSessionTimeout();
}
/** /**
* @deprecated Use {@link #applicationProtocolNegotiator()} instead. * @deprecated Use {@link #applicationProtocolNegotiator()} instead.

View File

@ -448,10 +448,6 @@ final class SslUtils {
return TLSV13_CIPHERS.contains(cipher); return TLSV13_CIPHERS.contains(cipher);
} }
static boolean isEmpty(Object[] arr) {
return arr == null || arr.length == 0;
}
private SslUtils() { private SslUtils() {
} }
} }

View File

@ -22,7 +22,6 @@ import org.junit.Ignore;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import javax.net.ssl.SSLSessionContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -81,9 +80,4 @@ public class ConscryptJdkSslEngineInteropTest extends SSLEngineTest {
// TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException. // TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException.
return super.mySetupMutualAuthServerIsValidServerException(cause) || causedBySSLException(cause); return super.mySetupMutualAuthServerIsValidServerException(cause) || causedBySSLException(cause);
} }
@Override
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
// Not supported by conscrypt
}
} }

View File

@ -22,7 +22,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import java.security.Provider; import java.security.Provider;
import java.util.ArrayList; import java.util.ArrayList;
@ -151,17 +150,6 @@ public class ConscryptOpenSslEngineInteropTest extends ConscryptSslEngineTest {
super.testSessionLocalWhenNonMutualWithKeyManager(); super.testSessionLocalWhenNonMutualWithKeyManager();
} }
@Ignore("Ignore for now as Conscrypt seems to behave different then expected")
@Override
public void testSessionCache() {
// Skip
}
@Override
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
// Not supported by conscrypt
}
@Override @Override
protected SSLEngine wrapEngine(SSLEngine engine) { protected SSLEngine wrapEngine(SSLEngine engine) {
return Java8SslTestUtils.wrapSSLEngineForTesting(engine); return Java8SslTestUtils.wrapSSLEngineForTesting(engine);

View File

@ -20,7 +20,6 @@ import org.junit.Ignore;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import javax.net.ssl.SSLSessionContext;
import java.security.Provider; import java.security.Provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -79,15 +78,4 @@ public class ConscryptSslEngineTest extends SSLEngineTest {
@Override @Override
public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() { public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() {
} }
@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
}
} }

View File

@ -23,7 +23,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import javax.net.ssl.SSLSessionContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -93,15 +92,4 @@ public class JdkConscryptSslEngineInteropTest extends SSLEngineTest {
// Ignore as Conscrypt does not correctly return the local certificates while the TrustManager is invoked. // Ignore as Conscrypt does not correctly return the local certificates while the TrustManager is invoked.
// See https://github.com/google/conscrypt/issues/634 // 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
}
} }

View File

@ -22,7 +22,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import java.security.Provider; import java.security.Provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -143,11 +142,6 @@ public class OpenSslConscryptSslEngineInteropTest extends ConscryptSslEngineTest
super.testSessionLocalWhenNonMutualWithKeyManager(); super.testSessionLocalWhenNonMutualWithKeyManager();
} }
@Override
protected void invalidateSessionsAndAssert(SSLSessionContext context) {
// Not supported by conscrypt
}
@Override @Override
protected SSLEngine wrapEngine(SSLEngine engine) { protected SSLEngine wrapEngine(SSLEngine engine) {
return Java8SslTestUtils.wrapSSLEngineForTesting(engine); return Java8SslTestUtils.wrapSSLEngineForTesting(engine);

View File

@ -1359,36 +1359,4 @@ public class OpenSslEngineTest extends SSLEngineTest {
} }
return context; 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();
}
} }

View File

@ -81,7 +81,6 @@ import java.security.cert.CertificateException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -109,7 +108,6 @@ import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
@ -1267,14 +1265,14 @@ public abstract class SSLEngineTest {
.trustManager(InsecureTrustManagerFactory.INSTANCE) .trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslClientProvider()) .sslProvider(sslClientProvider())
// This test only works for non TLSv1.3 for now // This test only works for non TLSv1.3 for now
.protocols(protocols()) .protocols(PROTOCOL_TLS_V1_2)
.sslContextProvider(clientSslContextProvider()) .sslContextProvider(clientSslContextProvider())
.build()); .build());
SelfSignedCertificate ssc = new SelfSignedCertificate(); SelfSignedCertificate ssc = new SelfSignedCertificate();
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslServerProvider()) .sslProvider(sslServerProvider())
// This test only works for non TLSv1.3 for now // This test only works for non TLSv1.3 for now
.protocols(protocols()) .protocols(PROTOCOL_TLS_V1_2)
.sslContextProvider(serverSslContextProvider()) .sslContextProvider(serverSslContextProvider())
.build()); .build());
SSLEngine clientEngine = null; SSLEngine clientEngine = null;
@ -1289,54 +1287,10 @@ public abstract class SSLEngineTest {
handshake(clientEngine, serverEngine); handshake(clientEngine, serverEngine);
if (protocolCipherCombo == ProtocolCipherCombo.TLSV13) { // After the handshake the id should have length > 0
// Allocate something which is big enough for sure assertNotEquals(0, clientEngine.getSession().getId().length);
ByteBuffer packetBuffer = allocateBuffer(32 * 1024); assertNotEquals(0, serverEngine.getSession().getId().length);
ByteBuffer appBuffer = allocateBuffer(32 * 1024); assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId());
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 { } finally {
cleanupClientSslEngine(clientEngine); cleanupClientSslEngine(clientEngine);
cleanupServerSslEngine(serverEngine); cleanupServerSslEngine(serverEngine);
@ -1513,7 +1467,8 @@ public abstract class SSLEngineTest {
ByteBuffer cTOs = allocateBuffer(clientEngine.getSession().getPacketBufferSize()); ByteBuffer cTOs = allocateBuffer(clientEngine.getSession().getPacketBufferSize());
ByteBuffer sTOc = allocateBuffer(serverEngine.getSession().getPacketBufferSize()); ByteBuffer sTOc = allocateBuffer(serverEngine.getSession().getPacketBufferSize());
ByteBuffer serverAppReadBuffer = allocateBuffer(serverEngine.getSession().getApplicationBufferSize()); ByteBuffer serverAppReadBuffer = allocateBuffer(
serverEngine.getSession().getApplicationBufferSize());
ByteBuffer clientAppReadBuffer = allocateBuffer( ByteBuffer clientAppReadBuffer = allocateBuffer(
clientEngine.getSession().getApplicationBufferSize()); clientEngine.getSession().getApplicationBufferSize());
@ -1527,6 +1482,7 @@ public abstract class SSLEngineTest {
boolean clientHandshakeFinished = false; boolean clientHandshakeFinished = false;
boolean serverHandshakeFinished = false; boolean serverHandshakeFinished = false;
boolean cTOsHasRemaining; boolean cTOsHasRemaining;
boolean sTOcHasRemaining; boolean sTOcHasRemaining;
@ -2942,267 +2898,6 @@ 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;
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());
} while ((reuse && (!isSessionMaybeReused(clientEngine) || !isSessionMaybeReused(serverEngine)))
|| (!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())
.sessionCacheSize(1)
.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 @Test
public void testSessionBindingEvent() throws Exception { public void testSessionBindingEvent() throws Exception {
clientSslCtx = wrapContext(SslContextBuilder.forClient() clientSslCtx = wrapContext(SslContextBuilder.forClient()
@ -3814,7 +3509,7 @@ public abstract class SSLEngineTest {
final Promise<SecretKey> promise = sb.config().group().next().newPromise(); final Promise<SecretKey> promise = sb.config().group().next().newPromise();
serverChannel = sb.childHandler(new ChannelInitializer<Channel>() { serverChannel = sb.childHandler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) throws Exception {
ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type)); ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type));
SslHandler sslHandler = delegatingExecutor == null ? SslHandler sslHandler = delegatingExecutor == null ?
@ -3872,7 +3567,7 @@ public abstract class SSLEngineTest {
new java.security.cert.X509Certificate[] { ssc.cert() }, ssc.key(), null, null, null); new java.security.cert.X509Certificate[] { ssc.cert() }, ssc.key(), null, null, null);
} }
private static final class TestTrustManagerFactory extends X509ExtendedTrustManager { private final class TestTrustManagerFactory extends X509ExtendedTrustManager {
private final Certificate localCert; private final Certificate localCert;
private volatile boolean verified; private volatile boolean verified;

View File

@ -1162,41 +1162,25 @@ public class SslHandlerTest {
EventLoopGroup group = new NioEventLoopGroup(); EventLoopGroup group = new NioEventLoopGroup();
Channel sc = null; 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]; final byte[] bytes = new byte[96];
PlatformDependent.threadLocalRandom().nextBytes(bytes); PlatformDependent.threadLocalRandom().nextBytes(bytes);
try { try {
final AtomicReference<AssertionError> assertErrorRef = new AtomicReference<AssertionError>();
sc = new ServerBootstrap() sc = new ServerBootstrap()
.group(group) .group(group)
.channel(NioServerSocketChannel.class) .channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() { .childHandler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
final SslHandler sslHandler = sslServerCtx.newHandler(ch.alloc()); ch.pipeline().addLast(serverSslHandler);
ch.pipeline().addLast(sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
private int handshakeCount;
@Override @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof SslHandshakeCompletionEvent) { 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)); ctx.writeAndFlush(Unpooled.wrappedBuffer(bytes));
} }
} }
@ -1205,31 +1189,6 @@ public class SslHandlerTest {
}) })
.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); .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() ChannelFuture future = new Bootstrap()
.group(group) .group(group)
.channel(NioSocketChannel.class) .channel(NioSocketChannel.class)
@ -1252,18 +1211,11 @@ public class SslHandlerTest {
} }
}); });
} }
}).connect(serverAddress); }).connect(sc.localAddress());
cc = future.syncUninterruptibly().channel(); cc = future.syncUninterruptibly().channel();
assertTrue(clientSslHandler.handshakeFuture().sync().isSuccess()); assertTrue(clientSslHandler.handshakeFuture().await().isSuccess());
assertTrue(serverSslHandler.handshakeFuture().await().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(); Object obj = queue.take();
if (obj instanceof ByteBuf) { if (obj instanceof ByteBuf) {
ByteBuf buffer = (ByteBuf) obj; ByteBuf buffer = (ByteBuf) obj;
@ -1280,6 +1232,11 @@ public class SslHandlerTest {
if (cc != null) { if (cc != null) {
cc.close().syncUninterruptibly(); cc.close().syncUninterruptibly();
} }
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslClientCtx);
} }
} }