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
79bb200f59
commit
682df517c9
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
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 InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
|
||||||
private static final Throwable UNAVAILABILITY_CAUSE;
|
private static final Throwable UNAVAILABILITY_CAUSE;
|
||||||
|
private static final String IGNORABLE_ERROR_PREFIX = "error:00000000:";
|
||||||
static final String IGNORABLE_ERROR_PREFIX = "error:00000000:";
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Throwable cause = null;
|
Throwable cause = null;
|
||||||
@ -80,5 +79,9 @@ public final class OpenSsl {
|
|||||||
return UNAVAILABILITY_CAUSE;
|
return UNAVAILABILITY_CAUSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isError(String error) {
|
||||||
|
return error != null && !error.startsWith(IGNORABLE_ERROR_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
private OpenSsl() { }
|
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.SSLEngine;
|
||||||
import javax.net.ssl.SSLEngineResult;
|
import javax.net.ssl.SSLEngineResult;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSessionContext;
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
import javax.security.cert.X509Certificate;
|
import javax.security.cert.X509Certificate;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ReadOnlyBufferException;
|
import java.nio.ReadOnlyBufferException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.cert.Certificate;
|
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.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
||||||
import static javax.net.ssl.SSLEngineResult.Status.*;
|
import static javax.net.ssl.SSLEngineResult.Status.*;
|
||||||
@ -47,12 +54,9 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
|
||||||
|
|
||||||
private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0];
|
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 ENGINE_CLOSED = new SSLException("engine closed");
|
||||||
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported");
|
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported");
|
||||||
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized");
|
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
||||||
RENEGOTIATION_UNSUPPORTED.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 =
|
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER =
|
||||||
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
|
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
|
||||||
|
|
||||||
|
private static final Pattern CIPHER_REPLACE_PATTERN = Pattern.compile("-");
|
||||||
// OpenSSL state
|
// OpenSSL state
|
||||||
private long ssl;
|
private long ssl;
|
||||||
private long networkBIO;
|
private long networkBIO;
|
||||||
@ -84,8 +89,12 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private volatile int destroyed;
|
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 String applicationProtocol;
|
||||||
|
private volatile Certificate[] peerCerts;
|
||||||
|
private volatile X509Certificate[] x509PeerCerts;
|
||||||
|
|
||||||
// SSL Engine status variables
|
// SSL Engine status variables
|
||||||
private boolean isInboundDone;
|
private boolean isInboundDone;
|
||||||
@ -94,6 +103,8 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
private int lastPrimingReadResult;
|
private int lastPrimingReadResult;
|
||||||
|
|
||||||
|
private final X509TrustManager[] managers;
|
||||||
|
private final boolean clientMode;
|
||||||
private final ByteBufAllocator alloc;
|
private final ByteBufAllocator alloc;
|
||||||
private final String fallbackApplicationProtocol;
|
private final String fallbackApplicationProtocol;
|
||||||
private SSLSession session;
|
private SSLSession session;
|
||||||
@ -104,7 +115,20 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
* @param sslCtx an OpenSSL {@code SSL_CTX} object
|
* @param sslCtx an OpenSSL {@code SSL_CTX} object
|
||||||
* @param alloc the {@link ByteBufAllocator} that will be used by this engine
|
* @param alloc the {@link ByteBufAllocator} that will be used by this engine
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) {
|
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();
|
OpenSsl.ensureAvailability();
|
||||||
if (sslCtx == 0) {
|
if (sslCtx == 0) {
|
||||||
throw new NullPointerException("sslContext");
|
throw new NullPointerException("sslContext");
|
||||||
@ -114,9 +138,15 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.alloc = alloc;
|
this.alloc = alloc;
|
||||||
ssl = SSL.newSSL(sslCtx, true);
|
ssl = SSL.newSSL(sslCtx, !clientMode);
|
||||||
networkBIO = SSL.makeNetworkBIO(ssl);
|
networkBIO = SSL.makeNetworkBIO(ssl);
|
||||||
this.fallbackApplicationProtocol = fallbackApplicationProtocol;
|
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 {
|
} else {
|
||||||
ByteBuf buf = alloc.directBuffer(len);
|
ByteBuf buf = alloc.directBuffer(len);
|
||||||
try {
|
try {
|
||||||
final long addr;
|
final long addr = memoryAddress(buf);
|
||||||
if (buf.hasMemoryAddress()) {
|
|
||||||
addr = buf.memoryAddress();
|
|
||||||
} else {
|
|
||||||
addr = Buffer.address(buf.nioBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
src.limit(pos + len);
|
src.limit(pos + len);
|
||||||
|
|
||||||
@ -198,12 +223,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
} else {
|
} else {
|
||||||
final ByteBuf buf = alloc.directBuffer(len);
|
final ByteBuf buf = alloc.directBuffer(len);
|
||||||
try {
|
try {
|
||||||
final long addr;
|
final long addr = memoryAddress(buf);
|
||||||
if (buf.hasMemoryAddress()) {
|
|
||||||
addr = buf.memoryAddress();
|
|
||||||
} else {
|
|
||||||
addr = Buffer.address(buf.nioBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.setBytes(0, src);
|
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 int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
|
||||||
final ByteBuf buf = alloc.directBuffer(len);
|
final ByteBuf buf = alloc.directBuffer(len);
|
||||||
try {
|
try {
|
||||||
final long addr;
|
final long addr = memoryAddress(buf);
|
||||||
if (buf.hasMemoryAddress()) {
|
|
||||||
addr = buf.memoryAddress();
|
|
||||||
} else {
|
|
||||||
addr = Buffer.address(buf.nioBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
final int sslRead = SSL.readFromSSL(ssl, addr, len);
|
final int sslRead = SSL.readFromSSL(ssl, addr, len);
|
||||||
if (sslRead > 0) {
|
if (sslRead > 0) {
|
||||||
@ -279,12 +294,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
} else {
|
} else {
|
||||||
final ByteBuf buf = alloc.directBuffer(pending);
|
final ByteBuf buf = alloc.directBuffer(pending);
|
||||||
try {
|
try {
|
||||||
final long addr;
|
final long addr = memoryAddress(buf);
|
||||||
if (buf.hasMemoryAddress()) {
|
|
||||||
addr = buf.memoryAddress();
|
|
||||||
} else {
|
|
||||||
addr = Buffer.address(buf.nioBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
||||||
if (bioRead > 0) {
|
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
|
// In handshake or close_notify stages, check if call to wrap was made
|
||||||
// without regard to the handshake status.
|
// without regard to the handshake status.
|
||||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||||
|
|
||||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
|
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
|
||||||
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
|
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
|
||||||
}
|
}
|
||||||
@ -367,7 +378,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
shutdown();
|
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
|
// 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?
|
// Do we have enough room in dst to write encrypted data?
|
||||||
int capacity = dst.remaining();
|
int capacity = dst.remaining();
|
||||||
if (capacity < pendingNet) {
|
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
|
// 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);
|
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
|
@Override
|
||||||
@ -449,7 +461,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
// In handshake or close_notify stages, check if call to unwrap was made
|
// In handshake or close_notify stages, check if call to unwrap was made
|
||||||
// without regard to the handshake status.
|
// without regard to the handshake status.
|
||||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
|
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
|
||||||
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
|
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
|
// Check for OpenSSL errors caused by the priming read
|
||||||
String error = SSL.getLastError();
|
String error = SSL.getLastError();
|
||||||
if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
|
if (OpenSsl.isError(error)) {
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"SSL_read failed: primingReadResult: " + lastPrimingReadResult +
|
"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?
|
// Do we have enough room in dsts to write decrypted data?
|
||||||
if (capacity < pendingApp) {
|
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
|
// Write decrypted data to dsts buffers
|
||||||
@ -534,7 +546,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
closeInbound();
|
closeInbound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -622,14 +634,65 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
throw new UnsupportedOperationException();
|
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
|
@Override
|
||||||
public SSLSession getSession() {
|
public SSLSession getSession() {
|
||||||
SSLSession session = this.session;
|
SSLSession session = this.session;
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
this.session = session = new SSLSession() {
|
this.session = session = new SSLSession() {
|
||||||
|
private volatile byte[] id;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getId() {
|
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
|
@Override
|
||||||
@ -675,8 +738,16 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Certificate[] getPeerCertificates() {
|
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
|
||||||
return EMPTY_CERTIFICATES;
|
// 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
|
@Override
|
||||||
@ -685,8 +756,28 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public X509Certificate[] getPeerCertificateChain() {
|
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
|
||||||
return EMPTY_X509_CERTIFICATES;
|
// 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
|
@Override
|
||||||
@ -747,7 +838,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
switch (accepted) {
|
switch (accepted) {
|
||||||
case 0:
|
case 0:
|
||||||
SSL.doHandshake(ssl);
|
handshake();
|
||||||
accepted = 2;
|
accepted = 2;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
@ -772,17 +863,53 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (accepted == 0) {
|
if (accepted == 0) {
|
||||||
SSL.doHandshake(ssl);
|
handshake();
|
||||||
accepted = 1;
|
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() {
|
private SSLEngineResult.Status getEngineStatus() {
|
||||||
return engineClosed? CLOSED : OK;
|
return engineClosed? CLOSED : OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
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) {
|
if (accepted == 0 || destroyed != 0) {
|
||||||
return NOT_HANDSHAKING;
|
return NOT_HANDSHAKING;
|
||||||
}
|
}
|
||||||
@ -798,7 +925,12 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
// Check to see if we have finished handshaking
|
// Check to see if we have finished handshaking
|
||||||
if (SSL.isInInit(ssl) == 0) {
|
if (SSL.isInInit(ssl) == 0) {
|
||||||
handshakeFinished = true;
|
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);
|
String applicationProtocol = SSL.getNextProtoNegotiated(ssl);
|
||||||
if (applicationProtocol == null) {
|
if (applicationProtocol == null) {
|
||||||
applicationProtocol = fallbackApplicationProtocol;
|
applicationProtocol = fallbackApplicationProtocol;
|
||||||
@ -808,6 +940,10 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
} else {
|
} else {
|
||||||
this.applicationProtocol = null;
|
this.applicationProtocol = null;
|
||||||
}
|
}
|
||||||
|
if (verify && managers != null) {
|
||||||
|
peerCerts = initPeerCertChain();
|
||||||
|
verifyCertificates(managers, peerCerts, cipher);
|
||||||
|
}
|
||||||
return FINISHED;
|
return FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,14 +968,14 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUseClientMode(boolean clientMode) {
|
public void setUseClientMode(boolean clientMode) {
|
||||||
if (clientMode) {
|
if (clientMode != this.clientMode) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getUseClientMode() {
|
public boolean getUseClientMode() {
|
||||||
return false;
|
return clientMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -878,6 +1014,31 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
return false;
|
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
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
super.finalize();
|
super.finalize();
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
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.SSL;
|
||||||
import org.apache.tomcat.jni.SSLContext;
|
import org.apache.tomcat.jni.SSLContext;
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static io.netty.util.internal.ObjectUtil.*;
|
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.
|
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||||
*/
|
*/
|
||||||
public final class OpenSslServerContext extends SslContext {
|
public final class OpenSslServerContext extends OpenSslContext {
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
@ -163,6 +123,7 @@ public final class OpenSslServerContext extends SslContext {
|
|||||||
Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn,
|
Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn,
|
||||||
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
||||||
|
|
||||||
|
super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER);
|
||||||
OpenSsl.ensureAvailability();
|
OpenSsl.ensureAvailability();
|
||||||
|
|
||||||
checkNotNull(certChainFile, "certChainFile");
|
checkNotNull(certChainFile, "certChainFile");
|
||||||
@ -170,65 +131,28 @@ public final class OpenSslServerContext extends SslContext {
|
|||||||
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
|
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
|
||||||
}
|
}
|
||||||
checkNotNull(keyFile, "keyFile");
|
checkNotNull(keyFile, "keyFile");
|
||||||
this.apn = checkNotNull(apn, "apn");
|
|
||||||
if (!keyFile.isFile()) {
|
if (!keyFile.isFile()) {
|
||||||
throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
|
throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
|
||||||
}
|
}
|
||||||
if (ciphers == null) {
|
|
||||||
ciphers = DEFAULT_CIPHERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyPassword == null) {
|
if (keyPassword == null) {
|
||||||
keyPassword = "";
|
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.
|
// Create a new SSL_CTX and configure it.
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
synchronized (OpenSslServerContext.class) {
|
synchronized (OpenSslContext.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set certificate verification policy. */
|
/* 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. */
|
/* Load the certificate file and private key. */
|
||||||
try {
|
try {
|
||||||
@ -242,51 +166,6 @@ public final class OpenSslServerContext extends SslContext {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, 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;
|
success = true;
|
||||||
} finally {
|
} finally {
|
||||||
@ -294,47 +173,6 @@ public final class OpenSslServerContext extends SslContext {
|
|||||||
destroyPools();
|
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
|
@Override
|
||||||
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||||
List<String> protocols = apn.protocols();
|
List<String> protos = applicationProtocolNegotiator().protocols();
|
||||||
if (protocols.isEmpty()) {
|
if (protos.isEmpty()) {
|
||||||
return new OpenSslEngine(ctx, alloc, null);
|
return new OpenSslEngine(ctx, alloc, null, isClient(), null);
|
||||||
} else {
|
} else {
|
||||||
return new OpenSslEngine(ctx, alloc, protocols.get(protocols.size() - 1));
|
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.buffer.ByteBufInputStream;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
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.SSLException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
import java.io.File;
|
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;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +66,15 @@ import java.util.List;
|
|||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public abstract class SslContext {
|
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.
|
* Returns the default server-side implementation provider currently in use.
|
||||||
*
|
*
|
||||||
@ -509,7 +528,6 @@ public abstract class SslContext {
|
|||||||
File certChainFile, TrustManagerFactory trustManagerFactory,
|
File certChainFile, TrustManagerFactory trustManagerFactory,
|
||||||
Iterable<String> ciphers, Iterable<String> nextProtocols,
|
Iterable<String> ciphers, Iterable<String> nextProtocols,
|
||||||
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
||||||
|
|
||||||
return newClientContext(
|
return newClientContext(
|
||||||
provider, certChainFile, trustManagerFactory, null, null, null, null,
|
provider, certChainFile, trustManagerFactory, null, null, null, null,
|
||||||
ciphers, IdentityCipherSuiteFilter.INSTANCE,
|
ciphers, IdentityCipherSuiteFilter.INSTANCE,
|
||||||
@ -593,9 +611,20 @@ public abstract class SslContext {
|
|||||||
if (provider != null && provider != SslProvider.JDK) {
|
if (provider != null && provider != SslProvider.JDK) {
|
||||||
throw new SSLException("client context unsupported for: " + provider);
|
throw new SSLException("client context unsupported for: " + provider);
|
||||||
}
|
}
|
||||||
|
if (provider == null) {
|
||||||
return new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
|
provider = SslProvider.JDK;
|
||||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
|
}
|
||||||
|
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) {
|
static ApplicationProtocolConfig toApplicationProtocolConfig(Iterable<String> nextProtocols) {
|
||||||
@ -693,4 +722,24 @@ public abstract class SslContext {
|
|||||||
private static SslHandler newHandler(SSLEngine engine) {
|
private static SslHandler newHandler(SSLEngine engine) {
|
||||||
return new SslHandler(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;
|
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>
|
* <p>
|
||||||
* <strong>NOTE:</strong>
|
* <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.
|
* It is purely for testing purposes, and thus it is very insecure.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
2
pom.xml
2
pom.xml
@ -567,7 +567,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>netty-tcnative</artifactId>
|
<artifactId>netty-tcnative</artifactId>
|
||||||
<version>1.1.30.Fork2</version>
|
<version>1.1.30.Fork3-SNAPSHOT</version>
|
||||||
<classifier>${os.detected.classifier}</classifier>
|
<classifier>${os.detected.classifier}</classifier>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
|
@ -30,6 +30,7 @@ import io.netty.handler.logging.LoggingHandler;
|
|||||||
import io.netty.handler.ssl.JdkSslClientContext;
|
import io.netty.handler.ssl.JdkSslClientContext;
|
||||||
import io.netty.handler.ssl.JdkSslServerContext;
|
import io.netty.handler.ssl.JdkSslServerContext;
|
||||||
import io.netty.handler.ssl.OpenSsl;
|
import io.netty.handler.ssl.OpenSsl;
|
||||||
|
import io.netty.handler.ssl.OpenSslClientContext;
|
||||||
import io.netty.handler.ssl.OpenSslServerContext;
|
import io.netty.handler.ssl.OpenSslServerContext;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||||
@ -84,9 +85,7 @@ public class SocketSslGreetingTest extends AbstractSocketTest {
|
|||||||
boolean hasOpenSsl = OpenSsl.isAvailable();
|
boolean hasOpenSsl = OpenSsl.isAvailable();
|
||||||
if (hasOpenSsl) {
|
if (hasOpenSsl) {
|
||||||
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
||||||
|
clientContexts.add(new OpenSslClientContext(CERT_FILE));
|
||||||
// TODO: Client mode is not supported yet.
|
|
||||||
// clientContexts.add(new OpenSslContext(CERT_FILE));
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
|
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.JdkSslClientContext;
|
||||||
import io.netty.handler.ssl.JdkSslServerContext;
|
import io.netty.handler.ssl.JdkSslServerContext;
|
||||||
import io.netty.handler.ssl.OpenSsl;
|
import io.netty.handler.ssl.OpenSsl;
|
||||||
|
import io.netty.handler.ssl.OpenSslClientContext;
|
||||||
import io.netty.handler.ssl.OpenSslServerContext;
|
import io.netty.handler.ssl.OpenSslServerContext;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslHandler;
|
import io.netty.handler.ssl.SslHandler;
|
||||||
@ -91,9 +92,7 @@ public class SocketStartTlsTest extends AbstractSocketTest {
|
|||||||
boolean hasOpenSsl = OpenSsl.isAvailable();
|
boolean hasOpenSsl = OpenSsl.isAvailable();
|
||||||
if (hasOpenSsl) {
|
if (hasOpenSsl) {
|
||||||
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
serverContexts.add(new OpenSslServerContext(CERT_FILE, KEY_FILE));
|
||||||
|
clientContexts.add(new OpenSslClientContext(CERT_FILE));
|
||||||
// TODO: Client mode is not supported yet.
|
|
||||||
// clientContexts.add(new OpenSslContext(CERT_FILE));
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
|
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user