Skip to content

Commit

Permalink
Support session cache for client and server when using native SSLEngi…
Browse files Browse the repository at this point in the history
…ne implementation (#10994)

Motivation:

At the moment we don't support session caching on the client side at all when using the native SSL implementation. We should at least allow to enable it.

Modification:

Allow to enable session cache for client side but disable ti by default due a JDK bug atm.

Result:

Be able to cache sessions on the client side when using native SSL implementation .
  • Loading branch information
normanmaurer committed Mar 7, 2021
1 parent 5d41611 commit 7d4aaa2
Show file tree
Hide file tree
Showing 29 changed files with 1,593 additions and 374 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
*/
package io.netty.handler.ssl;

import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.SuppressJava6Requirement;

import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
Expand Down Expand Up @@ -61,8 +63,23 @@ public List<byte[]> getStatusResponses() {
}

@Override
public final void handshakeFinished() throws SSLException {
wrapped.handshakeFinished();
public OpenSslSessionId sessionId() {
return wrapped.sessionId();
}

@Override
public void setSessionId(OpenSslSessionId id) {
wrapped.setSessionId(id);
}

@Override
public final void setLocalCertificate(Certificate[] localCertificate) {
wrapped.setLocalCertificate(localCertificate);
}

@Override
public String[] getPeerSupportedSignatureAlgorithms() {
return EmptyArrays.EMPTY_STRINGS;
}

@Override
Expand All @@ -81,7 +98,7 @@ public final byte[] getId() {
}

@Override
public final SSLSessionContext getSessionContext() {
public final OpenSslSessionContext getSessionContext() {
return wrapped.getSessionContext();
}

Expand All @@ -106,13 +123,22 @@ public final boolean isValid() {
}

@Override
public final void putValue(String s, Object o) {
wrapped.putValue(s, o);
public final void putValue(String name, Object value) {
if (value instanceof SSLSessionBindingListener) {
// Decorate the value if needed so we submit the correct SSLSession instance
value = new SSLSessionBindingListenerDecorator((SSLSessionBindingListener) value);
}
wrapped.putValue(name, value);
}

@Override
public final Object getValue(String s) {
return wrapped.getValue(s);
Object value = wrapped.getValue(s);
if (value instanceof SSLSessionBindingListenerDecorator) {
// Unwrap as needed so we return the original value
return ((SSLSessionBindingListenerDecorator) value).delegate;
}
return value;
}

@Override
Expand Down Expand Up @@ -179,4 +205,36 @@ public final int getPacketBufferSize() {
public final int getApplicationBufferSize() {
return wrapped.getApplicationBufferSize();
}

private final class SSLSessionBindingListenerDecorator implements SSLSessionBindingListener {

final SSLSessionBindingListener delegate;

SSLSessionBindingListenerDecorator(SSLSessionBindingListener delegate) {
this.delegate = delegate;
}

@Override
public void valueBound(SSLSessionBindingEvent event) {
delegate.valueBound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName()));
}

@Override
public void valueUnbound(SSLSessionBindingEvent event) {
delegate.valueUnbound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName()));
}
}

@Override
public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException {
wrapped.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
}

@Override
public String toString() {
return "ExtendedOpenSslSession{" +
"wrapped=" + wrapped +
'}';
}
}
10 changes: 0 additions & 10 deletions handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,6 @@ public final List<String> cipherSuites() {
return unmodifiableCipherSuites;
}

@Override
public final long sessionCacheSize() {
return sessionContext().getSessionCacheSize();
}

@Override
public final long sessionTimeout() {
return sessionContext().getSessionTimeout();
}

