Add supported for X509ExtendedTrustManager when using OpenSslEngine

Motivation:

For some use cases X509ExtendedTrustManager is needed as it allows to also access the SslEngine during validation.

Modifications:

Add support for X509ExtendedTrustManager on java >= 7

Result:

It's now possible to use X509ExtendedTrustManager with OpenSslEngine
This commit is contained in:
Norman Maurer 2015-03-13 16:12:37 +01:00
parent bdf0bddc85
commit a2428c7e47
6 changed files with 434 additions and 332 deletions

View File

@ -17,15 +17,13 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufInputStream;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.apache.tomcat.jni.CertificateVerifier;
import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext; import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import java.io.File; import java.io.File;
@ -40,8 +38,8 @@ import java.security.cert.X509Certificate;
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/ */
public final class OpenSslClientContext extends OpenSslContext { public final class OpenSslClientContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
private final OpenSslSessionContext sessionContext; private final OpenSslSessionContext sessionContext;
private final OpenSslEngineMap engineMap;
/** /**
* Creates a new instance. * Creates a new instance.
@ -132,19 +130,26 @@ public final class OpenSslClientContext extends OpenSslContext {
initTrustManagerFactory(certChainFile, trustManagerFactory); initTrustManagerFactory(certChainFile, trustManagerFactory);
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() { engineMap = newEngineMap(manager);
@Override
public boolean verify(long ssl, byte[][] chain, String auth) { // Use this to prevent an error when running on java < 7
X509Certificate[] peerCerts = certificates(chain); if (useExtendedTrustManager(manager)) {
try { final X509ExtendedTrustManager extendedManager = (X509ExtendedTrustManager) manager;
manager.checkServerTrusted(peerCerts, auth); SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() {
return true; @Override
} catch (Exception e) { void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception {
logger.debug("verification of certificate failed", e); OpenSslEngine engine = engineMap.remove(ssl);
extendedManager.checkServerTrusted(peerCerts, auth, engine);
} }
return false; });
} } else {
}); SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() {
@Override
void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception {
manager.checkServerTrusted(peerCerts, auth);
}
});
}
} catch (Exception e) { } catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e); throw new SSLException("unable to setup trustmanager", e);
} }
@ -185,6 +190,11 @@ public final class OpenSslClientContext extends OpenSslContext {
return sessionContext; return sessionContext;
} }
@Override
OpenSslEngineMap engineMap() {
return engineMap;
}
// No cache is currently supported for client side mode. // No cache is currently supported for client side mode.
private static final class OpenSslClientSessionContext extends OpenSslSessionContext { private static final class OpenSslClientSessionContext extends OpenSslSessionContext {
private OpenSslClientSessionContext(long context) { private OpenSslClientSessionContext(long context) {

View File

@ -19,6 +19,7 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.PlatformDependent; 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.CertificateVerifier;
import org.apache.tomcat.jni.Pool; import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext; import org.apache.tomcat.jni.SSLContext;
@ -26,11 +27,13 @@ import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
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 static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNull;
@ -226,13 +229,20 @@ public abstract class OpenSslContext extends SslContext {
@Override @Override
public final SSLEngine newEngine(ByteBufAllocator alloc) { public final SSLEngine newEngine(ByteBufAllocator alloc) {
List<String> protos = applicationProtocolNegotiator().protocols(); List<String> protos = applicationProtocolNegotiator().protocols();
OpenSslEngineMap engineMap = engineMap();
final OpenSslEngine engine;
if (protos.isEmpty()) { if (protos.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null, isClient(), sessionContext()); engine = new OpenSslEngine(ctx, alloc, null, isClient(), sessionContext(), engineMap);
} else { } else {
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), sessionContext()); engine = new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(),
sessionContext(), engineMap);
} }
engineMap.add(engine);
return engine;
} }
abstract OpenSslEngineMap engineMap();
/** /**
* Returns the {@code SSL_CTX} object of this context. * Returns the {@code SSL_CTX} object of this context.
*/ */
@ -290,7 +300,7 @@ public abstract class OpenSslContext extends SslContext {
} }
protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { protected static X509TrustManager chooseTrustManager(TrustManager[] managers) {
for (TrustManager m: managers) { for (TrustManager m : managers) {
if (m instanceof X509TrustManager) { if (m instanceof X509TrustManager) {
return (X509TrustManager) m; return (X509TrustManager) m;
} }
@ -333,4 +343,44 @@ public abstract class OpenSslContext extends SslContext {
.append(config.protocol()).append(" protocol").toString()); .append(config.protocol()).append(" protocol").toString());
} }
} }
static OpenSslEngineMap newEngineMap(X509TrustManager trustManager) {
if (useExtendedTrustManager(trustManager)) {
return new DefaultOpenSslEngineMap();
}
return OpenSslEngineMap.EMPTY;
}
static boolean useExtendedTrustManager(X509TrustManager trustManager) {
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
}
abstract static class AbstractCertificateVerifier implements CertificateVerifier {
@Override
public final boolean verify(long ssl, byte[][] chain, String auth) {
X509Certificate[] peerCerts = certificates(chain);
try {
verify(ssl, peerCerts, auth);
return true;
} catch (Exception e) {
logger.debug("verification of certificate failed", e);
}
return false;
}
abstract void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception;
}
private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
private final Map<Long, OpenSslEngine> engines = PlatformDependent.newConcurrentHashMap();
@Override
public OpenSslEngine remove(long ssl) {
return engines.remove(ssl);
}
@Override
public void add(OpenSslEngine engine) {
engines.put(engine.ssl(), engine);
}
}
} }

