All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.ssl.ReferenceCountedOpenSslContext Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.ssl;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.util.LazyX509Certificate;
import io.netty.internal.tcnative.AsyncSSLPrivateKeyMethod;
import io.netty.internal.tcnative.CertificateCompressionAlgo;
import io.netty.internal.tcnative.CertificateVerifier;
import io.netty.internal.tcnative.ResultCallback;
import io.netty.internal.tcnative.SSL;
import io.netty.internal.tcnative.SSLContext;
import io.netty.internal.tcnative.SSLPrivateKeyMethod;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.ReferenceCounted;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SuppressJava6Requirement;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateRevokedException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import static io.netty.handler.ssl.OpenSsl.DEFAULT_CIPHERS;
import static io.netty.handler.ssl.OpenSsl.availableJavaCipherSuites;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;

/**
 * An implementation of {@link SslContext} which works with libraries that support the
 * OpenSsl C library API.
 * 

Instances of this class must be {@link #release() released} or else native memory will leak! * *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} * which depends upon the instance of this class is released. Otherwise if any method of * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. */ public abstract class ReferenceCountedOpenSslContext extends SslContext implements ReferenceCounted { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslContext.class); private static final int DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE = Math.max(1, SystemPropertyUtil.getInt("io.netty.handler.ssl.openssl.bioNonApplicationBufferSize", 2048)); // Let's use tasks by default but still allow the user to disable it via system property just in case. static final boolean USE_TASKS = SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.useTasks", true); private static final Integer DH_KEY_LENGTH; private static final ResourceLeakDetector leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslContext.class); // TODO: Maybe make configurable ? protected static final int VERIFY_DEPTH = 10; static final boolean CLIENT_ENABLE_SESSION_TICKET = SystemPropertyUtil.getBoolean("jdk.tls.client.enableSessionTicketExtension", false); static final boolean CLIENT_ENABLE_SESSION_TICKET_TLSV13 = SystemPropertyUtil.getBoolean("jdk.tls.client.enableSessionTicketExtension", true); static final boolean SERVER_ENABLE_SESSION_TICKET = SystemPropertyUtil.getBoolean("jdk.tls.server.enableSessionTicketExtension", false); static final boolean SERVER_ENABLE_SESSION_TICKET_TLSV13 = SystemPropertyUtil.getBoolean("jdk.tls.server.enableSessionTicketExtension", true); static final boolean SERVER_ENABLE_SESSION_CACHE = SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheServer", true); static final boolean CLIENT_ENABLE_SESSION_CACHE = SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheClient", true); /** * The OpenSSL SSL_CTX object. * * {@link #ctxLock} must be hold while using ctx! */ protected long ctx; private final List unmodifiableCiphers; private final OpenSslApplicationProtocolNegotiator apn; private final int mode; // Reference Counting private final ResourceLeakTracker leak; private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { @Override public ReferenceCounted touch(Object hint) { if (leak != null) { leak.record(hint); } return ReferenceCountedOpenSslContext.this; } @Override protected void deallocate() { destroy(); if (leak != null) { boolean closed = leak.close(ReferenceCountedOpenSslContext.this); assert closed; } } }; final Certificate[] keyCertChain; final ClientAuth clientAuth; final String[] protocols; final boolean hasTLSv13Cipher; final boolean enableOcsp; final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap(); final ReadWriteLock ctxLock = new ReentrantReadWriteLock(); private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE; @SuppressWarnings("deprecation") static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR = new OpenSslApplicationProtocolNegotiator() { @Override public ApplicationProtocolConfig.Protocol protocol() { return ApplicationProtocolConfig.Protocol.NONE; } @Override public List protocols() { return Collections.emptyList(); } @Override public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() { return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL; } @Override public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() { return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT; } }; static { Integer dhLen = null; try { String dhKeySize = SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize"); if (dhKeySize != null) { try { dhLen = Integer.valueOf(dhKeySize); } catch (NumberFormatException e) { logger.debug("ReferenceCountedOpenSslContext supports -Djdk.tls.ephemeralDHKeySize={int}, but got: " + dhKeySize); } } } catch (Throwable ignore) { // ignore } DH_KEY_LENGTH = dhLen; } final boolean tlsFalseStart; ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp, boolean leakDetection, Map.Entry, Object>... ctxOptions) throws SSLException { super(startTls); OpenSsl.ensureAvailability(); if (enableOcsp && !OpenSsl.isOcspSupported()) { throw new IllegalStateException("OCSP is not supported."); } if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) { throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT"); } boolean tlsFalseStart = false; boolean useTasks = USE_TASKS; OpenSslPrivateKeyMethod privateKeyMethod = null; OpenSslAsyncPrivateKeyMethod asyncPrivateKeyMethod = null; OpenSslCertificateCompressionConfig certCompressionConfig = null; Integer maxCertificateList = null; if (ctxOptions != null) { for (Map.Entry, Object> ctxOpt : ctxOptions) { SslContextOption option = ctxOpt.getKey(); if (option == OpenSslContextOption.TLS_FALSE_START) { tlsFalseStart = (Boolean) ctxOpt.getValue(); } else if (option == OpenSslContextOption.USE_TASKS) { useTasks = (Boolean) ctxOpt.getValue(); } else if (option == OpenSslContextOption.PRIVATE_KEY_METHOD) { privateKeyMethod = (OpenSslPrivateKeyMethod) ctxOpt.getValue(); } else if (option == OpenSslContextOption.ASYNC_PRIVATE_KEY_METHOD) { asyncPrivateKeyMethod = (OpenSslAsyncPrivateKeyMethod) ctxOpt.getValue(); } else if (option == OpenSslContextOption.CERTIFICATE_COMPRESSION_ALGORITHMS) { certCompressionConfig = (OpenSslCertificateCompressionConfig) ctxOpt.getValue(); } else if (option == OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES) { maxCertificateList = (Integer) ctxOpt.getValue(); } else { logger.debug("Skipping unsupported " + SslContextOption.class.getSimpleName() + ": " + ctxOpt.getKey()); } } } if (privateKeyMethod != null && asyncPrivateKeyMethod != null) { throw new IllegalArgumentException("You can either only use " + OpenSslAsyncPrivateKeyMethod.class.getSimpleName() + " or " + OpenSslPrivateKeyMethod.class.getSimpleName()); } this.tlsFalseStart = tlsFalseStart; leak = leakDetection ? leakDetector.track(this) : null; this.mode = mode; this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE; this.protocols = protocols == null ? OpenSsl.defaultProtocols(mode == SSL.SSL_MODE_CLIENT) : protocols; this.enableOcsp = enableOcsp; this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone(); String[] suites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites( ciphers, DEFAULT_CIPHERS, availableJavaCipherSuites()); // Filter out duplicates. LinkedHashSet suitesSet = new LinkedHashSet(suites.length); Collections.addAll(suitesSet, suites); unmodifiableCiphers = new ArrayList(suitesSet); this.apn = checkNotNull(apn, "apn"); // Create a new SSL_CTX and configure it. boolean success = false; try { boolean tlsv13Supported = OpenSsl.isTlsv13Supported(); boolean anyTlsv13Ciphers = false; try { int protocolOpts = SSL.SSL_PROTOCOL_SSLV3 | SSL.SSL_PROTOCOL_TLSV1 | SSL.SSL_PROTOCOL_TLSV1_1 | SSL.SSL_PROTOCOL_TLSV1_2; if (tlsv13Supported) { protocolOpts |= SSL.SSL_PROTOCOL_TLSV1_3; } ctx = SSLContext.make(protocolOpts, mode); } catch (Exception e) { throw new SSLException("failed to create an SSL_CTX", e); } StringBuilder cipherBuilder = new StringBuilder(); StringBuilder cipherTLSv13Builder = new StringBuilder(); /* List the ciphers that are permitted to negotiate. */ try { if (unmodifiableCiphers.isEmpty()) { // Set non TLSv1.3 ciphers. SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, false); if (tlsv13Supported) { // Set TLSv1.3 ciphers. SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, true); } } else { CipherSuiteConverter.convertToCipherStrings( unmodifiableCiphers, cipherBuilder, cipherTLSv13Builder, OpenSsl.isBoringSSL()); // Set non TLSv1.3 ciphers. SSLContext.setCipherSuite(ctx, cipherBuilder.toString(), false); if (tlsv13Supported) { // Set TLSv1.3 ciphers. String tlsv13Ciphers = OpenSsl.checkTls13Ciphers(logger, cipherTLSv13Builder.toString()); SSLContext.setCipherSuite(ctx, tlsv13Ciphers, true); if (!tlsv13Ciphers.isEmpty()) { anyTlsv13Ciphers = true; } } } } catch (SSLException e) { throw e; } catch (Exception e) { throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e); } int options = SSLContext.getOptions(ctx) | SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | // Disable TLSv1 and TLSv1.1 by default as these are not considered secure anymore // and the JDK is doing the same: // https://www.oracle.com/java/technologies/javase/8u291-relnotes.html SSL.SSL_OP_NO_TLSv1 | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_CIPHER_SERVER_PREFERENCE | // We do not support compression at the moment so we should explicitly disable it. SSL.SSL_OP_NO_COMPRESSION | // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK. // This also let SSLSession.getId() work the same way for the JDK implementation and the // OpenSSLEngine. If tickets are supported SSLSession.getId() will only return an ID on the // server-side if it could make use of tickets. SSL.SSL_OP_NO_TICKET; if (cipherBuilder.length() == 0) { // No ciphers that are compatible with SSLv2 / SSLv3 / TLSv1 / TLSv1.1 / TLSv1.2 options |= SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2; } if (!tlsv13Supported) { // Explicit disable TLSv1.3 // See: // - https://github.com/netty/netty/issues/12968 options |= SSL.SSL_OP_NO_TLSv1_3; } hasTLSv13Cipher = anyTlsv13Ciphers; SSLContext.setOptions(ctx, options); // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between // calling OpenSSLEngine.wrap(...). // See https://github.com/netty/netty-tcnative/issues/100 SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); if (DH_KEY_LENGTH != null) { SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH); } List nextProtoList = apn.protocols(); /* Set next protocols for next protocol negotiation extension, if specified */ if (!nextProtoList.isEmpty()) { String[] appProtocols = nextProtoList.toArray(EmptyArrays.EMPTY_STRINGS); int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); switch (apn.protocol()) { case NPN: SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); break; case ALPN: SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); break; case NPN_AND_ALPN: SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); break; default: throw new Error(); } } if (enableOcsp) { SSLContext.enableOcsp(ctx, isClient()); } SSLContext.setUseTasks(ctx, useTasks); if (privateKeyMethod != null) { SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, privateKeyMethod)); } if (asyncPrivateKeyMethod != null) { SSLContext.setPrivateKeyMethod(ctx, new AsyncPrivateKeyMethod(engineMap, asyncPrivateKeyMethod)); } if (certCompressionConfig != null) { for (OpenSslCertificateCompressionConfig.AlgorithmConfig configPair : certCompressionConfig) { final CertificateCompressionAlgo algo = new CompressionAlgorithm(engineMap, configPair.algorithm()); switch (configPair.mode()) { case Decompress: SSLContext.addCertificateCompressionAlgorithm( ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_DECOMPRESS, algo); break; case Compress: SSLContext.addCertificateCompressionAlgorithm( ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_COMPRESS, algo); break; case Both: SSLContext.addCertificateCompressionAlgorithm( ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_BOTH, algo); break; default: throw new IllegalStateException(); } } } if (maxCertificateList != null) { SSLContext.setMaxCertList(ctx, maxCertificateList); } // Set the curves. SSLContext.setCurvesList(ctx, OpenSsl.NAMED_GROUPS); success = true; } finally { if (!success) { release(); } } } private static int opensslSelectorFailureBehavior(ApplicationProtocolConfig.SelectorFailureBehavior behavior) { switch (behavior) { case NO_ADVERTISE: return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE; case CHOOSE_MY_LAST_PROTOCOL: return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL; default: throw new Error(); } } @Override public final List cipherSuites() { return unmodifiableCiphers; } @Override public ApplicationProtocolNegotiator applicationProtocolNegotiator() { return apn; } @Override public final boolean isClient() { return mode == SSL.SSL_MODE_CLIENT; } @Override public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { return newEngine0(alloc, peerHost, peerPort, true); } @Override protected final SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) { return new SslHandler(newEngine0(alloc, null, -1, false), startTls); } @Override protected final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) { return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), startTls); } @Override protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) { return new SslHandler(newEngine0(alloc, null, -1, false), startTls, executor); } @Override protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls, Executor executor) { return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), executor); } SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) { return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode, true); } /** * Returns a new server-side {@link SSLEngine} with the current configuration. */ @Override public final SSLEngine newEngine(ByteBufAllocator alloc) { return newEngine(alloc, null, -1); } /** * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. * Be aware that it is freed as soon as the {@link #finalize()} method is called. * At this point {@code 0} will be returned. * * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! */ @Deprecated public final long context() { return sslCtxPointer(); } /** * Returns the stats of this context. * * @deprecated use {@link #sessionContext#stats()} */ @Deprecated public final OpenSslSessionStats stats() { return sessionContext().stats(); } /** * {@deprecated Renegotiation is not supported} * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding. */ @Deprecated public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) { if (!rejectRemoteInitiatedRenegotiation) { throw new UnsupportedOperationException("Renegotiation is not supported"); } } /** * {@deprecated Renegotiation is not supported} * @return {@code true} because renegotiation is not supported. */ @Deprecated public boolean getRejectRemoteInitiatedRenegotiation() { return true; } /** * Set the size of the buffer used by the BIO for non-application based writes * (e.g. handshake, renegotiation, etc...). */ public void setBioNonApplicationBufferSize(int bioNonApplicationBufferSize) { this.bioNonApplicationBufferSize = checkPositiveOrZero(bioNonApplicationBufferSize, "bioNonApplicationBufferSize"); } /** * Returns the size of the buffer used by the BIO for non-application based writes */ public int getBioNonApplicationBufferSize() { return bioNonApplicationBufferSize; } /** * Sets the SSL session ticket keys of this context. * * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])} */ @Deprecated public final void setTicketKeys(byte[] keys) { sessionContext().setTicketKeys(keys); } @Override public abstract OpenSslSessionContext sessionContext(); /** * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. * Be aware that it is freed as soon as the {@link #release()} method is called. * At this point {@code 0} will be returned. * * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! */ @Deprecated public final long sslCtxPointer() { Lock readerLock = ctxLock.readLock(); readerLock.lock(); try { return SSLContext.getSslCtx(ctx); } finally { readerLock.unlock(); } } /** * Set the {@link OpenSslPrivateKeyMethod} to use. This allows to offload private-key operations * if needed. * * This method is currently only supported when {@code BoringSSL} is used. * * @param method method to use. * @deprecated use {@link SslContextBuilder#option(SslContextOption, Object)} with * {@link OpenSslContextOption#PRIVATE_KEY_METHOD}. */ @Deprecated @UnstableApi public final void setPrivateKeyMethod(OpenSslPrivateKeyMethod method) { checkNotNull(method, "method"); Lock writerLock = ctxLock.writeLock(); writerLock.lock(); try { SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, method)); } finally { writerLock.unlock(); } } /** * @deprecated use {@link SslContextBuilder#option(SslContextOption, Object)} with * {@link OpenSslContextOption#USE_TASKS}. */ @Deprecated public final void setUseTasks(boolean useTasks) { Lock writerLock = ctxLock.writeLock(); writerLock.lock(); try { SSLContext.setUseTasks(ctx, useTasks); } finally { writerLock.unlock(); } } // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never // get access to an OpenSslSessionContext after this method was called to prevent the user from // producing a segfault. private void destroy() { Lock writerLock = ctxLock.writeLock(); writerLock.lock(); try { if (ctx != 0) { if (enableOcsp) { SSLContext.disableOcsp(ctx); } SSLContext.free(ctx); ctx = 0; OpenSslSessionContext context = sessionContext(); if (context != null) { context.destroy(); } } } finally { writerLock.unlock(); } } protected static X509Certificate[] certificates(byte[][] chain) { X509Certificate[] peerCerts = new X509Certificate[chain.length]; for (int i = 0; i < peerCerts.length; i++) { peerCerts[i] = new LazyX509Certificate(chain[i]); } return peerCerts; } protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { for (TrustManager m : managers) { if (m instanceof X509TrustManager) { X509TrustManager tm = (X509TrustManager) m; if (PlatformDependent.javaVersion() >= 7) { tm = OpenSslX509TrustManagerWrapper.wrapIfNeeded((X509TrustManager) m); if (useExtendedTrustManager(tm)) { // Wrap the TrustManager to provide a better exception message for users to debug hostname // validation failures. tm = new EnhancingX509ExtendedTrustManager(tm); } } return tm; } } throw new IllegalStateException("no X509TrustManager found"); } protected static X509KeyManager chooseX509KeyManager(KeyManager[] kms) { for (KeyManager km : kms) { if (km instanceof X509KeyManager) { return (X509KeyManager) km; } } throw new IllegalStateException("no X509KeyManager found"); } /** * Translate a {@link ApplicationProtocolConfig} object to a * {@link OpenSslApplicationProtocolNegotiator} object. * * @param config The configuration which defines the translation * @return The results of the translation */ @SuppressWarnings("deprecation") static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) { if (config == null) { return NONE_PROTOCOL_NEGOTIATOR; } switch (config.protocol()) { case NONE: return NONE_PROTOCOL_NEGOTIATOR; case ALPN: case NPN: case NPN_AND_ALPN: switch (config.selectedListenerFailureBehavior()) { case CHOOSE_MY_LAST_PROTOCOL: case ACCEPT: switch (config.selectorFailureBehavior()) { case CHOOSE_MY_LAST_PROTOCOL: case NO_ADVERTISE: return new OpenSslDefaultApplicationProtocolNegotiator( config); default: throw new UnsupportedOperationException( new StringBuilder("OpenSSL provider does not support ") .append(config.selectorFailureBehavior()) .append(" behavior").toString()); } default: throw new UnsupportedOperationException( new StringBuilder("OpenSSL provider does not support ") .append(config.selectedListenerFailureBehavior()) .append(" behavior").toString()); } default: throw new Error(); } } @SuppressJava6Requirement(reason = "Guarded by java version check") static boolean useExtendedTrustManager(X509TrustManager trustManager) { return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager; } @Override public final int refCnt() { return refCnt.refCnt(); } @Override public final ReferenceCounted retain() { refCnt.retain(); return this; } @Override public final ReferenceCounted retain(int increment) { refCnt.retain(increment); return this; } @Override public final ReferenceCounted touch() { refCnt.touch(); return this; } @Override public final ReferenceCounted touch(Object hint) { refCnt.touch(hint); return this; } @Override public final boolean release() { return refCnt.release(); } @Override public final boolean release(int decrement) { return refCnt.release(decrement); } abstract static class AbstractCertificateVerifier extends CertificateVerifier { private final OpenSslEngineMap engineMap; AbstractCertificateVerifier(OpenSslEngineMap engineMap) { this.engineMap = engineMap; } @Override public final int verify(long ssl, byte[][] chain, String auth) { final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); if (engine == null) { // May be null if it was destroyed in the meantime. return CertificateVerifier.X509_V_ERR_UNSPECIFIED; } X509Certificate[] peerCerts = certificates(chain); try { verify(engine, peerCerts, auth); return CertificateVerifier.X509_V_OK; } catch (Throwable cause) { logger.debug("verification of certificate failed", cause); engine.initHandshakeException(cause); // Try to extract the correct error code that should be used. if (cause instanceof OpenSslCertificateException) { // This will never return a negative error code as its validated when constructing the // OpenSslCertificateException. return ((OpenSslCertificateException) cause).errorCode(); } if (cause instanceof CertificateExpiredException) { return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; } if (cause instanceof CertificateNotYetValidException) { return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; } if (PlatformDependent.javaVersion() >= 7) { return translateToError(cause); } // Could not detect a specific error code to use, so fallback to a default code. return CertificateVerifier.X509_V_ERR_UNSPECIFIED; } } @SuppressJava6Requirement(reason = "Usage guarded by java version check") private static int translateToError(Throwable cause) { if (cause instanceof CertificateRevokedException) { return CertificateVerifier.X509_V_ERR_CERT_REVOKED; } // The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into // an CertificateException. So we need to handle the wrapped CertPathValidatorException to be // able to send the correct alert. Throwable wrapped = cause.getCause(); while (wrapped != null) { if (wrapped instanceof CertPathValidatorException) { CertPathValidatorException ex = (CertPathValidatorException) wrapped; CertPathValidatorException.Reason reason = ex.getReason(); if (reason == CertPathValidatorException.BasicReason.EXPIRED) { return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; } if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) { return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; } if (reason == CertPathValidatorException.BasicReason.REVOKED) { return CertificateVerifier.X509_V_ERR_CERT_REVOKED; } } wrapped = wrapped.getCause(); } return CertificateVerifier.X509_V_ERR_UNSPECIFIED; } abstract void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) throws Exception; } private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap { private final Map engines = PlatformDependent.newConcurrentHashMap(); @Override public ReferenceCountedOpenSslEngine remove(long ssl) { return engines.remove(ssl); } @Override public void add(ReferenceCountedOpenSslEngine engine) { engines.put(engine.sslPointer(), engine); } @Override public ReferenceCountedOpenSslEngine get(long ssl) { return engines.get(ssl); } } static void setKeyMaterial(long ctx, X509Certificate[] keyCertChain, PrivateKey key, String keyPassword) throws SSLException { /* Load the certificate file and private key. */ long keyBio = 0; long keyCertChainBio = 0; long keyCertChainBio2 = 0; PemEncoded encoded = null; try { // Only encode one time encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, keyCertChain); keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); if (key != null) { keyBio = toBIO(ByteBufAllocator.DEFAULT, key); } SSLContext.setCertificateBio( ctx, keyCertChainBio, keyBio, keyPassword == null ? StringUtil.EMPTY_STRING : keyPassword); // We may have more then one cert in the chain so add all of them now. SSLContext.setCertificateChainBio(ctx, keyCertChainBio2, true); } catch (SSLException e) { throw e; } catch (Exception e) { throw new SSLException("failed to set certificate and key", e); } finally { freeBio(keyBio); freeBio(keyCertChainBio); freeBio(keyCertChainBio2); if (encoded != null) { encoded.release(); } } } static void freeBio(long bio) { if (bio != 0) { SSL.freeBIO(bio); } } /** * Return the pointer to a in-memory BIO * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}. */ static long toBIO(ByteBufAllocator allocator, PrivateKey key) throws Exception { if (key == null) { return 0; } PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key); try { return toBIO(allocator, pem.retain()); } finally { pem.release(); } } /** * Return the pointer to a in-memory BIO * or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}. */ static long toBIO(ByteBufAllocator allocator, X509Certificate... certChain) throws Exception { if (certChain == null) { return 0; } checkNonEmpty(certChain, "certChain"); PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain); try { return toBIO(allocator, pem.retain()); } finally { pem.release(); } } static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception { try { // We can turn direct buffers straight into BIOs. No need to // make a yet another copy. ByteBuf content = pem.content(); if (content.isDirect()) { return newBIO(content.retainedSlice()); } ByteBuf buffer = allocator.directBuffer(content.readableBytes()); try { buffer.writeBytes(content, content.readerIndex(), content.readableBytes()); return newBIO(buffer.retainedSlice()); } finally { try { // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we // need to zero out the bytes of the copy before we're releasing it. if (pem.isSensitive()) { SslUtils.zeroout(buffer); } } finally { buffer.release(); } } } finally { pem.release(); } } private static long newBIO(ByteBuf buffer) throws Exception { try { long bio = SSL.newMemBIO(); int readable = buffer.readableBytes(); if (SSL.bioWrite(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) { SSL.freeBIO(bio); throw new IllegalStateException("Could not write data to memory BIO"); } return bio; } finally { buffer.release(); } } /** * Returns the {@link OpenSslKeyMaterialProvider} that should be used for OpenSSL. Depending on the given * {@link KeyManagerFactory} this may cache the {@link OpenSslKeyMaterial} for better performance if it can * ensure that the same material is always returned for the same alias. */ static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) { if (factory instanceof OpenSslX509KeyManagerFactory) { return ((OpenSslX509KeyManagerFactory) factory).newProvider(); } if (factory instanceof OpenSslCachingX509KeyManagerFactory) { // The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache. return ((OpenSslCachingX509KeyManagerFactory) factory).newProvider(password); } // We can not be sure if the material may change at runtime so we will not cache it. return new OpenSslKeyMaterialProvider(chooseX509KeyManager(factory.getKeyManagers()), password); } private static ReferenceCountedOpenSslEngine retrieveEngine(OpenSslEngineMap engineMap, long ssl) throws SSLException { ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); if (engine == null) { throw new SSLException("Could not find a " + StringUtil.simpleClassName(ReferenceCountedOpenSslEngine.class) + " for sslPointer " + ssl); } return engine; } private static final class PrivateKeyMethod implements SSLPrivateKeyMethod { private final OpenSslEngineMap engineMap; private final OpenSslPrivateKeyMethod keyMethod; PrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslPrivateKeyMethod keyMethod) { this.engineMap = engineMap; this.keyMethod = keyMethod; } @Override public byte[] sign(long ssl, int signatureAlgorithm, byte[] digest) throws Exception { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); try { return verifyResult(keyMethod.sign(engine, signatureAlgorithm, digest)); } catch (Exception e) { engine.initHandshakeException(e); throw e; } } @Override public byte[] decrypt(long ssl, byte[] input) throws Exception { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); try { return verifyResult(keyMethod.decrypt(engine, input)); } catch (Exception e) { engine.initHandshakeException(e); throw e; } } } private static final class AsyncPrivateKeyMethod implements AsyncSSLPrivateKeyMethod { private final OpenSslEngineMap engineMap; private final OpenSslAsyncPrivateKeyMethod keyMethod; AsyncPrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslAsyncPrivateKeyMethod keyMethod) { this.engineMap = engineMap; this.keyMethod = keyMethod; } @Override public void sign(long ssl, int signatureAlgorithm, byte[] bytes, ResultCallback resultCallback) { try { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); keyMethod.sign(engine, signatureAlgorithm, bytes) .addListener(new ResultCallbackListener(engine, ssl, resultCallback)); } catch (SSLException e) { resultCallback.onError(ssl, e); } } @Override public void decrypt(long ssl, byte[] bytes, ResultCallback resultCallback) { try { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); keyMethod.decrypt(engine, bytes) .addListener(new ResultCallbackListener(engine, ssl, resultCallback)); } catch (SSLException e) { resultCallback.onError(ssl, e); } } private static final class ResultCallbackListener implements FutureListener { private final ReferenceCountedOpenSslEngine engine; private final long ssl; private final ResultCallback resultCallback; ResultCallbackListener(ReferenceCountedOpenSslEngine engine, long ssl, ResultCallback resultCallback) { this.engine = engine; this.ssl = ssl; this.resultCallback = resultCallback; } @Override public void operationComplete(Future future) { Throwable cause = future.cause(); if (cause == null) { try { byte[] result = verifyResult(future.getNow()); resultCallback.onSuccess(ssl, result); return; } catch (SignatureException e) { cause = e; engine.initHandshakeException(e); } } resultCallback.onError(ssl, cause); } } } private static byte[] verifyResult(byte[] result) throws SignatureException { if (result == null) { throw new SignatureException(); } return result; } private static final class CompressionAlgorithm implements CertificateCompressionAlgo { private final OpenSslEngineMap engineMap; private final OpenSslCertificateCompressionAlgorithm compressionAlgorithm; CompressionAlgorithm(OpenSslEngineMap engineMap, OpenSslCertificateCompressionAlgorithm compressionAlgorithm) { this.engineMap = engineMap; this.compressionAlgorithm = compressionAlgorithm; } @Override public byte[] compress(long ssl, byte[] bytes) throws Exception { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); return compressionAlgorithm.compress(engine, bytes); } @Override public byte[] decompress(long ssl, int len, byte[] bytes) throws Exception { ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); return compressionAlgorithm.decompress(engine, len, bytes); } @Override public int algorithmId() { return compressionAlgorithm.algorithmId(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy