Add OpenSslClientContext to allow creating SslEngine for client side
Motivation: We only support openssl for server side at the moment but it would be also useful for client side. Modification: * Upgrade to new netty-tcnative snapshot to support client side openssl support * Add OpenSslClientContext which can be used to create SslEngine for client side usage * Factor out common logic between OpenSslClientContext and OpenSslServerContent into new abstract base class called OpenSslContext * Correctly detect handshake failures as soon as possible * Guard against segfault caused by multiple calls to destroyPools(). This can happen if OpenSslContext throws an exception in the constructor and the finalize() method is called later during GC Result: openssl can be used for client and servers now.
This commit is contained in:
parent
e266cfcf2b
commit
60f24ac2a1
@ -16,7 +16,6 @@
|
||||
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
@ -30,8 +30,7 @@ public final class OpenSsl {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
|
||||
private static final Throwable UNAVAILABILITY_CAUSE;
|
||||
|
||||
static final String IGNORABLE_ERROR_PREFIX = "error:00000000:";
|
||||
private static final String IGNORABLE_ERROR_PREFIX = "error:00000000:";
|
||||
|
||||
static {
|
||||
Throwable cause = null;
|
||||
@ -80,5 +79,9 @@ public final class OpenSsl {
|
||||
return UNAVAILABILITY_CAUSE;
|
||||
}
|
||||
|
||||
static boolean isError(String error) {
|
||||
return error != null && !error.startsWith(IGNORABLE_ERROR_PREFIX);
|
||||
}
|
||||
|
||||
private OpenSsl() { }
|
||||
}
|
||||
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import org.apache.tomcat.jni.SSL;
|
||||
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.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||
*/
|
||||
public final class OpenSslClientContext extends OpenSslContext {
|
||||
|
||||
private final X509TrustManager[] managers;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public OpenSslClientContext() throws SSLException {
|
||||
this(null, null, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param certChainFile an X.509 certificate chain file in PEM format.
|
||||
* {@code null} to use the system default
|
||||
*/
|
||||
public OpenSslClientContext(File certChainFile) throws SSLException {
|
||||
this(certChainFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
|
||||
* that verifies the certificates sent from servers.
|
||||
* {@code null} to use the default.
|
||||
*/
|
||||
public OpenSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException {
|
||||
this(null, trustManagerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param certChainFile an X.509 certificate chain file in PEM format.
|
||||
* {@code null} to use the system default
|
||||
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
|
||||
* that verifies the certificates sent from servers.
|
||||
* {@code null} to use the default.
|
||||
*/
|
||||
public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
|
||||
this(certChainFile, trustManagerFactory, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param certChainFile an X.509 certificate chain file in PEM format
|
||||
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
|
||||
* that verifies the certificates sent from servers.
|
||||
* {@code null} to use the default..
|
||||
* @param ciphers the cipher suites to enable, in the order of preference.
|
||||
* {@code null} to use the default cipher suites.
|
||||
* @param apn Provides a means to configure parameters related to application protocol negotiation.
|
||||
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
|
||||
* {@code 0} to use the default value.
|
||||
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
|
||||
* {@code 0} to use the default value.
|
||||
*/
|
||||
public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable<String> ciphers,
|
||||
ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout)
|
||||
throws SSLException {
|
||||
super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT);
|
||||
boolean success = false;
|
||||
try {
|
||||
if (certChainFile != null && !certChainFile.isFile()) {
|
||||
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
|
||||
}
|
||||
|
||||
synchronized (OpenSslContext.class) {
|
||||
if (certChainFile != null) {
|
||||
/* Load the certificate chain. We must skip the first cert when server mode */
|
||||
if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
|
||||
String error = SSL.getLastError();
|
||||
if (OpenSsl.isError(error)) {
|
||||
throw new SSLException(
|
||||
"failed to set certificate chain: "
|
||||
+ certChainFile + " (" + SSL.getLastError() + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
|
||||
|
||||
// check if verification should take place or not.
|
||||
if (trustManagerFactory != null) {
|
||||
try {
|
||||
if (certChainFile == null) {
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
} else {
|
||||
initTrustManagerFactory(certChainFile, trustManagerFactory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to initialize the client-side SSL context", e);
|
||||
}
|
||||
TrustManager[] tms = trustManagerFactory.getTrustManagers();
|
||||
if (tms == null || tms.length == 0) {
|
||||
managers = null;
|
||||
} else {
|
||||
List<X509TrustManager> managerList = new ArrayList<X509TrustManager>(tms.length);
|
||||
for (TrustManager tm: tms) {
|
||||
if (tm instanceof X509TrustManager) {
|
||||
managerList.add((X509TrustManager) tm);
|
||||
}
|
||||
}
|
||||
managers = managerList.toArray(new X509TrustManager[managerList.size()]);
|
||||
}
|
||||
} else {
|
||||
managers = null;
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
destroyPools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||
List<String> protos = applicationProtocolNegotiator().protocols();
|
||||
if (protos.isEmpty()) {
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient(), managers);
|
||||
} else {
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), managers);
|
||||
}
|
||||
}
|
||||
}
|
302
handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
Normal file
302
handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.Pool;
|
||||
import org.apache.tomcat.jni.SSL;
|
||||
import org.apache.tomcat.jni.SSLContext;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
public abstract class OpenSslContext extends SslContext {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslContext.class);
|
||||
private static final List<String> DEFAULT_CIPHERS;
|
||||
private static final AtomicIntegerFieldUpdater<OpenSslContext> DESTROY_UPDATER;
|
||||
|
||||
// TODO: Maybe make configurable ?
|
||||
protected static final int VERIFY_DEPTH = 10;
|
||||
|
||||
private final long aprPool;
|
||||
@SuppressWarnings("unused")
|
||||
private volatile int aprPoolDestroyed;
|
||||
private final List<String> ciphers = new ArrayList<String>();
|
||||
private final List<String> unmodifiableCiphers = Collections.unmodifiableList(ciphers);
|
||||
private final long sessionCacheSize;
|
||||
private final long sessionTimeout;
|
||||
private final OpenSslApplicationProtocolNegotiator apn;
|
||||
/** The OpenSSL SSL_CTX object */
|
||||
protected final long ctx;
|
||||
private final int mode;
|
||||
private final OpenSslSessionStats stats;
|
||||
|
||||
static {
|
||||
List<String> ciphers = new ArrayList<String>();
|
||||
// XXX: Make sure to sync this list with JdkSslEngineFactory.
|
||||
Collections.addAll(
|
||||
ciphers,
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-SHA",
|
||||
"ECDHE-RSA-AES256-SHA",
|
||||
"AES128-GCM-SHA256",
|
||||
"AES128-SHA",
|
||||
"AES256-SHA",
|
||||
"DES-CBC3-SHA",
|
||||
"RC4-SHA");
|
||||
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Default cipher suite (OpenSSL): " + ciphers);
|
||||
}
|
||||
|
||||
AtomicIntegerFieldUpdater<OpenSslContext> updater =
|
||||
PlatformDependent.newAtomicIntegerFieldUpdater(OpenSslContext.class, "aprPoolDestroyed");
|
||||
if (updater == null) {
|
||||
updater = AtomicIntegerFieldUpdater.newUpdater(OpenSslContext.class, "aprPoolDestroyed");
|
||||
}
|
||||
DESTROY_UPDATER = updater;
|
||||
}
|
||||
|
||||
OpenSslContext(Iterable<String> ciphers, ApplicationProtocolConfig apnCfg, long sessionCacheSize,
|
||||
long sessionTimeout, int mode) throws SSLException {
|
||||
this(ciphers, toNegotiator(apnCfg, mode == SSL.SSL_MODE_SERVER), sessionCacheSize, sessionTimeout, mode);
|
||||
}
|
||||
|
||||
OpenSslContext(Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
|
||||
long sessionTimeout, int mode) throws SSLException {
|
||||
OpenSsl.ensureAvailability();
|
||||
|
||||
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");
|
||||
}
|
||||
this.mode = mode;
|
||||
|
||||
if (ciphers == null) {
|
||||
ciphers = DEFAULT_CIPHERS;
|
||||
}
|
||||
|
||||
for (String c: ciphers) {
|
||||
if (c == null) {
|
||||
break;
|
||||
}
|
||||
this.ciphers.add(c);
|
||||
}
|
||||
|
||||
this.apn = checkNotNull(apn, "apn");
|
||||
|
||||
// Allocate a new APR pool.
|
||||
aprPool = Pool.create(0);
|
||||
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
synchronized (OpenSslContext.class) {
|
||||
try {
|
||||
ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, mode);
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to create an SSL_CTX", e);
|
||||
}
|
||||
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
/* List the ciphers that are permitted to negotiate. */
|
||||
try {
|
||||
// Convert the cipher list into a colon-separated string.
|
||||
StringBuilder cipherBuf = new StringBuilder();
|
||||
for (String c: this.ciphers) {
|
||||
cipherBuf.append(c);
|
||||
cipherBuf.append(':');
|
||||
}
|
||||
cipherBuf.setLength(cipherBuf.length() - 1);
|
||||
|
||||
SSLContext.setCipherSuite(ctx, cipherBuf.toString());
|
||||
} catch (SSLException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to set cipher suite: " + this.ciphers, e);
|
||||
}
|
||||
|
||||
List<String> nextProtoList = apn.protocols();
|
||||
/* Set next protocols for next protocol negotiation extension, if specified */
|
||||
if (!nextProtoList.isEmpty()) {
|
||||
// Convert the protocol list into a comma-separated string.
|
||||
StringBuilder nextProtocolBuf = new StringBuilder();
|
||||
for (String p: nextProtoList) {
|
||||
nextProtocolBuf.append(p);
|
||||
nextProtocolBuf.append(',');
|
||||
}
|
||||
nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
|
||||
|
||||
SSLContext.setNextProtos(ctx, nextProtocolBuf.toString());
|
||||
}
|
||||
|
||||
/* Set session cache size, if specified */
|
||||
if (sessionCacheSize > 0) {
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
||||
} else {
|
||||
// Get the default session cache size using SSLContext.setSessionCacheSize()
|
||||
this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
|
||||
// Revert the session cache size to the default value.
|
||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
||||
}
|
||||
|
||||
/* Set session timeout, if specified */
|
||||
if (sessionTimeout > 0) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
} else {
|
||||
// Get the default session timeout using SSLContext.setSessionCacheTimeout()
|
||||
this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
|
||||
// Revert the session timeout to the default value.
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
destroyPools();
|
||||
}
|
||||
}
|
||||
|
||||
stats = new OpenSslSessionStats(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> cipherSuites() {
|
||||
return unmodifiableCiphers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionCacheSize() {
|
||||
return sessionCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long sessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
@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) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/**
|
||||
* Returns the {@code SSL_CTX} object of this context.
|
||||
*/
|
||||
public final long context() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats of this context.
|
||||
*/
|
||||
public final OpenSslSessionStats stats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("FinalizeDeclaration")
|
||||
protected final void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
synchronized (OpenSslContext.class) {
|
||||
if (ctx != 0) {
|
||||
SSLContext.free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
destroyPools();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SSL session ticket keys of this context.
|
||||
*/
|
||||
public final void setTicketKeys(byte[] keys) {
|
||||
if (keys == null) {
|
||||
throw new NullPointerException("keys");
|
||||
}
|
||||
SSLContext.setSessionTicketKeys(ctx, keys);
|
||||
}
|
||||
|
||||
protected final void destroyPools() {
|
||||
// Guard against multiple destroyPools() calls triggered by construction exception and finalize() later
|
||||
if (aprPool != 0 && DESTROY_UPDATER.compareAndSet(this, 0, 1)) {
|
||||
Pool.destroy(aprPool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a {@link ApplicationProtocolConfig} object to a
|
||||
* {@link OpenSslApplicationProtocolNegotiator} object.
|
||||
* @param config The configuration which defines the translation
|
||||
* @param isServer {@code true} if a server {@code false} otherwise.
|
||||
* @return The results of the translation
|
||||
*/
|
||||
static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config,
|
||||
boolean isServer) {
|
||||
if (config == null) {
|
||||
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
|
||||
}
|
||||
|
||||
switch(config.protocol()) {
|
||||
case NONE:
|
||||
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
|
||||
case NPN:
|
||||
if (isServer) {
|
||||
switch(config.selectedListenerFailureBehavior()) {
|
||||
case CHOOSE_MY_LAST_PROTOCOL:
|
||||
return new OpenSslNpnApplicationProtocolNegotiator(config.supportedProtocols());
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
new StringBuilder("OpenSSL provider does not support ")
|
||||
.append(config.selectedListenerFailureBehavior()).append(" behavior").toString());
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("OpenSSL provider does not support client mode");
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ")
|
||||
.append(config.protocol()).append(" protocol").toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -26,14 +26,21 @@ import org.apache.tomcat.jni.SSL;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
||||
import static javax.net.ssl.SSLEngineResult.Status.*;
|
||||
@ -47,12 +54,9 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
|
||||
|
||||
private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0];
|
||||
private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0];
|
||||
|
||||
private static final SSLException ENGINE_CLOSED = new SSLException("engine closed");
|
||||
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported");
|
||||
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized");
|
||||
|
||||
static {
|
||||
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
||||
RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
||||
@ -71,6 +75,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER =
|
||||
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
|
||||
|
||||
private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-");
|
||||
// OpenSSL state
|
||||
private long ssl;
|
||||
private long networkBIO;
|
||||
@ -84,8 +89,12 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private volatile int destroyed;
|
||||
|
||||
private String cipher;
|
||||
// Use an invalid cipherSuite until the handshake is completed
|
||||
// See http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#getSession()
|
||||
private volatile String cipher = "SSL_NULL_WITH_NULL_NULL";
|
||||
private volatile String applicationProtocol;
|
||||
private volatile Certificate[] peerCerts;
|
||||
private volatile X509Certificate[] x509PeerCerts;
|
||||
|
||||
// SSL Engine status variables
|
||||
private boolean isInboundDone;
|
||||
@ -94,6 +103,8 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
private int lastPrimingReadResult;
|
||||
|
||||
private final X509TrustManager[] managers;
|
||||
private final boolean clientMode;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final String fallbackApplicationProtocol;
|
||||
private SSLSession session;
|
||||
@ -104,7 +115,20 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
* @param sslCtx an OpenSSL {@code SSL_CTX} object
|
||||
* @param alloc the {@link ByteBufAllocator} that will be used by this engine
|
||||
*/
|
||||
@Deprecated
|
||||
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) {
|
||||
this(sslCtx, alloc, fallbackApplicationProtocol, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param sslCtx an OpenSSL {@code SSL_CTX} object
|
||||
* @param alloc the {@link ByteBufAllocator} that will be used by this engine
|
||||
* @param clientMode {@code true} if this is used for clients, {@code false} otherwise
|
||||
*/
|
||||
OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol,
|
||||
boolean clientMode, X509TrustManager[] managers) {
|
||||
OpenSsl.ensureAvailability();
|
||||
if (sslCtx == 0) {
|
||||
throw new NullPointerException("sslContext");
|
||||
@ -114,9 +138,15 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
ssl = SSL.newSSL(sslCtx, true);
|
||||
ssl = SSL.newSSL(sslCtx, !clientMode);
|
||||
networkBIO = SSL.makeNetworkBIO(ssl);
|
||||
this.fallbackApplicationProtocol = fallbackApplicationProtocol;
|
||||
this.clientMode = clientMode;
|
||||
if (managers == null || managers.length == 0) {
|
||||
this.managers = null;
|
||||
} else {
|
||||
this.managers = managers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,12 +184,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
} else {
|
||||
ByteBuf buf = alloc.directBuffer(len);
|
||||
try {
|
||||
final long addr;
|
||||
if (buf.hasMemoryAddress()) {
|
||||
addr = buf.memoryAddress();
|
||||
} else {
|
||||
addr = Buffer.address(buf.nioBuffer());
|
||||
}
|
||||
final long addr = memoryAddress(buf);
|
||||
|
||||
src.limit(pos + len);
|
||||
|
||||
@ -198,12 +223,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
} else {
|
||||
final ByteBuf buf = alloc.directBuffer(len);
|
||||
try {
|
||||
final long addr;
|
||||
if (buf.hasMemoryAddress()) {
|
||||
addr = buf.memoryAddress();
|
||||
} else {
|
||||
addr = Buffer.address(buf.nioBuffer());
|
||||
}
|
||||
final long addr = memoryAddress(buf);
|
||||
|
||||
buf.setBytes(0, src);
|
||||
|
||||
@ -242,12 +262,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
|
||||
final ByteBuf buf = alloc.directBuffer(len);
|
||||
try {
|
||||
final long addr;
|
||||
if (buf.hasMemoryAddress()) {
|
||||
addr = buf.memoryAddress();
|
||||
} else {
|
||||
addr = Buffer.address(buf.nioBuffer());
|
||||
}
|
||||
final long addr = memoryAddress(buf);
|
||||
|
||||
final int sslRead = SSL.readFromSSL(ssl, addr, len);
|
||||
if (sslRead > 0) {
|
||||
@ -279,12 +294,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
} else {
|
||||
final ByteBuf buf = alloc.directBuffer(pending);
|
||||
try {
|
||||
final long addr;
|
||||
if (buf.hasMemoryAddress()) {
|
||||
addr = buf.memoryAddress();
|
||||
} else {
|
||||
addr = Buffer.address(buf.nioBuffer());
|
||||
}
|
||||
final long addr = memoryAddress(buf);
|
||||
|
||||
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
||||
if (bioRead > 0) {
|
||||
@ -336,7 +346,8 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// In handshake or close_notify stages, check if call to wrap was made
|
||||
// without regard to the handshake status.
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||
|
||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
|
||||
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
|
||||
}
|
||||
@ -367,7 +378,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), 0, bytesProduced);
|
||||
}
|
||||
|
||||
// There was no pending data in the network BIO -- encrypt any application data
|
||||
@ -389,7 +400,8 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
// Do we have enough room in dst to write encrypted data?
|
||||
int capacity = dst.remaining();
|
||||
if (capacity < pendingNet) {
|
||||
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(
|
||||
BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
// Write the pending data from the network BIO into the dst buffer
|
||||
@ -399,12 +411,12 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
throw new SSLException(e);
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -449,7 +461,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// In handshake or close_notify stages, check if call to unwrap was made
|
||||
// without regard to the handshake status.
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
|
||||
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
|
||||
}
|
||||
@ -474,7 +486,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// Check for OpenSSL errors caused by the priming read
|
||||
String error = SSL.getLastError();
|
||||
if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
|
||||
if (OpenSsl.isError(error)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(
|
||||
"SSL_read failed: primingReadResult: " + lastPrimingReadResult +
|
||||
@ -491,7 +503,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// Do we have enough room in dsts to write decrypted data?
|
||||
if (capacity < pendingApp) {
|
||||
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
|
||||
return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, 0);
|
||||
}
|
||||
|
||||
// Write decrypted data to dsts buffers
|
||||
@ -534,7 +546,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
closeInbound();
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -622,14 +634,65 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
try {
|
||||
int len = 0;
|
||||
if (chain != null) {
|
||||
len += chain.length;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Certificate[] peerCerts;
|
||||
if (clientCert != null) {
|
||||
len++;
|
||||
peerCerts = new Certificate[len];
|
||||
peerCerts[i++] = SslContext.X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteArrayInputStream(clientCert));
|
||||
} else {
|
||||
peerCerts = new Certificate[len];
|
||||
}
|
||||
if (chain != null) {
|
||||
int a = 0;
|
||||
for (; i < peerCerts.length; i++) {
|
||||
peerCerts[i] = SslContext.X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteArrayInputStream(chain[a++]));
|
||||
}
|
||||
}
|
||||
return peerCerts;
|
||||
} catch (CertificateException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession() {
|
||||
SSLSession session = this.session;
|
||||
if (session == null) {
|
||||
this.session = session = new SSLSession() {
|
||||
private volatile byte[] id;
|
||||
|
||||
@Override
|
||||
public byte[] getId() {
|
||||
return String.valueOf(ssl).getBytes();
|
||||
// these are lazy created to reduce memory overhead but cached for performance reasons.
|
||||
if (id == null) {
|
||||
id = String.valueOf(ssl).getBytes();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -675,8 +738,16 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getPeerCertificates() {
|
||||
return EMPTY_CERTIFICATES;
|
||||
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
|
||||
@ -685,8 +756,28 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getPeerCertificateChain() {
|
||||
return EMPTY_X509_CERTIFICATES;
|
||||
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 (javax.security.cert.CertificateException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
c = x509PeerCerts = peerCerts;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -747,7 +838,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
switch (accepted) {
|
||||
case 0:
|
||||
SSL.doHandshake(ssl);
|
||||
handshake();
|
||||
accepted = 2;
|
||||
break;
|
||||
case 1:
|
||||
@ -772,17 +863,53 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
if (accepted == 0) {
|
||||
SSL.doHandshake(ssl);
|
||||
handshake();
|
||||
accepted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void handshake() throws SSLException {
|
||||
int code = SSL.doHandshake(ssl);
|
||||
if (code <= 0) {
|
||||
// Check for OpenSSL errors caused by the handshake
|
||||
String error = SSL.getLastError();
|
||||
if (OpenSsl.isError(error)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(
|
||||
"SSL_do_handshake failed: OpenSSL error: '" + error + '\'');
|
||||
}
|
||||
|
||||
// There was an internal error -- shutdown
|
||||
shutdown();
|
||||
throw new SSLException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long memoryAddress(ByteBuf buf) {
|
||||
if (buf.hasMemoryAddress()) {
|
||||
return buf.memoryAddress();
|
||||
} else {
|
||||
return Buffer.address(buf.nioBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
private SSLEngineResult.Status getEngineStatus() {
|
||||
return engineClosed? CLOSED : OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||
try {
|
||||
return handshakeStatus(false);
|
||||
} catch (SSLException e) {
|
||||
// Can not happen!
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// No need to synchronize access as it is only called from within synchronized methods
|
||||
private SSLEngineResult.HandshakeStatus handshakeStatus(boolean verify) throws SSLException {
|
||||
if (accepted == 0 || destroyed != 0) {
|
||||
return NOT_HANDSHAKING;
|
||||
}
|
||||
@ -798,7 +925,12 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
// Check to see if we have finished handshaking
|
||||
if (SSL.isInInit(ssl) == 0) {
|
||||
handshakeFinished = true;
|
||||
cipher = SSL.getCipherForSSL(ssl);
|
||||
String c = SSL.getCipherForSSL(ssl);
|
||||
if (c != null) {
|
||||
// OpenSSL returns the ciphers seperated by '-' but the JDK SSLEngine does by '_', so replace '-'
|
||||
// with '_' to match the behaviour.
|
||||
cipher = CIPHER_REPLACE_PATTERN.matcher(c).replaceAll("_");
|
||||
}
|
||||
String applicationProtocol = SSL.getNextProtoNegotiated(ssl);
|
||||
if (applicationProtocol == null) {
|
||||
applicationProtocol = fallbackApplicationProtocol;
|
||||
@ -808,6 +940,10 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
} else {
|
||||
this.applicationProtocol = null;
|
||||
}
|
||||
if (verify && managers != null) {
|
||||
peerCerts = initPeerCertChain();
|
||||
verifyCertificates(managers, peerCerts, cipher);
|
||||
}
|
||||
return FINISHED;
|
||||
}
|
||||
|
||||
@ -832,14 +968,14 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
@Override
|
||||
public void setUseClientMode(boolean clientMode) {
|
||||
if (clientMode) {
|
||||
if (clientMode != this.clientMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseClientMode() {
|
||||
return false;
|
||||
return clientMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -878,6 +1014,31 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void verifyCertificates(X509TrustManager[] managers, Certificate[] peerCerts, String cipher)
|
||||
throws SSLException {
|
||||
|
||||
List<java.security.cert.X509Certificate> certs =
|
||||
new ArrayList<java.security.cert.X509Certificate>(peerCerts.length);
|
||||
for (Certificate c: peerCerts) {
|
||||
if (c instanceof java.security.cert.X509Certificate) {
|
||||
certs.add((java.security.cert.X509Certificate) c);
|
||||
}
|
||||
}
|
||||
java.security.cert.X509Certificate[] certArray =
|
||||
certs.toArray(new java.security.cert.X509Certificate[certs.size()]);
|
||||
for (X509TrustManager tm: managers) {
|
||||
try {
|
||||
if (clientMode) {
|
||||
tm.checkServerTrusted(certArray, cipher);
|
||||
} else {
|
||||
tm.checkClientTrusted(certArray, cipher);
|
||||
}
|
||||
} catch (CertificateException e) {
|
||||
throw new SSLException("peer certificate chain not trusted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
|
@ -16,17 +16,12 @@
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import org.apache.tomcat.jni.Pool;
|
||||
import org.apache.tomcat.jni.SSL;
|
||||
import org.apache.tomcat.jni.SSLContext;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
@ -34,42 +29,7 @@ import static io.netty.util.internal.ObjectUtil.*;
|
||||
/**
|
||||
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||
*/
|
||||
public final class OpenSslServerContext extends SslContext {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
|
||||
private static final List<String> DEFAULT_CIPHERS;
|
||||
|
||||
static {
|
||||
List<String> ciphers = new ArrayList<String>();
|
||||
// XXX: Make sure to sync this list with JdkSslEngineFactory.
|
||||
Collections.addAll(
|
||||
ciphers,
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-SHA",
|
||||
"ECDHE-RSA-AES256-SHA",
|
||||
"AES128-GCM-SHA256",
|
||||
"AES128-SHA",
|
||||
"AES256-SHA",
|
||||
"DES-CBC3-SHA",
|
||||
"RC4-SHA");
|
||||
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Default cipher suite (OpenSSL): " + ciphers);
|
||||
}
|
||||
}
|
||||
|
||||
private final long aprPool;
|
||||
|
||||
private final List<String> ciphers = new ArrayList<String>();
|
||||
private final List<String> unmodifiableCiphers = Collections.unmodifiableList(ciphers);
|
||||
private final long sessionCacheSize;
|
||||
private final long sessionTimeout;
|
||||
private final OpenSslApplicationProtocolNegotiator apn;
|
||||
|
||||
/** The OpenSSL SSL_CTX object */
|
||||
private final long ctx;
|
||||
private final OpenSslSessionStats stats;
|
||||
public final class OpenSslServerContext extends OpenSslContext {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -163,6 +123,7 @@ public final class OpenSslServerContext extends SslContext {
|
||||
Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn,
|
||||
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
||||
|
||||
super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER);
|
||||
OpenSsl.ensureAvailability();
|
||||
|
||||
checkNotNull(certChainFile, "certChainFile");
|
||||
@ -170,65 +131,28 @@ public final class OpenSslServerContext extends SslContext {
|
||||
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
|
||||
}
|
||||
checkNotNull(keyFile, "keyFile");
|
||||
this.apn = checkNotNull(apn, "apn");
|
||||
if (!keyFile.isFile()) {
|
||||
throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
|
||||
}
|
||||
if (ciphers == null) {
|
||||
ciphers = DEFAULT_CIPHERS;
|
||||
}
|
||||
|
||||
if (keyPassword == null) {
|
||||
keyPassword = "";
|
||||
}
|
||||
|
||||
for (String c: ciphers) {
|
||||
if (c == null) {
|
||||
break;
|
||||
}
|
||||
this.ciphers.add(c);
|
||||
}
|
||||
|
||||
// Allocate a new APR pool.
|
||||
aprPool = Pool.create(0);
|
||||
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
synchronized (OpenSslServerContext.class) {
|
||||
try {
|
||||
ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to create an SSL_CTX", e);
|
||||
}
|
||||
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv3);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_ECDH_USE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_SINGLE_DH_USE);
|
||||
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
/* List the ciphers that the client is permitted to negotiate. */
|
||||
try {
|
||||
// Convert the cipher list into a colon-separated string.
|
||||
StringBuilder cipherBuf = new StringBuilder();
|
||||
for (String c: this.ciphers) {
|
||||
cipherBuf.append(c);
|
||||
cipherBuf.append(':');
|
||||
}
|
||||
cipherBuf.setLength(cipherBuf.length() - 1);
|
||||
|
||||
SSLContext.setCipherSuite(ctx, cipherBuf.toString());
|
||||
} catch (SSLException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to set cipher suite: " + this.ciphers, e);
|
||||
}
|
||||
|
||||
synchronized (OpenSslContext.class) {
|
||||
/* Set certificate verification policy. */
|
||||
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10);
|
||||
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
||||
|
||||
/* Load the certificate chain. We must skip the first cert when server mode */
|
||||
if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
|
||||
String error = SSL.getLastError();
|
||||
if (OpenSsl.isError(error)) {
|
||||
throw new SSLException(
|
||||
"failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')');
|
||||
}
|
||||
}
|
||||
|
||||
/* Load the certificate file and private key. */
|
||||
try {
|
||||
@ -242,51 +166,6 @@ public final class OpenSslServerContext extends SslContext {
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e);
|
||||
}
|
||||
|
||||
/* Load the certificate chain. We must skip the first cert since it was loaded above. */
|
||||
if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
|
||||
String error = SSL.getLastError();
|
||||
if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
|
||||
throw new SSLException(
|
||||
"failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')');
|
||||
}
|
||||
}
|
||||
|
||||
/* Set next protocols for next protocol negotiation extension, if specified */
|
||||
List<String> protocols = apn.protocols();
|
||||
if (!protocols.isEmpty()) {
|
||||
// Convert the protocol list into a comma-separated string.
|
||||
StringBuilder nextProtocolBuf = new StringBuilder();
|
||||
for (int i = 0; i < protocols.size(); ++i) {
|
||||
nextProtocolBuf.append(protocols.get(i));
|
||||
nextProtocolBuf.append(',');
|
||||
}
|
||||
nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
|
||||
|
||||
SSLContext.setNextProtos(ctx, nextProtocolBuf.toString());
|
||||
}
|
||||
|
||||
/* Set session cache size, if specified */
|
||||
if (sessionCacheSize > 0) {
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
||||
} else {
|
||||
// Get the default session cache size using SSLContext.setSessionCacheSize()
|
||||
this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
|
||||
// Revert the session cache size to the default value.
|
||||
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
|
||||
}
|
||||
|
||||
/* Set session timeout, if specified */
|
||||
if (sessionTimeout > 0) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
} else {
|
||||
// Get the default session timeout using SSLContext.setSessionCacheTimeout()
|
||||
this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
|
||||
// Revert the session timeout to the default value.
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
@ -294,47 +173,6 @@ public final class OpenSslServerContext extends SslContext {
|
||||
destroyPools();
|
||||
}
|
||||
}
|
||||
|
||||
stats = new OpenSslSessionStats(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cipherSuites() {
|
||||
return unmodifiableCiphers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sessionCacheSize() {
|
||||
return sessionCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
|
||||
return apn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code SSL_CTX} object of this context.
|
||||
*/
|
||||
public long context() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats of this context.
|
||||
*/
|
||||
public OpenSslSessionStats stats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -342,79 +180,11 @@ public final class OpenSslServerContext extends SslContext {
|
||||
*/
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||
List<String> protocols = apn.protocols();
|
||||
if (protocols.isEmpty()) {
|
||||
return new OpenSslEngine(ctx, alloc, null);
|
||||
List<String> protos = applicationProtocolNegotiator().protocols();
|
||||
if (protos.isEmpty()) {
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient(), null);
|
||||
} else {
|
||||
return new OpenSslEngine(ctx, alloc, protocols.get(protocols.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SSL session ticket keys of this context.
|
||||
*/
|
||||
public void setTicketKeys(byte[] keys) {
|
||||
if (keys == null) {
|
||||
throw new NullPointerException("keys");
|
||||
}
|
||||
SSLContext.setSessionTicketKeys(ctx, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("FinalizeDeclaration")
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
synchronized (OpenSslServerContext.class) {
|
||||
if (ctx != 0) {
|
||||
SSLContext.free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
destroyPools();
|
||||
}
|
||||
|
||||
private void destroyPools() {
|
||||
if (aprPool != 0) {
|
||||
Pool.destroy(aprPool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a {@link ApplicationProtocolConfig} object to a
|
||||
* {@link OpenSslApplicationProtocolNegotiator} object.
|
||||
* @param config The configuration which defines the translation
|
||||
* @param isServer {@code true} if a server {@code false} otherwise.
|
||||
* @return The results of the translation
|
||||
*/
|
||||
private static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config,
|
||||
boolean isServer) {
|
||||
if (config == null) {
|
||||
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
|
||||
}
|
||||
|
||||
switch(config.protocol()) {
|
||||
case NONE:
|
||||
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
|
||||
case NPN:
|
||||
if (isServer) {
|
||||
switch(config.selectedListenerFailureBehavior()) {
|
||||
case CHOOSE_MY_LAST_PROTOCOL:
|
||||
return new OpenSslNpnApplicationProtocolNegotiator(config.supportedProtocols());
|
||||
default:
|
||||
throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ")
|
||||
.append(config.selectedListenerFailureBehavior()).append(" behavior").toString());
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("OpenSSL provider does not support client mode");
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ")
|
||||
.append(config.protocol()).append(" protocol").toString());
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
@ -30,7 +32,15 @@ import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -56,6 +66,15 @@ import java.util.List;
|
||||
* </pre>
|
||||
*/
|
||||
public abstract class SslContext {
|
||||
static final CertificateFactory X509_CERT_FACTORY;
|
||||
static {
|
||||
try {
|
||||
X509_CERT_FACTORY = CertificateFactory.getInstance("X.509");
|
||||
} catch (CertificateException e) {
|
||||
throw new IllegalStateException("unable to instance X.509 CertificateFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default server-side implementation provider currently in use.
|
||||
*
|
||||
@ -509,7 +528,6 @@ public abstract class SslContext {
|
||||
File certChainFile, TrustManagerFactory trustManagerFactory,
|
||||
Iterable<String> ciphers, Iterable<String> nextProtocols,
|
||||
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
||||
|
||||
return newClientContext(
|
||||
provider, certChainFile, trustManagerFactory, null, null, null, null,
|
||||
ciphers, IdentityCipherSuiteFilter.INSTANCE,
|
||||
@ -593,9 +611,20 @@ public abstract class SslContext {
|
||||
if (provider != null && provider != SslProvider.JDK) {
|
||||
throw new SSLException("client context unsupported for: " + provider);
|
||||
}
|
||||
|
||||
return new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
|
||||
if (provider == null) {
|
||||
provider = SslProvider.JDK;
|
||||
}
|
||||
switch (provider) {
|
||||
case JDK:
|
||||
new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
|
||||
case OPENSSL:
|
||||
return new OpenSslClientContext(
|
||||
trustCertChainFile, trustManagerFactory,
|
||||
ciphers, apn, sessionCacheSize, sessionTimeout);
|
||||
}
|
||||
// Should never happen!!
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
static ApplicationProtocolConfig toApplicationProtocolConfig(Iterable<String> nextProtocols) {
|
||||
@ -693,4 +722,24 @@ public abstract class SslContext {
|
||||
private static SslHandler newHandler(SSLEngine engine) {
|
||||
return new SslHandler(engine);
|
||||
}
|
||||
|
||||
static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(null, null);
|
||||
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
|
||||
try {
|
||||
for (ByteBuf buf: certs) {
|
||||
X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteBufInputStream(buf));
|
||||
X500Principal principal = cert.getSubjectX500Principal();
|
||||
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
|
||||
}
|
||||
} finally {
|
||||
for (ByteBuf buf: certs) {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
trustManagerFactory.init(ks);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ import java.security.KeyStore;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* An insecure {@link javax.net.ssl.TrustManagerFactory} that trusts all X.509 certificates without any verification.
|
||||
* An insecure {@link TrustManagerFactory} that trusts all X.509 certificates without any verification.
|
||||
* <p>
|
||||
* <strong>NOTE:</strong>
|
||||
* Never use this {@link javax.net.ssl.TrustManagerFactory} in production.
|
||||
* Never use this {@link TrustManagerFactory} in production.
|
||||
* It is purely for testing purposes, and thus it is very insecure.
|
||||
* </p>
|
||||
*/
|
||||
|
2
pom.xml
2
pom.xml
@ -559,7 +559,7 @@
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<version>1.1.30.Fork2</version>
|
||||
<version>1.1.30.Fork3-SNAPSHOT</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
|
@ -30,6 +30,7 @@ import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.JdkSslClientContext;
|
||||
import io.netty.handler.ssl.JdkSslServerContext;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.OpenSslClientContext;
|
||||
import io.netty.handler.ssl.OpenSslServerContext;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
@ -84,9 +85,7 @@ public class SocketSslGreetingTest extends AbstractSocketTest {
|
||||
boolean hasOpenSsl = OpenSsl.isAvailable();
|
||||
if (hasOpenSsl) {
|
||||
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
||||
|
||||
// TODO: Client mode is not supported yet.
|
||||
// clientContexts.add(new OpenSslContext(CERT_FILE));
|
||||
clientContexts.add(new OpenSslClientContext(CERT_FILE));
|
||||
} else {
|
||||
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.JdkSslClientContext;
|
||||
import io.netty.handler.ssl.JdkSslServerContext;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.OpenSslClientContext;
|
||||
import io.netty.handler.ssl.OpenSslServerContext;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
@ -91,9 +92,7 @@ public class SocketStartTlsTest extends AbstractSocketTest {
|
||||
boolean hasOpenSsl = OpenSsl.isAvailable();
|
||||
if (hasOpenSsl) {
|
||||
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
||||
|
||||
// TODO: Client mode is not supported yet.
|
||||
// clientContexts.add(new OpenSslContext(CERT_FILE));
|
||||
clientContexts.add(new OpenSslClientContext(CERT_FILE));
|
||||
} else {
|
||||
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user