Support session cache for client and server when using native SSLEngine implementation (#10331)
Motivation: At the moment we don't support session caching for client side when using native SSLEngine implementation and our implementation of SSLSessionContext is incomplete. Modification: - Consume netty-tcnative changes to be able to cache session in an external cache - Add and adjust unit tests to test session caching - Add an in memory session cache that is hooked into native SSLEngine Result: Support session caching on the client and server side
This commit is contained in:
parent
6035188246
commit
7bf1ffb2d4
@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
|
||||||
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.SSLSessionContext;
|
import javax.net.ssl.SSLSessionBindingEvent;
|
||||||
|
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;
|
||||||
@ -58,8 +60,38 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handshakeFinished() throws SSLException {
|
public OpenSslSessionId sessionId() {
|
||||||
wrapped.handshakeFinished();
|
return wrapped.sessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
@ -78,7 +110,7 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final SSLSessionContext getSessionContext() {
|
public final OpenSslSessionContext getSessionContext() {
|
||||||
return wrapped.getSessionContext();
|
return wrapped.getSessionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,13 +135,22 @@ abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements Open
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void putValue(String s, Object o) {
|
public final void putValue(String name, Object value) {
|
||||||
wrapped.putValue(s, o);
|
if (value instanceof SSLSessionBindingListener) {
|
||||||
|
// Decorate the value if needed so we submit the correct SSLSession instance
|
||||||
|
value = new SSLSessionBindingListenerDecorator((SSLSessionBindingListener) value);
|
||||||
|
}
|
||||||
|
wrapped.putValue(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object getValue(String s) {
|
public final Object getValue(String s) {
|
||||||
return wrapped.getValue(s);
|
Object value = wrapped.getValue(s);
|
||||||
|
if (value instanceof SSLSessionBindingListenerDecorator) {
|
||||||
|
// Unwrap as needed so we return the original value
|
||||||
|
return ((SSLSessionBindingListenerDecorator) value).delegate;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -176,4 +217,62 @@ 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,16 +315,6 @@ 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);
|
||||||
|
@ -49,13 +49,14 @@ final class OpenSslClientContext extends OpenSslContext {
|
|||||||
boolean enableOcsp,
|
boolean enableOcsp,
|
||||||
String keyStore)
|
String keyStore)
|
||||||
throws SSLException {
|
throws SSLException {
|
||||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
|
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||||
ClientAuth.NONE, protocols, false, enableOcsp);
|
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) {
|
||||||
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,19 +28,18 @@ 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,
|
||||||
long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
|
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, sessionCacheSize, sessionTimeout, mode, keyCertChain,
|
super(ciphers, cipherFilter, apnCfg, 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, long sessionCacheSize,
|
OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain,
|
||||||
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, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols,
|
super(ciphers, cipherFilter, apn, mode, keyCertChain, clientAuth, protocols,
|
||||||
startTls, enableOcsp, false);
|
startTls, enableOcsp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,14 @@ 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,
|
||||||
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -51,14 +51,15 @@ final class OpenSslServerContext extends OpenSslContext {
|
|||||||
boolean enableOcsp,
|
boolean enableOcsp,
|
||||||
String keyStore)
|
String keyStore)
|
||||||
throws SSLException {
|
throws SSLException {
|
||||||
super(ciphers, cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER,
|
super(ciphers, cipherFilter, toNegotiator(apn), SSL.SSL_MODE_SERVER,
|
||||||
keyCertChain, clientAuth, protocols, startTls, enableOcsp);
|
keyCertChain, clientAuth, protocols, startTls, enableOcsp);
|
||||||
// Create a new SSL_CTX and configure it.
|
// Create a new SSL_CTX and configure it.
|
||||||
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) {
|
||||||
|
@ -26,81 +26,7 @@ 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);
|
super(context, provider, SSL.SSL_SESS_CACHE_SERVER, new OpenSslSessionCache(context.engineMap));
|
||||||
}
|
|
||||||
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,16 +15,41 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
import javax.net.ssl.SSLException;
|
import io.netty.util.ReferenceCounted;
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
|
|
||||||
interface OpenSslSession extends SSLSession {
|
import javax.net.ssl.SSLSession;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by
|
* {@link SSLSession} that is specific to our native implementation and {@link ReferenceCounted} to track native
|
||||||
* the user.
|
* resources.
|
||||||
*/
|
*/
|
||||||
void handshakeFinished() throws SSLException;
|
interface OpenSslSession extends SSLSession, ReferenceCounted {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link OpenSslSessionId} that can be used to identify this session.
|
||||||
|
*/
|
||||||
|
OpenSslSessionId sessionId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
@ -33,4 +58,26 @@ interface OpenSslSession extends SSLSession {
|
|||||||
* @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);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,19 +20,19 @@ import static java.util.Objects.requireNonNull;
|
|||||||
import io.netty.internal.tcnative.SSL;
|
import io.netty.internal.tcnative.SSL;
|
||||||
import io.netty.internal.tcnative.SSLContext;
|
import io.netty.internal.tcnative.SSLContext;
|
||||||
import io.netty.internal.tcnative.SessionTicketKey;
|
import io.netty.internal.tcnative.SessionTicketKey;
|
||||||
|
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;
|
||||||
|
|
||||||
@ -42,30 +42,73 @@ 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) {
|
OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider, long mask,
|
||||||
|
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 boolean useKeyManager() {
|
final DefaultOpenSslSession newOpenSslSession(long sslSession, String peerHost,
|
||||||
return provider != null;
|
int peerPort, String protocol, String cipher,
|
||||||
|
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) {
|
||||||
requireNonNull(bytes, "bytes");
|
return sessionCache.getSession(bytes);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Enumeration<byte[]> getIds() {
|
public Enumeration<byte[]> getIds() {
|
||||||
return EMPTY;
|
return Collections.enumeration(sessionCache.getIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,12 +167,33 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
|||||||
/**
|
/**
|
||||||
* Enable or disable caching of SSL sessions.
|
* Enable or disable caching of SSL sessions.
|
||||||
*/
|
*/
|
||||||
public abstract void setSessionCacheEnabled(boolean enabled);
|
public void setSessionCacheEnabled(boolean enabled) {
|
||||||
|
long mode = enabled ? mask | SSL.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP |
|
||||||
|
SSL.SSL_SESS_CACHE_NO_INTERNAL_STORE : SSL.SSL_SESS_CACHE_OFF;
|
||||||
|
Lock writerLock = context.ctxLock.writeLock();
|
||||||
|
writerLock.lock();
|
||||||
|
try {
|
||||||
|
SSLContext.setSessionCacheMode(context.ctx, mode);
|
||||||
|
if (!enabled) {
|
||||||
|
sessionCache.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 abstract boolean isSessionCacheEnabled();
|
public boolean isSessionCacheEnabled() {
|
||||||
|
Lock readerLock = context.ctxLock.readLock();
|
||||||
|
readerLock.lock();
|
||||||
|
try {
|
||||||
|
return (SSLContext.getSessionCacheMode(context.ctx) & mask) != 0;
|
||||||
|
} finally {
|
||||||
|
readerLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stats of this context.
|
* Returns the stats of this context.
|
||||||
@ -142,17 +206,6 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
@ -66,12 +66,13 @@ 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, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
|
super(ciphers, cipherFilter, apn, 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) {
|
||||||
@ -91,7 +92,8 @@ 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) throws SSLException {
|
String keyStore, long sessionCacheSize, long sessionTimeout)
|
||||||
|
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");
|
||||||
@ -172,9 +174,20 @@ 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 {
|
||||||
@ -184,44 +197,13 @@ 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);
|
super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void setSession(ReferenceCountedOpenSslEngine engine) throws SSLException {
|
||||||
public void setSessionTimeout(int seconds) {
|
((OpenSslClientSessionCache) sessionCache).setSession(engine);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +98,6 @@ 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;
|
||||||
|
|
||||||
@ -178,16 +176,15 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||||
ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout,
|
ApplicationProtocolConfig apnCfg,
|
||||||
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), sessionCacheSize, sessionTimeout, mode, keyCertChain,
|
this(ciphers, cipherFilter, toNegotiator(apnCfg), 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, long sessionCacheSize,
|
OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain,
|
||||||
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);
|
||||||
@ -315,22 +312,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set session cache size, if specified */
|
|
||||||
if (sessionCacheSize <= 0) {
|
|
||||||
// Get the default session cache size using SSLContext.setSessionCacheSize()
|
|
||||||
sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
|
|
||||||
}
|
|
||||||
this.sessionCacheSize = sessionCacheSize;
|
|
||||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
|
||||||
|
|
||||||
/* Set session timeout, if specified */
|
|
||||||
if (sessionTimeout <= 0) {
|
|
||||||
// Get the default session timeout using SSLContext.setSessionCacheTimeout()
|
|
||||||
sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
|
|
||||||
}
|
|
||||||
this.sessionTimeout = sessionTimeout;
|
|
||||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
|
||||||
|
|
||||||
if (enableOcsp) {
|
if (enableOcsp) {
|
||||||
SSLContext.enableOcsp(ctx, isClient());
|
SSLContext.enableOcsp(ctx, isClient());
|
||||||
}
|
}
|
||||||
@ -360,16 +341,6 @@ 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;
|
||||||
@ -690,6 +661,7 @@ 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);
|
||||||
|
@ -27,23 +27,19 @@ import io.netty.util.ResourceLeakDetectorFactory;
|
|||||||
import io.netty.util.ResourceLeakTracker;
|
import io.netty.util.ResourceLeakTracker;
|
||||||
import io.netty.util.internal.EmptyArrays;
|
import io.netty.util.internal.EmptyArrays;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import io.netty.util.internal.StringUtil;
|
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
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;
|
||||||
|
|
||||||
@ -53,12 +49,7 @@ 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;
|
||||||
@ -119,7 +110,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!
|
||||||
*/
|
*/
|
||||||
private static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH;
|
static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH;
|
||||||
|
|
||||||
private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0);
|
private static final SSLEngineResult NEED_UNWRAP_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);
|
||||||
@ -180,10 +171,6 @@ 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.
|
||||||
@ -204,13 +191,15 @@ 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 final OpenSslSession session;
|
private volatile 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.
|
||||||
@ -231,85 +220,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
this.alloc = requireNonNull(alloc, "alloc");
|
this.alloc = requireNonNull(alloc, "alloc");
|
||||||
apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
|
apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator();
|
||||||
clientMode = context.isClient();
|
clientMode = context.isClient();
|
||||||
session = new ExtendedOpenSslSession(new DefaultOpenSslSession(context.sessionContext())) {
|
session = wrapSessionIfNeeded(context.sessionContext().nullSession);
|
||||||
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<>(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.emptyList() : Collections.singletonList(ocspResponse);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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();
|
||||||
@ -348,6 +261,11 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE);
|
SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
@ -369,6 +287,13 @@ 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;
|
||||||
@ -376,6 +301,58 @@ 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()) {
|
||||||
@ -383,7 +360,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
}
|
}
|
||||||
SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
|
SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
|
||||||
}
|
}
|
||||||
localCertificateChain = keyMaterial.certificateChain();
|
session.setLocalCertificate(keyMaterial.certificateChain());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,6 +486,8 @@ 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
|
||||||
@ -648,7 +627,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.
|
||||||
@ -836,7 +815,6 @@ 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);
|
||||||
}
|
}
|
||||||
@ -1214,7 +1192,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 || jdkCompatibilityMode) {
|
} else if (packetLength == 0) {
|
||||||
// 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;
|
||||||
@ -1688,6 +1666,11 @@ 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));
|
||||||
}
|
}
|
||||||
@ -1744,10 +1727,6 @@ 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;
|
||||||
}
|
}
|
||||||
@ -1804,15 +1783,20 @@ 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) {
|
|
||||||
lastAccessed = System.currentTimeMillis();
|
if (!sessionSet && getUseClientMode()) {
|
||||||
|
(((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(SSL.bioLengthNonApplication(networkBIO));
|
return pendingStatus(nonApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
|
if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
|
||||||
@ -1831,12 +1815,134 @@ 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 (SSL.bioLengthNonApplication(networkBIO) > 0) {
|
if (nonApp > 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.
|
||||||
session.handshakeFinished();
|
OpenSslSession oldSession = session;
|
||||||
|
Certificate[] local = oldSession.getLocalCertificates();
|
||||||
|
|
||||||
|
try {
|
||||||
|
session = wrapSessionIfNeeded(newOpenSslSession(SSL.getSession(ssl)));
|
||||||
|
session.setLocalCertificate(local);
|
||||||
|
|
||||||
|
selectApplicationProtocol();
|
||||||
|
calculateMaxWrapOverhead();
|
||||||
|
|
||||||
|
handshakeState = HandshakeState.FINISHED;
|
||||||
return 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)
|
||||||
@ -2060,6 +2166,13 @@ 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();
|
||||||
}
|
}
|
||||||
@ -2085,377 +2198,82 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
return Buffer.address(b);
|
return Buffer.address(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class DefaultOpenSslSession implements OpenSslSession {
|
private final class DefaultExtendedOpenSslSession extends ExtendedOpenSslSession {
|
||||||
private final OpenSslSessionContext sessionContext;
|
private String[] peerSupportedSignatureAlgorithms;
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private List requestedServerNames;
|
||||||
|
|
||||||
// These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any
|
DefaultExtendedOpenSslSession(OpenSslSession wrapped) {
|
||||||
// thread.
|
super(wrapped);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) {
|
|
||||||
return new SSLSessionBindingEvent(session, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
public byte[] getId() {
|
public List getRequestedServerNames() {
|
||||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
|
||||||
if (id == null) {
|
|
||||||
return EmptyArrays.EMPTY_BYTES;
|
|
||||||
}
|
|
||||||
return id.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SSLSessionContext getSessionContext() {
|
|
||||||
return sessionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getCreationTime() {
|
|
||||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
|
||||||
if (creationTime == 0 && !isDestroyed()) {
|
|
||||||
creationTime = SSL.getTime(ssl) * 1000L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return creationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getLastAccessedTime() {
|
|
||||||
long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed;
|
|
||||||
// if lastAccessed is -1 we will just return the creation time as the handshake was not started yet.
|
|
||||||
return lastAccessed == -1 ? getCreationTime() : lastAccessed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidate() {
|
|
||||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
|
||||||
if (!isDestroyed()) {
|
|
||||||
SSL.setTimeout(ssl, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValid() {
|
|
||||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
|
||||||
if (!isDestroyed()) {
|
|
||||||
return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putValue(String name, Object value) {
|
|
||||||
requireNonNull(name, "name");
|
|
||||||
requireNonNull(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<>(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) {
|
|
||||||
requireNonNull(name, "name");
|
|
||||||
synchronized (this) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return values.get(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeValue(String name) {
|
|
||||||
requireNonNull(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 (clientMode) {
|
||||||
if (isEmpty(chain)) {
|
return Java8SslUtils.getSniHostNames(sniHostNames);
|
||||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
|
||||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
|
||||||
} else {
|
} 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) {
|
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||||
if (isEmpty(peerCerts)) {
|
if (requestedServerNames == null) {
|
||||||
throw new SSLPeerUnverifiedException("peer not verified");
|
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;
|
||||||
}
|
}
|
||||||
return peerCerts.clone();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate[] getLocalCertificates() {
|
public String[] getPeerSupportedSignatureAlgorithms() {
|
||||||
Certificate[] localCerts = ReferenceCountedOpenSslEngine.this.localCertificateChain;
|
|
||||||
if (localCerts == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return localCerts.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
|
|
||||||
synchronized (ReferenceCountedOpenSslEngine.this) {
|
synchronized (ReferenceCountedOpenSslEngine.this) {
|
||||||
if (isEmpty(x509PeerCerts)) {
|
if (peerSupportedSignatureAlgorithms == null) {
|
||||||
throw new SSLPeerUnverifiedException("peer not verified");
|
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<>(algs.length);
|
||||||
|
for (String alg: algs) {
|
||||||
|
String converted = SignatureAlgorithmConverter.toJavaName(alg);
|
||||||
|
|
||||||
|
if (converted != null) {
|
||||||
|
algorithmList.add(converted);
|
||||||
}
|
}
|
||||||
return x509PeerCerts.clone();
|
}
|
||||||
|
peerSupportedSignatureAlgorithms = algorithmList.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peerSupportedSignatureAlgorithms.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
|
public List<byte[]> getStatusResponses() {
|
||||||
Certificate[] peer = getPeerCertificates();
|
byte[] ocspResponse = null;
|
||||||
// No need for null or length > 0 is needed as this is done in getPeerCertificates()
|
if (enableOcsp && clientMode) {
|
||||||
// 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()) {
|
||||||
protocol = SSL.getVersion(ssl);
|
ocspResponse = SSL.getOcspResponse(ssl);
|
||||||
} else {
|
|
||||||
protocol = StringUtil.EMPTY_STRING;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return protocol;
|
return ocspResponse == null ?
|
||||||
}
|
Collections.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -70,13 +70,14 @@ 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, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
|
super(ciphers, cipherFilter, apn, 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();
|
||||||
}
|
}
|
||||||
@ -99,7 +100,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)
|
String keyStore, long sessionCacheSize, long sessionTimeout)
|
||||||
throws SSLException {
|
throws SSLException {
|
||||||
OpenSslKeyMaterialProvider keyMaterialProvider = null;
|
OpenSslKeyMaterialProvider keyMaterialProvider = null;
|
||||||
try {
|
try {
|
||||||
@ -186,6 +187,14 @@ 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;
|
||||||
|
|
||||||
@ -213,6 +222,7 @@ 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.
|
||||||
|
@ -892,12 +892,16 @@ 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 abstract long sessionCacheSize();
|
public 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 abstract long sessionTimeout();
|
public long sessionTimeout() {
|
||||||
|
return sessionContext().getSessionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #applicationProtocolNegotiator()} instead.
|
* @deprecated Use {@link #applicationProtocolNegotiator()} instead.
|
||||||
|
@ -396,6 +396,10 @@ 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() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ 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;
|
||||||
@ -80,4 +81,9 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ 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;
|
||||||
@ -150,6 +151,17 @@ 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);
|
||||||
|
@ -20,6 +20,7 @@ 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;
|
||||||
@ -78,4 +79,15 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ 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;
|
||||||
@ -92,4 +93,15 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ 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;
|
||||||
@ -142,6 +143,11 @@ 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);
|
||||||
|
@ -1347,4 +1347,36 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ 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;
|
||||||
@ -109,6 +110,7 @@ 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.Random;
|
import java.util.Random;
|
||||||
@ -128,6 +130,7 @@ import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_1;
|
|||||||
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2;
|
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2;
|
||||||
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_3;
|
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_3;
|
||||||
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
|
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -1278,14 +1281,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(PROTOCOL_TLS_V1_2)
|
.protocols(protocols())
|
||||||
.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(PROTOCOL_TLS_V1_2)
|
.protocols(protocols())
|
||||||
.sslContextProvider(serverSslContextProvider())
|
.sslContextProvider(serverSslContextProvider())
|
||||||
.build());
|
.build());
|
||||||
SSLEngine clientEngine = null;
|
SSLEngine clientEngine = null;
|
||||||
@ -1300,10 +1303,54 @@ public abstract class SSLEngineTest {
|
|||||||
|
|
||||||
handshake(clientEngine, serverEngine);
|
handshake(clientEngine, serverEngine);
|
||||||
|
|
||||||
|
if (protocolCipherCombo == ProtocolCipherCombo.TLSV13) {
|
||||||
|
// Allocate something which is big enough for sure
|
||||||
|
ByteBuffer packetBuffer = allocateBuffer(32 * 1024);
|
||||||
|
ByteBuffer appBuffer = allocateBuffer(32 * 1024);
|
||||||
|
|
||||||
|
appBuffer.clear().position(4).flip();
|
||||||
|
packetBuffer.clear();
|
||||||
|
|
||||||
|
do {
|
||||||
|
SSLEngineResult result;
|
||||||
|
|
||||||
|
do {
|
||||||
|
result = serverEngine.wrap(appBuffer, packetBuffer);
|
||||||
|
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||||
|
|
||||||
|
appBuffer.clear();
|
||||||
|
packetBuffer.flip();
|
||||||
|
do {
|
||||||
|
result = clientEngine.unwrap(packetBuffer, appBuffer);
|
||||||
|
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||||
|
|
||||||
|
packetBuffer.clear();
|
||||||
|
appBuffer.clear().position(4).flip();
|
||||||
|
|
||||||
|
do {
|
||||||
|
result = clientEngine.wrap(appBuffer, packetBuffer);
|
||||||
|
} while (appBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||||
|
|
||||||
|
appBuffer.clear();
|
||||||
|
packetBuffer.flip();
|
||||||
|
|
||||||
|
do {
|
||||||
|
result = serverEngine.unwrap(packetBuffer, appBuffer);
|
||||||
|
} while (packetBuffer.hasRemaining() || result.bytesProduced() > 0);
|
||||||
|
|
||||||
|
packetBuffer.clear();
|
||||||
|
appBuffer.clear().position(4).flip();
|
||||||
|
} while (clientEngine.getSession().getId().length == 0);
|
||||||
|
|
||||||
|
// With TLS1.3 we should see pseudo IDs and so these should never match.
|
||||||
|
assertFalse(Arrays.equals(clientEngine.getSession().getId(), serverEngine.getSession().getId()));
|
||||||
|
} else {
|
||||||
// After the handshake the id should have length > 0
|
// After the handshake the id should have length > 0
|
||||||
assertNotEquals(0, clientEngine.getSession().getId().length);
|
assertNotEquals(0, clientEngine.getSession().getId().length);
|
||||||
assertNotEquals(0, serverEngine.getSession().getId().length);
|
assertNotEquals(0, serverEngine.getSession().getId().length);
|
||||||
|
|
||||||
assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId());
|
assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId());
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
cleanupClientSslEngine(clientEngine);
|
cleanupClientSslEngine(clientEngine);
|
||||||
cleanupServerSslEngine(serverEngine);
|
cleanupServerSslEngine(serverEngine);
|
||||||
@ -1477,8 +1524,7 @@ 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(
|
ByteBuffer serverAppReadBuffer = allocateBuffer(serverEngine.getSession().getApplicationBufferSize());
|
||||||
serverEngine.getSession().getApplicationBufferSize());
|
|
||||||
ByteBuffer clientAppReadBuffer = allocateBuffer(
|
ByteBuffer clientAppReadBuffer = allocateBuffer(
|
||||||
clientEngine.getSession().getApplicationBufferSize());
|
clientEngine.getSession().getApplicationBufferSize());
|
||||||
|
|
||||||
@ -1492,7 +1538,6 @@ public abstract class SSLEngineTest {
|
|||||||
|
|
||||||
boolean clientHandshakeFinished = false;
|
boolean clientHandshakeFinished = false;
|
||||||
boolean serverHandshakeFinished = false;
|
boolean serverHandshakeFinished = false;
|
||||||
|
|
||||||
boolean cTOsHasRemaining;
|
boolean cTOsHasRemaining;
|
||||||
boolean sTOcHasRemaining;
|
boolean sTOcHasRemaining;
|
||||||
|
|
||||||
@ -2912,6 +2957,267 @@ 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()
|
||||||
@ -3520,7 +3826,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) throws Exception {
|
protected void initChannel(Channel ch) {
|
||||||
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 ?
|
||||||
@ -3578,7 +3884,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 final class TestTrustManagerFactory extends X509ExtendedTrustManager {
|
private static final class TestTrustManagerFactory extends X509ExtendedTrustManager {
|
||||||
private final Certificate localCert;
|
private final Certificate localCert;
|
||||||
private volatile boolean verified;
|
private volatile boolean verified;
|
||||||
|
|
||||||
|
@ -1137,25 +1137,41 @@ public class SslHandlerTest {
|
|||||||
|
|
||||||
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
||||||
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];
|
||||||
ThreadLocalRandom.current().nextBytes(bytes);
|
ThreadLocalRandom.current().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) {
|
||||||
ch.pipeline().addLast(serverSslHandler);
|
final SslHandler sslHandler = sslServerCtx.newHandler(ch.alloc());
|
||||||
|
ch.pipeline().addLast(sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT));
|
||||||
ch.pipeline().addLast(new ChannelHandler() {
|
ch.pipeline().addLast(new ChannelHandler() {
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1164,6 +1180,31 @@ 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)
|
||||||
@ -1186,11 +1227,18 @@ public class SslHandlerTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).connect(sc.localAddress());
|
}).connect(serverAddress);
|
||||||
cc = future.syncUninterruptibly().channel();
|
cc = future.syncUninterruptibly().channel();
|
||||||
|
|
||||||
assertTrue(clientSslHandler.handshakeFuture().await().isSuccess());
|
assertTrue(clientSslHandler.handshakeFuture().sync().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;
|
||||||
@ -1207,11 +1255,6 @@ 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user