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:
parent
bdf0bddc85
commit
a2428c7e47
@ -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) {
|
||||
|
@ -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<String> 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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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
|
||||
@ -117,7 +111,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
@ -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<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;
|
||||
}
|
||||
|
||||
@ -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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
3
pom.xml
3
pom.xml
@ -968,8 +968,9 @@
|
||||
<ignore>sun.security.x509.X509CertInfo</ignore>
|
||||
<ignore>sun.security.x509.X509CertImpl</ignore>
|
||||
|
||||
<!-- SSLSession implelementation -->
|
||||
<!-- SSLSession implementation -->
|
||||
<ignore>javax.net.ssl.SSLEngine</ignore>
|
||||
<ignore>javax.net.ssl.X509ExtendedTrustManager</ignore>
|
||||
</ignores>
|
||||
</configuration>
|
||||
<executions>
|
||||
|
Loading…
Reference in New Issue
Block a user