@Override
public final SSLEngine newEngine(ByteBufAllocator alloc) {
return configureAndWrapEngine(context().createSSLEngine(), alloc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,14 @@ public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory tr
long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStore,
Map.Entry<SslContextOption<?>, Object>... options)
throws SSLException {
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain,
ClientAuth.NONE, protocols, false, enableOcsp, options);
boolean success = false;
try {
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
sessionCacheSize, sessionTimeout);
success = true;
} finally {
if (!success) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;

import io.netty.internal.tcnative.SSL;
import io.netty.util.AsciiString;

import java.util.HashMap;
import java.util.Map;

/**
* {@link OpenSslSessionCache} that is used by the client-side.
*/
final class OpenSslClientSessionCache extends OpenSslSessionCache {
// TODO: Should we support to have a List of OpenSslSessions for a Host/Port key and so be able to
// support sessions for different protocols / ciphers to the same remote peer ?
private final Map<HostPort, NativeSslSession> sessions = new HashMap<HostPort, NativeSslSession>();

OpenSslClientSessionCache(OpenSslEngineMap engineMap) {
super(engineMap);
}

@Override
protected boolean sessionCreated(NativeSslSession session) {
assert Thread.holdsLock(this);
HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
if (hostPort == null || sessions.containsKey(hostPort)) {
return false;
}
sessions.put(hostPort, session);
return true;
}

@Override
protected void sessionRemoved(NativeSslSession session) {
assert Thread.holdsLock(this);
HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
if (hostPort == null) {
return;
}
sessions.remove(hostPort);
}

@Override
void setSession(long ssl, String host, int port) {
HostPort hostPort = keyFor(host, port);
if (hostPort == null) {
return;
}
final NativeSslSession session;
final boolean reused;
synchronized (this) {
session = sessions.get(hostPort);
if (session == null) {
return;
}
if (!session.isValid()) {
removeSessionWithId(session.sessionId());
return;
}
// Try to set the session, if true is returned OpenSSL incremented the reference count
// of the underlying SSL_SESSION*.
reused = SSL.setSession(ssl, session.session());
}

if (reused) {
if (session.shouldBeSingleUse()) {
// Should only be used once
session.invalidate();
}
session.updateLastAccessedTime();
}
}

private static HostPort keyFor(String host, int port) {
if (host == null && port < 1) {
return null;
}
return new HostPort(host, port);
}

@Override
synchronized void clear() {
super.clear();
sessions.clear();
}

/**
* Host / Port tuple used to find a {@link OpenSslSession} in the cache.
*/
private static final class HostPort {
private final int hash;
private final String host;
private final int port;

HostPort(String host, int port) {
this.host = host;
this.port = port;
// Calculate a hashCode that does ignore case.
this.hash = 31 * AsciiString.hashCode(host) + port;
}

@Override
public int hashCode() {
return hash;
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof HostPort)) {
return false;
}
HostPort other = (HostPort) obj;
return port == other.port && host.equalsIgnoreCase(other.host);
}

@Override
public String toString() {
return "HostPort{" +
"host='" + host + '\'' +
", port=" + port +
'}';
}
}
}
18 changes: 9 additions & 9 deletions handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@
*/
public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg,
long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
Map.Entry<SslContextOption<?>, Object>... options)
throws SSLException {
super(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
super(ciphers, cipherFilter, toNegotiator(apnCfg), mode, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, false, options);
}

OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
long sessionTimeout, int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp, Map.Entry<SslContextOption<?>, Object>... options) throws SSLException {
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols,
startTls, enableOcsp, false, options);
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
int mode, Certificate[] keyCertChain,
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
Map.Entry<SslContextOption<?>, Object>... options)
throws SSLException {
super(ciphers, cipherFilter, apn, mode, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, false, options);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,16 @@ private OpenSslServerContext(
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp, String keyStore, Map.Entry<SslContextOption<?>, Object>... options)
throws SSLException {
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain,
clientAuth, protocols, startTls, enableOcsp, options);

// Create a new SSL_CTX and configure it.
boolean success = false;
try {
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore);
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
sessionCacheSize, sessionTimeout);
success = true;
} finally {
if (!success) {
Expand Down

0 comments on commit 7d4aaa2

Please sign in to comment.