More complete OpenSslEngine SSLSession implementation

Motivation:

The current SSLSession implementation used by OpenSslEngine does not support various operations and so may not be a good replacement by the SSLEngine provided by the JDK implementation.

Modifications:

- Add SSLSession.getCreationTime()
- Add SSLSession.getLastAccessedTime()
- Add SSLSession.putValue(...), getValue(...), removeValue(...), getValueNames()
- Add correct SSLSession.getProtocol()
- Ensure OpenSSLEngine.getSession() is thread-safe
- Use optimized AtomicIntegerFieldUpdater when possible

Result:

More complete OpenSslEngine SSLSession implementation
This commit is contained in:
Norman Maurer 2014-10-20 09:59:39 +02:00 committed by Norman Maurer
parent 6600ea3fac
commit 9ff689331c

View File

@ -18,6 +18,7 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
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 org.apache.tomcat.jni.Buffer; import org.apache.tomcat.jni.Buffer;
@ -28,6 +29,8 @@ import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException; 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.net.ssl.SSLSessionContext;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
@ -38,8 +41,11 @@ import java.security.Principal;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
@ -61,6 +67,19 @@ public final class OpenSslEngine extends SSLEngine {
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
ENCRYPTED_PACKET_OVERSIZED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); ENCRYPTED_PACKET_OVERSIZED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
AtomicIntegerFieldUpdater<OpenSslEngine> destroyedUpdater =
PlatformDependent.newAtomicIntegerFieldUpdater(OpenSslEngine.class, "destroyed");
if (destroyedUpdater == null) {
destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
}
DESTROYED_UPDATER = destroyedUpdater;
AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> sessionUpdater =
PlatformDependent.newAtomicReferenceFieldUpdater(OpenSslEngine.class, "session");
if (sessionUpdater == null) {
sessionUpdater = AtomicReferenceFieldUpdater.newUpdater(OpenSslEngine.class, SSLSession.class, "session");
}
SESSION_UPDATER = sessionUpdater;
} }
private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14 private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
@ -72,8 +91,8 @@ public final class OpenSslEngine extends SSLEngine {
static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH; static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH;
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER = private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER;
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); private static final AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> SESSION_UPDATER;
private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-"); private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-");
// OpenSSL state // OpenSSL state
@ -93,8 +112,9 @@ public final class OpenSslEngine extends SSLEngine {
// See http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#getSession() // See http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#getSession()
private volatile String cipher = "SSL_NULL_WITH_NULL_NULL"; private volatile String cipher = "SSL_NULL_WITH_NULL_NULL";
private volatile String applicationProtocol; private volatile String applicationProtocol;
// We store this outside of the SslSession so we not need to create an instance during verifyCertificates(...)
private volatile Certificate[] peerCerts; private volatile Certificate[] peerCerts;
private volatile X509Certificate[] x509PeerCerts;
// SSL Engine status variables // SSL Engine status variables
private boolean isInboundDone; private boolean isInboundDone;
@ -107,7 +127,9 @@ public final class OpenSslEngine extends SSLEngine {
private final boolean clientMode; private final boolean clientMode;
private final ByteBufAllocator alloc; private final ByteBufAllocator alloc;
private final String fallbackApplicationProtocol; private final String fallbackApplicationProtocol;
private SSLSession session;
@SuppressWarnings("unused")
private volatile SSLSession session;
/** /**
* Creates a new instance * Creates a new instance
@ -681,10 +703,16 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public SSLSession getSession() { public SSLSession getSession() {
// A other methods on SSLEngine are thread-safe we also need to make this thread-safe...
SSLSession session = this.session; SSLSession session = this.session;
if (session == null) { if (session == null) {
this.session = session = new SSLSession() { session = new SSLSession() {
private volatile byte[] id; // SSLSession implementation seems to not need to be thread-safe so no need for volatile etc.
private byte[] id;
private X509Certificate[] x509PeerCerts;
// lazy init for memory reasons
private Map<String, Object> values;
@Override @Override
public byte[] getId() { public byte[] getId() {
@ -702,16 +730,19 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public long getCreationTime() { public long getCreationTime() {
return 0; // We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds.
return SSL.getTime(ssl) * 1000L;
} }
@Override @Override
public long getLastAccessedTime() { public long getLastAccessedTime() {
return 0; // TODO: Add proper implementation
return getCreationTime();
} }
@Override @Override
public void invalidate() { public void invalidate() {
// NOOP
} }
@Override @Override
@ -720,22 +751,63 @@ public final class OpenSslEngine extends SSLEngine {
} }
@Override @Override
public void putValue(String s, Object o) { public void putValue(String name, Object value) {
if (name == null) {
throw new NullPointerException("name");
}
if (value == null) {
throw new NullPointerException("value");
}
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);
}
Object old = values.put(name, value);
if (value instanceof SSLSessionBindingListener) {
((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
}
notifyUnbound(old, name);
} }
@Override @Override
public Object getValue(String s) { public Object getValue(String name) {
if (name == null) {
throw new NullPointerException("name");
}
if (values == null) {
return null; return null;
} }
return values.get(name);
}
@Override @Override
public void removeValue(String s) { public void removeValue(String name) {
if (name == null) {
throw new NullPointerException("name");
}
Map<String, Object> values = this.values;
if (values == null) {
return;
}
Object old = values.remove(name);
notifyUnbound(old, name);
} }
@Override @Override
public String[] getValueNames() { public String[] getValueNames() {
Map<String, Object> values = this.values;
if (values == null || values.isEmpty()) {
return EmptyArrays.EMPTY_STRINGS; return EmptyArrays.EMPTY_STRINGS;
} }
return values.keySet().toArray(new String[values.size()]);
}
private void notifyUnbound(Object value, String name) {
if (value instanceof SSLSessionBindingListener) {
((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name));
}
}
@Override @Override
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
@ -752,6 +824,7 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public Certificate[] getLocalCertificates() { public Certificate[] getLocalCertificates() {
// TODO: Find out how to get these
return EMPTY_CERTIFICATES; return EMPTY_CERTIFICATES;
} }
@ -781,14 +854,26 @@ public final class OpenSslEngine extends SSLEngine {
} }
@Override @Override
public Principal getPeerPrincipal() { public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] peer = getPeerCertificates();
if (peer == null || peer.length == 0) {
return null; return null;
} }
return principal(peer);
}
@Override @Override
public Principal getLocalPrincipal() { public Principal getLocalPrincipal() {
Certificate[] local = getLocalCertificates();
if (local == null || local.length == 0) {
return null; return null;
} }
return principal(local);
}
private Principal principal(Certificate[] certs) {
return ((java.security.cert.X509Certificate) certs[0]).getIssuerX500Principal();
}
@Override @Override
public String getCipherSuite() { public String getCipherSuite() {
@ -797,12 +882,12 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public String getProtocol() { public String getProtocol() {
// TODO: Figure out how to get the current protocol.
String applicationProtocol = OpenSslEngine.this.applicationProtocol; String applicationProtocol = OpenSslEngine.this.applicationProtocol;
String version = SSL.getVersion(ssl);
if (applicationProtocol == null) { if (applicationProtocol == null) {
return "unknown"; return version;
} else { } else {
return "unknown:" + applicationProtocol; return version + ':' + applicationProtocol;
} }
} }
@ -826,6 +911,11 @@ public final class OpenSslEngine extends SSLEngine {
return MAX_PLAINTEXT_LENGTH; return MAX_PLAINTEXT_LENGTH;
} }
}; };
if (!SESSION_UPDATER.compareAndSet(this, null, session)) {
// Was lazy created in the meantime so get the current reference.
session = this.session;
}
} }
return session; return session;