From a2428c7e47aa3ebd172f361e5f2b498919062945 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 13 Mar 2015 16:12:37 +0100 Subject: [PATCH] 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 --- .../handler/ssl/OpenSslClientContext.java | 42 +- .../io/netty/handler/ssl/OpenSslContext.java | 56 +- .../io/netty/handler/ssl/OpenSslEngine.java | 580 +++++++++--------- .../netty/handler/ssl/OpenSslEngineMap.java | 42 ++ .../handler/ssl/OpenSslServerContext.java | 43 +- pom.xml | 3 +- 6 files changed, 434 insertions(+), 332 deletions(-) create mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java index cf0952fba9..e3582222a4 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java @@ -17,15 +17,13 @@ package io.netty.handler.ssl; import io.netty.buffer.ByteBuf; 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.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; 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. */ public final class OpenSslClientContext extends OpenSslContext { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class); private final OpenSslSessionContext sessionContext; + private final OpenSslEngineMap engineMap; /** * Creates a new instance. @@ -132,19 +130,26 @@ public final class OpenSslClientContext extends OpenSslContext { initTrustManagerFactory(certChainFile, trustManagerFactory); final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); - SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() { - @Override - public boolean verify(long ssl, byte[][] chain, String auth) { - X509Certificate[] peerCerts = certificates(chain); - try { - manager.checkServerTrusted(peerCerts, auth); - return true; - } catch (Exception e) { - logger.debug("verification of certificate failed", e); + engineMap = newEngineMap(manager); + + // Use this to prevent an error when running on java < 7 + if (useExtendedTrustManager(manager)) { + final X509ExtendedTrustManager extendedManager = (X509ExtendedTrustManager) manager; + SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() { + @Override + void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception { + 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) { throw new SSLException("unable to setup trustmanager", e); } @@ -185,6 +190,11 @@ public final class OpenSslClientContext extends OpenSslContext { return sessionContext; } + @Override + OpenSslEngineMap engineMap() { + return engineMap; + } + // No cache is currently supported for client side mode. private static final class OpenSslClientSessionContext extends OpenSslSessionContext { private OpenSslClientSessionContext(long context) { diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java index 8a1b5f04a7..4080b01f8e 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.util.internal.PlatformDependent; 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.Pool; import org.apache.tomcat.jni.SSL; 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.SSLException; import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static io.netty.util.internal.ObjectUtil.checkNotNull; @@ -226,13 +229,20 @@ public abstract class OpenSslContext extends SslContext { @Override public final SSLEngine newEngine(ByteBufAllocator alloc) { List protos = applicationProtocolNegotiator().protocols(); + OpenSslEngineMap engineMap = engineMap(); + final OpenSslEngine engine; if (protos.isEmpty()) { - return new OpenSslEngine(ctx, alloc, null, isClient(), sessionContext()); + engine = new OpenSslEngine(ctx, alloc, null, isClient(), sessionContext(), engineMap); } 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. */ @@ -290,7 +300,7 @@ public abstract class OpenSslContext extends SslContext { } protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { - for (TrustManager m: managers) { + for (TrustManager m : managers) { if (m instanceof X509TrustManager) { return (X509TrustManager) m; } @@ -333,4 +343,44 @@ public abstract class OpenSslContext extends SslContext { .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 engines = PlatformDependent.newConcurrentHashMap(); + @Override + public OpenSslEngine remove(long ssl) { + return engines.remove(ssl); + } + + @Override + public void add(OpenSslEngine engine) { + engines.put(engine.ssl(), engine); + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java index 3f331e3f3e..8e1066fa7f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -47,7 +48,6 @@ import java.util.List; import java.util.Map; import java.util.Set; 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.Status.*; @@ -75,12 +75,6 @@ public final class OpenSslEngine extends SSLEngine { destroyedUpdater = AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed"); } DESTROYED_UPDATER = destroyedUpdater; - AtomicReferenceFieldUpdater 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 @@ -117,7 +111,6 @@ public final class OpenSslEngine extends SSLEngine { } private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER; - private static final AtomicReferenceFieldUpdater SESSION_UPDATER; 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 String fallbackApplicationProtocol; private final OpenSslSessionContext sessionContext; + private final OpenSslEngineMap engineMap; - @SuppressWarnings("unused") - private volatile SSLSession session; + private final SSLSession session = new OpenSslSession(); /** * Creates a new instance @@ -166,7 +159,7 @@ public final class OpenSslEngine extends SSLEngine { */ @Deprecated 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. */ OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, - boolean clientMode, OpenSslSessionContext sessionContext) { + boolean clientMode, OpenSslSessionContext sessionContext, OpenSslEngineMap engineMap) { OpenSsl.ensureAvailability(); if (sslCtx == 0) { - throw new NullPointerException("sslContext"); - } - if (alloc == null) { - throw new NullPointerException("alloc"); + throw new NullPointerException("sslCtx"); } - this.alloc = alloc; + this.alloc = ObjectUtil.checkNotNull(alloc, "alloc"); ssl = SSL.newSSL(sslCtx, !clientMode); networkBIO = SSL.makeNetworkBIO(ssl); this.fallbackApplicationProtocol = fallbackApplicationProtocol; this.clientMode = clientMode; 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() { if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) { + engineMap.remove(ssl); SSL.freeSSL(ssl); SSL.freeBIO(networkBIO); ssl = networkBIO = 0; @@ -733,9 +739,7 @@ public final class OpenSslEngine extends SSLEngine { @Override public void setEnabledCipherSuites(String[] cipherSuites) { - if (cipherSuites == null) { - throw new NullPointerException("cipherSuites"); - } + ObjectUtil.checkNotNull(cipherSuites, "cipherSuites"); final StringBuilder buf = new StringBuilder(); 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 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 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 values = this.values; - if (values == null) { - // Use size of 2 to keep the memory overhead small - values = this.values = new HashMap(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 values = this.values; - if (values == null) { - return; - } - Object old = values.remove(name); - notifyUnbound(old, name); - } - - @Override - public String[] getValueNames() { - Map 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; } @@ -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. 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 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 values = this.values; + if (values == null) { + // Use size of 2 to keep the memory overhead small + values = this.values = new HashMap(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 values = this.values; + if (values == null) { + return; + } + Object old = values.remove(name); + notifyUnbound(old, name); + } + + @Override + public String[] getValueNames() { + Map 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; + } + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java new file mode 100644 index 0000000000..382a28d6c5 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java @@ -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); +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java index f69b4d2577..fd4f1e43d3 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java @@ -17,14 +17,12 @@ package io.netty.handler.ssl; import io.netty.buffer.ByteBuf; 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.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import java.io.File; 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. */ public final class OpenSslServerContext extends OpenSslContext { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class); - private final OpenSslServerSessionContext sessionContext; + private final OpenSslEngineMap engineMap; /** * Creates a new instance. @@ -258,19 +255,26 @@ public final class OpenSslServerContext extends OpenSslContext { } final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); - SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() { - @Override - public boolean verify(long ssl, byte[][] chain, String auth) { - X509Certificate[] peerCerts = certificates(chain); - try { - manager.checkClientTrusted(peerCerts, auth); - return true; - } catch (Exception e) { - logger.debug("verification of certificate failed", e); + + engineMap = newEngineMap(manager); + // Use this to prevent an error when running on java < 7 + if (useExtendedTrustManager(manager)) { + final X509ExtendedTrustManager extendedManager = (X509ExtendedTrustManager) manager; + SSLContext.setCertVerifyCallback(ctx, new AbstractCertificateVerifier() { + @Override + void verify(long ssl, X509Certificate[] peerCerts, String auth) throws Exception { + 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) { throw new SSLException("unable to setup trustmanager", e); } @@ -288,4 +292,9 @@ public final class OpenSslServerContext extends OpenSslContext { public OpenSslServerSessionContext sessionContext() { return sessionContext; } + + @Override + OpenSslEngineMap engineMap() { + return engineMap; + } } diff --git a/pom.xml b/pom.xml index 6d5716915c..0ba881894e 100644 --- a/pom.xml +++ b/pom.xml @@ -968,8 +968,9 @@ sun.security.x509.X509CertInfo sun.security.x509.X509CertImpl - + javax.net.ssl.SSLEngine + javax.net.ssl.X509ExtendedTrustManager