View File

@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent; 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;
@ -47,7 +48,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
import static javax.net.ssl.SSLEngineResult.Status.*; import static javax.net.ssl.SSLEngineResult.Status.*;
@ -75,12 +75,6 @@ public final class OpenSslEngine extends SSLEngine {
destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
} }
DESTROYED_UPDATER = destroyedUpdater; 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
@ -117,7 +111,6 @@ public final class OpenSslEngine extends SSLEngine {
} }
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER; private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER;
private static final AtomicReferenceFieldUpdater<OpenSslEngine, SSLSession> SESSION_UPDATER;
private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL"; private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
@ -154,9 +147,9 @@ public final class OpenSslEngine extends SSLEngine {
private final ByteBufAllocator alloc; private final ByteBufAllocator alloc;
private final String fallbackApplicationProtocol; private final String fallbackApplicationProtocol;
private final OpenSslSessionContext sessionContext; private final OpenSslSessionContext sessionContext;
private final OpenSslEngineMap engineMap;
@SuppressWarnings("unused") private final SSLSession session = new OpenSslSession();
private volatile SSLSession session;
/** /**
* Creates a new instance * Creates a new instance
@ -166,7 +159,7 @@ public final class OpenSslEngine extends SSLEngine {
*/ */
@Deprecated @Deprecated
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) {
this(sslCtx, alloc, fallbackApplicationProtocol, false, null); this(sslCtx, alloc, fallbackApplicationProtocol, false, null, OpenSslEngineMap.EMPTY);
} }
/** /**
@ -178,21 +171,33 @@ public final class OpenSslEngine extends SSLEngine {
* @param sessionContext the {@link OpenSslSessionContext} this {@link SSLEngine} belongs to. * @param sessionContext the {@link OpenSslSessionContext} this {@link SSLEngine} belongs to.
*/ */
OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol,
boolean clientMode, OpenSslSessionContext sessionContext) { boolean clientMode, OpenSslSessionContext sessionContext, OpenSslEngineMap engineMap) {
OpenSsl.ensureAvailability(); OpenSsl.ensureAvailability();
if (sslCtx == 0) { if (sslCtx == 0) {
throw new NullPointerException("sslContext"); throw new NullPointerException("sslCtx");
}
if (alloc == null) {
throw new NullPointerException("alloc");
} }
this.alloc = alloc; this.alloc = ObjectUtil.checkNotNull(alloc, "alloc");
ssl = SSL.newSSL(sslCtx, !clientMode); ssl = SSL.newSSL(sslCtx, !clientMode);
networkBIO = SSL.makeNetworkBIO(ssl); networkBIO = SSL.makeNetworkBIO(ssl);
this.fallbackApplicationProtocol = fallbackApplicationProtocol; this.fallbackApplicationProtocol = fallbackApplicationProtocol;
this.clientMode = clientMode; this.clientMode = clientMode;
this.sessionContext = sessionContext; this.sessionContext = sessionContext;
this.engineMap = engineMap;
}
@Override
public SSLSession getHandshakeSession() {
if (accepted > 0) {
// handshake started we are able to return the session.
return session;
}
// As stated by the javadocs of getHandshakeSession() we should return null if the handshake not started yet.
return null;
}
long ssl() {
return ssl;
} }
/** /**
@ -200,6 +205,7 @@ public final class OpenSslEngine extends SSLEngine {
*/ */
public synchronized void shutdown() { public synchronized void shutdown() {
if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) { if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
engineMap.remove(ssl);
SSL.freeSSL(ssl); SSL.freeSSL(ssl);
SSL.freeBIO(networkBIO); SSL.freeBIO(networkBIO);
ssl = networkBIO = 0; ssl = networkBIO = 0;
@ -733,9 +739,7 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public void setEnabledCipherSuites(String[] cipherSuites) { public void setEnabledCipherSuites(String[] cipherSuites) {
if (cipherSuites == null) { ObjectUtil.checkNotNull(cipherSuites, "cipherSuites");
throw new NullPointerException("cipherSuites");
}
final StringBuilder buf = new StringBuilder(); final StringBuilder buf = new StringBuilder();
for (String c: cipherSuites) { for (String c: cipherSuites) {
@ -850,283 +854,8 @@ public final class OpenSslEngine extends SSLEngine {
} }
} }
private Certificate[] initPeerCertChain() throws SSLPeerUnverifiedException {
byte[][] chain = SSL.getPeerCertChain(ssl);
byte[] clientCert;
if (!clientMode) {
// 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
clientCert = SSL.getPeerCertificate(ssl);
} else {
clientCert = null;
}
if (chain == null && clientCert == null) {
throw new SSLPeerUnverifiedException("peer not verified");
}
int len = 0;
if (chain != null) {
len += chain.length;
}
int i = 0;
Certificate[] peerCerts;
if (clientCert != null) {
len++;
peerCerts = new Certificate[len];
peerCerts[i++] = new OpenSslX509Certificate(clientCert);
} else {
peerCerts = new Certificate[len];
}
if (chain != null) {
int a = 0;
for (; i < peerCerts.length; i++) {
peerCerts[i] = new OpenSslX509Certificate(chain[a++]);
}
}
return peerCerts;
}
@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;
if (session == null) {
session = new SSLSession() {
// SSLSession implementation seems to not need to be thread-safe so no need for volatile etc.
private X509Certificate[] x509PeerCerts;
// lazy init for memory reasons
private Map<String, Object> values;
@Override
public byte[] getId() {
// We don't cache that to keep memory usage to a minimum.
byte[] id = SSL.getSessionId(ssl);
if (id == null) {
// The id should never be null, if it was null then the SESSION itself was not valid.
throw new IllegalStateException("SSL session ID not available");
}
return id;
}
@Override
public SSLSessionContext getSessionContext() {
return sessionContext;
}
@Override
public long getCreationTime() {
// We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds.
return SSL.getTime(ssl) * 1000L;
}
@Override
public long getLastAccessedTime() {
// TODO: Add proper implementation
return getCreationTime();
}
@Override
public void invalidate() {
// NOOP
}
@Override
public boolean isValid() {
return false;
}
@Override
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
public Object getValue(String name) {
if (name == null) {
throw new NullPointerException("name");
}
if (values == null) {
return null;
}
return values.get(name);
}
@Override
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
public String[] getValueNames() {
Map<String, Object> values = this.values;
if (values == null || values.isEmpty()) {
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
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
// these are lazy created to reduce memory overhead
Certificate[] c = peerCerts;
if (c == null) {
if (SSL.isInInit(ssl) != 0) {
throw new SSLPeerUnverifiedException("peer not verified");
}
c = peerCerts = initPeerCertChain();
}
return c;
}
@Override
public Certificate[] getLocalCertificates() {
// TODO: Find out how to get these
return EMPTY_CERTIFICATES;
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
// these are lazy created to reduce memory overhead
X509Certificate[] c = x509PeerCerts;
if (c == null) {
if (SSL.isInInit(ssl) != 0) {
throw new SSLPeerUnverifiedException("peer not verified");
}
byte[][] chain = SSL.getPeerCertChain(ssl);
if (chain == null) {
throw new SSLPeerUnverifiedException("peer not verified");
}
X509Certificate[] peerCerts = new X509Certificate[chain.length];
for (int i = 0; i < peerCerts.length; i++) {
try {
peerCerts[i] = X509Certificate.getInstance(chain[i]);
} catch (CertificateException e) {
throw new IllegalStateException(e);
}
}
c = x509PeerCerts = peerCerts;
}
return c;
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] peer = getPeerCertificates();
if (peer == null || peer.length == 0) {
return null;
}
return principal(peer);
}
@Override
public Principal getLocalPrincipal() {
Certificate[] local = getLocalCertificates();
if (local == null || local.length == 0) {
return null;
}
return principal(local);
}
private Principal principal(Certificate[] certs) {
return ((java.security.cert.X509Certificate) certs[0]).getIssuerX500Principal();
}
@Override
public String getCipherSuite() {
if (!handshakeFinished) {
return INVALID_CIPHER;
}
if (cipher == null) {
String c = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
if (c != null) {
cipher = c;
}
}
return cipher;
}
@Override
public String getProtocol() {
String applicationProtocol = OpenSslEngine.this.applicationProtocol;
if (applicationProtocol == null) {
applicationProtocol = SSL.getNextProtoNegotiated(ssl);
if (applicationProtocol == null) {
applicationProtocol = fallbackApplicationProtocol;
}
if (applicationProtocol != null) {
OpenSslEngine.this.applicationProtocol = applicationProtocol.replace(':', '_');
} else {
OpenSslEngine.this.applicationProtocol = applicationProtocol = "";
}
}
String version = SSL.getVersion(ssl);
if (applicationProtocol.isEmpty()) {
return version;
} else {
return version + ':' + applicationProtocol;
}
}
@Override
public String getPeerHost() {
return null;
}
@Override
public int getPeerPort() {
return 0;
}
@Override
public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET_LENGTH;
}
@Override
public int getApplicationBufferSize() {
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;
} }
@ -1349,4 +1078,265 @@ public final class OpenSslEngine extends SSLEngine {
// Call shutdown as the user may have created the OpenSslEngine and not used it at all. // Call shutdown as the user may have created the OpenSslEngine and not used it at all.
shutdown(); shutdown();
} }
private final class OpenSslSession implements SSLSession {
// SSLSession implementation seems to not need to be thread-safe so no need for volatile etc.
private X509Certificate[] x509PeerCerts;
// lazy init for memory reasons
private Map<String, Object> values;
@Override
public byte[] getId() {
// We don't cache that to keep memory usage to a minimum.
byte[] id = SSL.getSessionId(ssl);
if (id == null) {
// The id should never be null, if it was null then the SESSION itself was not valid.
throw new IllegalStateException("SSL session ID not available");
}
return id;
}
@Override
public SSLSessionContext getSessionContext() {
return sessionContext;
}
@Override
public long getCreationTime() {
// We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds.
return SSL.getTime(ssl) * 1000L;
}
@Override
public long getLastAccessedTime() {
// TODO: Add proper implementation
return getCreationTime();
}
@Override
public void invalidate() {
// NOOP
}
@Override
public boolean isValid() {
return false;
}
@Override
public void putValue(String name, Object value) {
ObjectUtil.checkNotNull(name, "name");
ObjectUtil.checkNotNull(value, "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
public Object getValue(String name) {
ObjectUtil.checkNotNull(name, "name");
if (values == null) {
return null;
}
return values.get(name);
}
@Override
public void removeValue(String name) {
ObjectUtil.checkNotNull(name, "name");
Map<String, Object> values = this.values;
if (values == null) {
return;
}
Object old = values.remove(name);
notifyUnbound(old, name);
}
@Override
public String[] getValueNames() {
Map<String, Object> values = this.values;
if (values == null || values.isEmpty()) {
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
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
// these are lazy created to reduce memory overhead
Certificate[] c = peerCerts;
if (c == null) {
if (SSL.isInInit(ssl) != 0) {
throw new SSLPeerUnverifiedException("peer not verified");
}
c = peerCerts = initPeerCertChain();
}
return c;
}
private Certificate[] initPeerCertChain() throws SSLPeerUnverifiedException {
byte[][] chain = SSL.getPeerCertChain(ssl);
byte[] clientCert;
if (!clientMode) {
// 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
clientCert = SSL.getPeerCertificate(ssl);
} else {
clientCert = null;
}
if (chain == null && clientCert == null) {
throw new SSLPeerUnverifiedException("peer not verified");
}
int len = 0;
if (chain != null) {
len += chain.length;
}
int i = 0;
Certificate[] peerCerts;
if (clientCert != null) {
len++;
peerCerts = new Certificate[len];
peerCerts[i++] = new OpenSslX509Certificate(clientCert);
} else {
peerCerts = new Certificate[len];
}
if (chain != null) {
int a = 0;
for (; i < peerCerts.length; i++) {
peerCerts[i] = new OpenSslX509Certificate(chain[a++]);
}
}
return peerCerts;
}
@Override
public Certificate[] getLocalCertificates() {
// TODO: Find out how to get these
return EMPTY_CERTIFICATES;
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
// these are lazy created to reduce memory overhead
X509Certificate[] c = x509PeerCerts;
if (c == null) {
if (SSL.isInInit(ssl) != 0) {
throw new SSLPeerUnverifiedException("peer not verified");
}
byte[][] chain = SSL.getPeerCertChain(ssl);
if (chain == null) {
throw new SSLPeerUnverifiedException("peer not verified");
}
X509Certificate[] peerCerts = new X509Certificate[chain.length];
for (int i = 0; i < peerCerts.length; i++) {
try {
peerCerts[i] = X509Certificate.getInstance(chain[i]);
} catch (CertificateException e) {
throw new IllegalStateException(e);
}
}
c = x509PeerCerts = peerCerts;
}
return c;
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] peer = getPeerCertificates();
if (peer == null || peer.length == 0) {
return null;
}
return principal(peer);
}
@Override
public Principal getLocalPrincipal() {
Certificate[] local = getLocalCertificates();
if (local == null || local.length == 0) {
return null;
}
return principal(local);
}
private Principal principal(Certificate[] certs) {
return ((java.security.cert.X509Certificate) certs[0]).getIssuerX500Principal();
}
@Override
public String getCipherSuite() {
if (!handshakeFinished) {
return INVALID_CIPHER;
}
if (cipher == null) {
String c = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
if (c != null) {
cipher = c;
}
}
return cipher;
}
@Override
public String getProtocol() {
String applicationProtocol = OpenSslEngine.this.applicationProtocol;
if (applicationProtocol == null) {
applicationProtocol = SSL.getNextProtoNegotiated(ssl);
if (applicationProtocol == null) {
applicationProtocol = fallbackApplicationProtocol;
}
if (applicationProtocol != null) {
OpenSslEngine.this.applicationProtocol = applicationProtocol.replace(':', '_');
} else {
OpenSslEngine.this.applicationProtocol = applicationProtocol = "";
}
}
String version = SSL.getVersion(ssl);
if (applicationProtocol.isEmpty()) {
return version;
} else {
return version + ':' + applicationProtocol;
}
}
@Override
public String getPeerHost() {
return null;
}
@Override
public int getPeerPort() {
return 0;
}
@Override
public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET_LENGTH;
}
@Override
public int getApplicationBufferSize() {
return MAX_PLAINTEXT_LENGTH;
}
}
} }

View File

@ -0,0 +1,42 @@
/*
* Copyright 2014 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;
interface OpenSslEngineMap {
OpenSslEngineMap EMPTY = new OpenSslEngineMap() {
@Override
public OpenSslEngine remove(long ssl) {
return null;
}
@Override
public void add(OpenSslEngine engine) {
// NOOP
}
};
/**
* Remove the {@link OpenSslEngine} with the given {@code ssl} address and
* return it.
*/
OpenSslEngine remove(long ssl);
/**
* Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}.
*/
void add(OpenSslEngine engine);
}

