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:
Norman Maurer 2014-10-13 13:21:29 +02:00 committed by Norman Maurer
parent 79bb200f59
commit 682df517c9
11 changed files with 756 additions and 311 deletions

View File

@ -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;

View File

@ -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() { }
} }

View File

@ -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);
}
}
}

View 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());
}
}
}

View File

@ -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();

View File

@ -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());
} }
} }
} }

View File

@ -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;
}
switch (provider) {
case JDK:
new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); 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);
}
} }

View File

@ -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>
*/ */

View File

@ -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>

View File

@ -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());
} }

View File

@ -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());
} }