View File

@ -17,14 +17,12 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufInputStream;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.apache.tomcat.jni.CertificateVerifier;
import org.apache.tomcat.jni.SSL; import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext; import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.io.File; import java.io.File;
import java.security.KeyFactory; import java.security.KeyFactory;
@ -44,9 +42,8 @@ import static io.netty.util.internal.ObjectUtil.*;
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/ */
public final class OpenSslServerContext extends OpenSslContext { public final class OpenSslServerContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
private final OpenSslServerSessionContext sessionContext; private final OpenSslServerSessionContext sessionContext;
private final OpenSslEngineMap engineMap;
/** /**
* Creates a new instance. * Creates a new instance.
@ -258,19 +255,26 @@ public final class OpenSslServerContext extends OpenSslContext {
} }
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
@Override engineMap = newEngineMap(manager);
public boolean verify(long ssl, byte[][] chain, String auth) { // Use this to prevent an error when running on java < 7
X509Certificate[] peerCerts = certificates(chain); if (useExtendedTrustManager(manager)) {
try { final X509ExtendedTrustManager extendedManager = (X509ExtendedTrustManager) manager;
manager.checkClientTrusted(peerCerts, auth); SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() {
return true; @Override
} catch (Exception e) { void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception {
logger.debug("verification of certificate failed", e); OpenSslEngine engine = engineMap.remove(ssl);
extendedManager.checkClientTrusted(peerCerts, auth, engine);
} }
return false; });
} } else {
}); SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() {
@Override
void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception {
manager.checkClientTrusted(peerCerts, auth);
}
});
}
} catch (Exception e) { } catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e); throw new SSLException("unable to setup trustmanager", e);
} }
@ -288,4 +292,9 @@ public final class OpenSslServerContext extends OpenSslContext {
public OpenSslServerSessionContext sessionContext() { public OpenSslServerSessionContext sessionContext() {
return sessionContext; return sessionContext;
} }
@Override
OpenSslEngineMap engineMap() {
return engineMap;
}
} }

View File

@ -968,8 +968,9 @@
<ignore>sun.security.x509.X509CertInfo</ignore> <ignore>sun.security.x509.X509CertInfo</ignore>
<ignore>sun.security.x509.X509CertImpl</ignore> <ignore>sun.security.x509.X509CertImpl</ignore>
<!-- SSLSession implelementation --> <!-- SSLSession implementation -->
<ignore>javax.net.ssl.SSLEngine</ignore> <ignore>javax.net.ssl.SSLEngine</ignore>
<ignore>javax.net.ssl.X509ExtendedTrustManager</ignore>
</ignores> </ignores>
</configuration> </configuration>
<executions> <executions>