Add OpenSslEngine

Motivation:

Some users already use an SSLEngine implementation in finagle-native. It
wraps OpenSSL to get higher SSL performance.  However, to take advantage
of it, finagle-native must be compiled manually, and it means we cannot
pull it in as a dependency and thus we cannot test our SslHandler
against the OpenSSL-based SSLEngine.  For an instance, we had #2216.

Modifications:

- Pull netty-tcnative in as an optional dependency.
  http://netty.io/wiki/forked-tomcat-native.html
- Backport NativeLibraryLoader from 4.0
- Move OpenSSL-based SSLEngine implementation into our code base.
  - Copied from finagle-native; originally written by @jpinner et al.
  - Overall cleanup by @trustin.
- Run all SslHandler tests with both default SSLEngine and OpenSslEngine
This commit is contained in:
Trustin Lee 2014-05-03 18:09:20 +09:00
parent ef9ee90b1a
commit 4cff4b99fd
21 changed files with 2108 additions and 30 deletions

19
pom.xml
View File

@ -71,6 +71,15 @@
</developers>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.30.Fork1</version>
<classifier>${os.detected.classifier}</classifier>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- JBoss Marshalling - completely optional -->
<dependency>
<groupId>org.jboss.marshalling</groupId>
@ -186,7 +195,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
@ -228,6 +237,14 @@
</properties>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.2.0.Final</version>
</extension>
</extensions>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>

View File

@ -0,0 +1,77 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.apache.tomcat.jni.Library;
import org.apache.tomcat.jni.SSL;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.internal.NativeLibraryLoader;
/**
* Tells if {@code netty-tcnative} and its OpenSSL support are available.
*/
public final class OpenSsl {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
private static final Throwable UNAVAILABILITY_CAUSE;
static {
Throwable cause = null;
try {
NativeLibraryLoader.load("netty-tcnative", SSL.class.getClassLoader());
Library.initialize("provided");
SSL.initialize(null);
} catch (Throwable t) {
cause = t;
logger.debug(
"Failed to load netty-tcnative; " +
OpenSslEngine.class.getSimpleName() + " will be unavailable.", t);
}
UNAVAILABILITY_CAUSE = cause;
}
/**
* Returns {@code true} if and only if {@code netty-tcnative} and its OpenSSL support are available.
*/
public static boolean isAvailable() {
return UNAVAILABILITY_CAUSE == null;
}
/**
* Ensure that {@code netty-tcnative} and its OpenSSL support are available.
*
* @throws UnsatisfiedLinkError if unavailable
*/
public static void ensureAvailability() {
if (UNAVAILABILITY_CAUSE != null) {
throw (Error) new UnsatisfiedLinkError(
"failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
}
}
/**
* Returns the cause of unavailability of {@code netty-tcnative} and its OpenSSL support.
*
* @return the cause if unavailable. {@code null} if available.
*/
public static Throwable unavailabilityCause() {
return UNAVAILABILITY_CAUSE;
}
private OpenSsl() { }
}

View File

@ -0,0 +1,95 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.jboss.netty.util.internal.EmptyArrays;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Manages a pool of directly-allocated ByteBuffers.
*
* This is necessary as the reclamation of these buffers does not work appropriately
* on some platforms.
*
* TODO: Attempt to replace the directly-allocated ByteBuffers this with one APR pool.
*/
public final class OpenSslBufferPool {
private static final RuntimeException ALLOCATION_INTERRUPTED =
new IllegalStateException("buffer allocation interrupted");
static {
ALLOCATION_INTERRUPTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
// BUFFER_SIZE must be large enough to accomodate the maximum SSL record size.
// Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
private static final int BUFFER_SIZE = 18713;
private final BlockingQueue<ByteBuffer> buffers;
/**
* Construct a new pool with the specified capacity.
*
* @param capacity The number of buffers to instantiate.
*/
public OpenSslBufferPool(int capacity) {
OpenSsl.ensureAvailability();
buffers = new LinkedBlockingQueue<ByteBuffer>(capacity);
while (buffers.remainingCapacity() > 0) {
ByteBuffer buf = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder());
buffers.offer(buf);
}
}
/**
* Take a buffer from the pool.
*
* @return a ByteBuffer.
*/
public ByteBuffer acquire() {
try {
return buffers.take();
} catch (InterruptedException ignore) {
throw ALLOCATION_INTERRUPTED;
}
}
/**
* Release a buffer back into the stream
*
* @param buffer the ByteBuffer to release
*/
public void release(ByteBuffer buffer) {
buffer.clear();
buffers.offer(buffer);
}
@Override
public String toString() {
return "[DirectBufferPool " +
buffers.size() + " buffers * " +
BUFFER_SIZE + " bytes = " +
buffers.size() * BUFFER_SIZE + " total bytes; " +
"size: " + buffers.size() +
" remainingCapacity: " + buffers.remainingCapacity() +
']';
}
}

View File

@ -0,0 +1,164 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.jboss.netty.util.internal.StringUtil;
import javax.net.ssl.SSLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Builds a new OpenSSL context object.
*/
public class OpenSslContextBuilder {
private long aprPool;
private OpenSslBufferPool bufPool;
private String certPath;
private String keyPath;
private List<String> cipherSpec;
private int sessionCacheSize;
private int sessionTimeout;
private String keyPassword;
private String caPath;
private String nextProtos;
public OpenSslContextBuilder() {
OpenSsl.ensureAvailability();
}
public OpenSslContextBuilder certPath(String certPath) {
if (certPath == null) {
throw new NullPointerException("certPath");
}
this.certPath = certPath;
return this;
}
public OpenSslContextBuilder keyPath(String keyPath) {
if (keyPath == null) {
throw new NullPointerException("keyPath");
}
this.keyPath = keyPath;
return this;
}
public OpenSslContextBuilder cipherSpec(Iterable<String> cipherSpec) {
if (cipherSpec == null) {
this.cipherSpec = null;
return this;
}
List<String> list = new ArrayList<String>();
for (String c: cipherSpec) {
if (c == null) {
break;
}
if (c.contains(":")) {
for (String cc : StringUtil.split(c, ':')) {
if (cc.length() != 0) {
list.add(cc);
}
}
} else {
list.add(c);
}
}
if (list.isEmpty()) {
this.cipherSpec = null;
} else {
this.cipherSpec = list;
}
return this;
}
public OpenSslContextBuilder cipherSpec(String... cipherSpec) {
if (cipherSpec == null) {
throw new NullPointerException("cipherSpec");
}
return cipherSpec(Arrays.asList(cipherSpec));
}
public OpenSslContextBuilder keyPassword(String keyPassword) {
this.keyPassword = keyPassword;
return this;
}
public OpenSslContextBuilder caPath(String caPath) {
this.caPath = caPath;
return this;
}
public OpenSslContextBuilder nextProtos(String nextProtos) {
this.nextProtos = nextProtos;
return this;
}
public OpenSslContextBuilder sessionCacheSize(int sessionCacheSize) {
this.sessionCacheSize = sessionCacheSize;
return this;
}
public OpenSslContextBuilder sessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
return this;
}
public OpenSslContextBuilder aprPool(long aprPool) {
this.aprPool = aprPool;
return this;
}
public OpenSslContextBuilder bufPool(OpenSslBufferPool bufPool) {
this.bufPool = bufPool;
return this;
}
/**
* Creates a new server context.
*
* @throws SSLException if the required fields are not assigned
*/
public OpenSslServerContext newServerContext() throws SSLException {
// If the cipherSpec was not specified or empty, use the default.
if (cipherSpec == null) {
cipherSpec = new ArrayList<String>();
}
if (cipherSpec.isEmpty()) {
Collections.addAll(
cipherSpec,
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-RSA-RC4-SHA",
"ECDHE-RSA-AES128-SHA",
"ECDHE-RSA-AES256-SHA",
"AES128-GCM-SHA256",
"RC4-SHA",
"RC4-MD5",
"AES128-SHA",
"AES256-SHA",
"DES-CBC3-SHA");
}
return new OpenSslServerContext(
aprPool, bufPool,
certPath, keyPath, keyPassword, caPath, nextProtos, cipherSpec, sessionCacheSize, sessionTimeout);
}
}

View File

@ -0,0 +1,801 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.apache.tomcat.jni.Buffer;
import org.apache.tomcat.jni.SSL;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.internal.EmptyArrays;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.security.cert.X509Certificate;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
import static javax.net.ssl.SSLEngineResult.Status.*;
/**
* Implements a java.net.SSLEngine in terms of OpenSSL.
*
* Documentation on the dataflow and operation of SSLEngine and OpenSSL BIO abstractions
* can be found at:
*
* SSLEngine: http://download.oracle.com/javase/1,5.0/docs/api/javax/net/ssl/SSLEngine.html
* OpenSSL: http://www.openssl.org/docs/crypto/BIO_s_bio.html#example
*/
public final class OpenSslEngine extends SSLEngine {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0];
private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0];
private static final SSLException ENGINE_CLOSED = new SSLException("engine closed");
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported");
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized");
static {
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
ENCRYPTED_PACKET_OVERSIZED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
private static final int MAX_ENCRYPTED_PACKET = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
private static final String SSL_IGNORABLE_ERROR_PREFIX = "error:00000000:";
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
// OpenSSL state
private long ssl;
private long networkBIO;
/**
* 0 - not accepted, 1 - accepted implicitly via wrap()/unwrap(), 2 - accepted explicitly via beginHandshake() call
*/
private int accepted;
private boolean handshakeFinished;
private boolean receivedShutdown;
@SuppressWarnings("UnusedDeclaration")
private volatile int destroyed;
private String cipher;
private String protocol;
// SSL Engine status variables
private boolean isInboundDone;
private boolean isOutboundDone;
private boolean engineClosed;
private int lastPrimingReadResult;
private final OpenSslBufferPool bufPool;
private SSLSession session;
public OpenSslEngine(long sslContext, OpenSslBufferPool bufPool) {
OpenSsl.ensureAvailability();
if (sslContext == 0) {
throw new NullPointerException("sslContext");
}
if (bufPool == null) {
throw new NullPointerException("bufPool");
}
this.bufPool = bufPool;
ssl = SSL.newSSL(sslContext, true);
networkBIO = SSL.makeNetworkBIO(ssl);
}
public synchronized void shutdown() {
if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
SSL.freeSSL(ssl);
SSL.freeBIO(networkBIO);
ssl = networkBIO = 0;
// internal errors can cause shutdown without marking the engine closed
isInboundDone = isOutboundDone = engineClosed = true;
}
}
/**
* Write plaintext data to the OpenSSL internal BIO
*
* Calling this function with src.remaining == 0 is undefined.
*/
private int writePlaintextData(final ByteBuffer src) {
final ByteBuffer buf = bufPool.acquire();
final long addr = Buffer.address(buf);
try {
int position = src.position();
int limit = src.limit();
int len = Math.min(src.remaining(), MAX_PLAINTEXT_LENGTH);
if (len > buf.capacity()) {
throw new IllegalStateException("buffer pool write overflow");
}
src.limit(position + len);
buf.put(src);
src.limit(limit);
final int sslWrote = SSL.writeToSSL(ssl, addr, len);
if (sslWrote > 0) {
src.position(position + sslWrote);
return sslWrote;
} else {
src.position(position);
throw new IllegalStateException("SSL.writeToSSL() returned a non-positive value: " + sslWrote);
}
} finally {
bufPool.release(buf);
}
}
/**
* Write encrypted data to the OpenSSL network BIO
*/
private int writeEncryptedData(final ByteBuffer src) {
final ByteBuffer buf = bufPool.acquire();
final long addr = Buffer.address(buf);
try {
int position = src.position();
int len = src.remaining();
if (len > buf.capacity()) {
throw new IllegalStateException("buffer pool write overflow");
}
buf.put(src);
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(position + netWrote);
lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read
return netWrote;
} else {
src.position(position);
return 0;
}
} finally {
bufPool.release(buf);
}
}
/**
* Read plaintext data from the OpenSSL internal BIO
*/
private int readPlaintextData(final ByteBuffer dst) {
final ByteBuffer buf = bufPool.acquire();
final long addr = Buffer.address(buf);
try {
final int len = Math.min(buf.capacity(), dst.capacity());
buf.limit(len);
final int sslRead = SSL.readFromSSL(ssl, addr, len);
if (sslRead > 0) {
buf.limit(sslRead);
dst.put(buf);
return sslRead;
} else {
return 0;
}
} finally {
bufPool.release(buf);
}
}
/**
* Read encrypted data from the OpenSSL network BIO
*/
private int readEncryptedData(final ByteBuffer dst, final int pending) {
final ByteBuffer buf = bufPool.acquire();
final long addr = Buffer.address(buf);
try {
if (pending > buf.capacity()) {
throw new IllegalStateException("network BIO read overflow " +
"(pending: " + pending + ", capacity: " + buf.capacity() + ')');
}
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
if (bioRead > 0) {
buf.limit(bioRead);
dst.put(buf);
return bioRead;
} else {
return 0;
}
} finally {
bufPool.release(buf);
}
}
/**
* Encrypt plaintext data from srcs buffers into dst buffer.
*
* This is called both to encrypt application data as well as
* to retrieve handshake data destined for the peer.
*/
@Override
public synchronized SSLEngineResult wrap(
final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException {
// Check to make sure the engine has not been closed
if (destroyed != 0) {
return new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0);
}
// Throw required runtime exceptions
if (srcs == null) {
throw new NullPointerException("srcs");
}
if (dst == null) {
throw new NullPointerException("dst");
}
if (offset >= srcs.length || offset + length > srcs.length) {
throw new IndexOutOfBoundsException(
"offset: " + offset + ", length: " + length +
" (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
}
if (dst.isReadOnly()) {
throw new ReadOnlyBufferException();
}
// Prepare OpenSSL to work in server mode and receive handshake
if (accepted == 0) {
beginHandshakeImplicitly();
}
// In handshake or close_notify stages, check if call to wrap was made
// without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
}
int bytesProduced = 0;
int pendingNet;
// Check for pending data in the network BIO
pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
if (pendingNet > 0) {
// Do we have enough room in dst to write encrypted data?
int capacity = dst.remaining();
if (capacity < pendingNet) {
return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus, 0, bytesProduced);
}
// Write the pending data from the network BIO into the dst buffer
try {
bytesProduced += readEncryptedData(dst, pendingNet);
} catch (Exception e) {
throw new SSLException(e);
}
// If isOuboundDone is set, then the data from the network BIO
// was the close_notify message -- we are not required to wait
// for the receipt the peer's close_notify message -- shutdown.
if (isOutboundDone) {
shutdown();
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced);
}
// There was no pending data in the network BIO -- encrypt any application data
int bytesConsumed = 0;
for (int i = offset; i < length; ++ i) {
final ByteBuffer src = srcs[i];
while (src.hasRemaining()) {
// Write plaintext application data to the SSL engine
try {
bytesConsumed += writePlaintextData(src);
} catch (Exception e) {
throw new SSLException(e);
}
// Check to see if the engine wrote data into the network BIO
pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
if (pendingNet > 0) {
// Do we have enough room in dst to write encrypted data?
int capacity = dst.remaining();
if (capacity < pendingNet) {
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
}
// Write the pending data from the network BIO into the dst buffer
try {
bytesProduced += readEncryptedData(dst, pendingNet);
} catch (Exception e) {
throw new SSLException(e);
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
}
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
/**
* Decrypt encrypted data from src buffers into dsts buffers.
*/
@Override
public synchronized SSLEngineResult unwrap(
final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
// Check to make sure the engine has not been closed
if (destroyed != 0) {
return new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0);
}
// Throw requried runtime exceptions
if (src == null) {
throw new NullPointerException("src");
}
if (dsts == null) {
throw new NullPointerException("dsts");
}
if (offset >= dsts.length || offset + length > dsts.length) {
throw new IndexOutOfBoundsException(
"offset: " + offset + ", length: " + length +
" (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
}
int capacity = 0;
for (int i = offset; i < offset + length; ++i) {
ByteBuffer dst = dsts[i];
if (dst == null) {
throw new IllegalArgumentException();
}
if (dst.isReadOnly()) {
throw new ReadOnlyBufferException();
}
capacity += dst.remaining();
}
// Prepare OpenSSL to work in server mode and receive handshake
if (accepted == 0) {
beginHandshakeImplicitly();
}
// In handshake or close_notify stages, check if call to unwrap was made
// without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
}
// protect against protocol overflow attack vector
if (src.remaining() > MAX_ENCRYPTED_PACKET) {
isInboundDone = true;
isOutboundDone = true;
engineClosed = true;
shutdown();
throw ENCRYPTED_PACKET_OVERSIZED;
}
// Write encrypted data to network BIO
int bytesConsumed = 0;
lastPrimingReadResult = 0;
try {
bytesConsumed += writeEncryptedData(src);
} catch (Exception e) {
throw new SSLException(e);
}
// Check for OpenSSL errors caused by the priming read
String error = SSL.getLastError();
if (error != null && !error.startsWith(SSL_IGNORABLE_ERROR_PREFIX)) {
if (logger.isInfoEnabled()) {
logger.info(
"SSL_read failed: primingReadResult: " + lastPrimingReadResult +
"; OpenSSL error: '" + error + '\'');
}
// There was an internal error -- shutdown
shutdown();
throw new SSLException(error);
}
// There won't be any application data until we're done handshaking
int pendingApp = SSL.isInInit(ssl) == 0 ? SSL.pendingReadableBytesInSSL(ssl) : 0;
// Do we have enough room in dsts to write decrypted data?
if (capacity < pendingApp) {
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
}
// Write decrypted data to dsts buffers
int bytesProduced = 0;
int idx = offset;
for (;;) {
ByteBuffer dst = dsts[idx];
if (!dst.hasRemaining()) {
idx ++;
continue;
}
if (pendingApp <= 0) {
break;
}
int bytesRead;
try {
bytesRead = readPlaintextData(dst);
} catch (Exception e) {
throw new SSLException(e);
}
if (bytesRead == 0) {
break;
}
bytesProduced += bytesRead;
pendingApp -= bytesRead;
if (!dst.hasRemaining()) {
idx ++;
}
}
// Check to see if we received a close_notify message from the peer
if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
receivedShutdown = true;
closeOutbound();
closeInbound();
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
/**
* Currently we do not delegate SSL computation tasks
* TODO: in the future, possibly create tasks to do encrypt / decrypt async
*/
@Override
public Runnable getDelegatedTask() {
return null;
}
/**
* This method is called on channel disconnection by SSLHandler when the server
* did not initiate the closure process to detect against truncation attacks.
*/
@Override
public synchronized void closeInbound() throws SSLException {
if (isInboundDone) {
return;
}
isInboundDone = true;
engineClosed = true;
if (accepted != 0) {
if (!receivedShutdown) {
shutdown();
throw new SSLException("close_notify has not been received");
}
} else {
// engine closing before initial handshake
shutdown();
}
}
@Override
public synchronized boolean isInboundDone() {
return isInboundDone || engineClosed;
}
/**
* This method is called on channel disconnection to send close_notify
*/
@Override
public synchronized void closeOutbound() {
if (isOutboundDone) {
return;
}
isOutboundDone = true;
engineClosed = true;
if (accepted != 0 && destroyed == 0) {
int mode = SSL.getShutdown(ssl);
if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
SSL.shutdownSSL(ssl);
}
} else {
// engine closing before initial handshake
shutdown();
}
}
@Override
public synchronized boolean isOutboundDone() {
return isOutboundDone;
}
@Override
public String[] getSupportedCipherSuites() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public String[] getEnabledCipherSuites() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public void setEnabledCipherSuites(String[] strings) {
throw new UnsupportedOperationException();
}
@Override
public String[] getSupportedProtocols() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public String[] getEnabledProtocols() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public void setEnabledProtocols(String[] strings) {
throw new UnsupportedOperationException();
}
@Override
public SSLSession getSession() {
SSLSession session = this.session;
if (session == null) {
this.session = session = new SSLSession() {
public byte[] getId() {
return String.valueOf(ssl).getBytes();
}
public SSLSessionContext getSessionContext() {
return null;
}
public long getCreationTime() {
return 0;
}
public long getLastAccessedTime() {
return 0;
}
public void invalidate() {
}
public boolean isValid() {
return false;
}
public void putValue(String s, Object o) {
}
public Object getValue(String s) {
return null;
}
public void removeValue(String s) {
}
public String[] getValueNames() {
return EmptyArrays.EMPTY_STRINGS;
}
public Certificate[] getPeerCertificates() {
return EMPTY_CERTIFICATES;
}
public Certificate[] getLocalCertificates() {
return EMPTY_CERTIFICATES;
}
public X509Certificate[] getPeerCertificateChain() {
return EMPTY_X509_CERTIFICATES;
}
public Principal getPeerPrincipal() {
return null;
}
public Principal getLocalPrincipal() {
return null;
}
public String getCipherSuite() {
return cipher;
}
public String getProtocol() {
return protocol;
}
public String getPeerHost() {
return null;
}
public int getPeerPort() {
return 0;
}
public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET;
}
public int getApplicationBufferSize() {
return MAX_PLAINTEXT_LENGTH;
}
};
}
return session;
}
/**
* This method causes the OpenSSL engine to accept connections
*/
@Override
public synchronized void beginHandshake() throws SSLException {
if (engineClosed) {
throw ENGINE_CLOSED;
}
switch (accepted) {
case 0:
SSL.doHandshake(ssl);
accepted = 2;
break;
case 1:
// A user did not start handshake by calling this method by him/herself,
// but handshake has been started already by wrap() or unwrap() implicitly.
// Because it's the user's first time to call this method, it is unfair to
// raise an exception. From the user's standpoint, he or she never asked
// for renegotiation.
accepted = 2; // Next time this method is invoked by the user, we should raise an exception.
break;
case 2:
throw RENEGOTIATION_UNSUPPORTED;
default:
throw new Error();
}
}
private synchronized void beginHandshakeImplicitly() throws SSLException {
if (engineClosed) {
throw ENGINE_CLOSED;
}
if (accepted == 0) {
SSL.doHandshake(ssl);
accepted = 1;
}
}
private SSLEngineResult.Status getEngineStatus() {
return engineClosed? CLOSED : OK;
}
/**
* Return the handshake status of the SSL Engine.
*/
@Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
if (accepted == 0 || destroyed != 0) {
return NOT_HANDSHAKING;
}
// Check if we are in the initial handshake phase
if (!handshakeFinished) {
// There is pending data in the network BIO -- call wrap
if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
return NEED_WRAP;
}
// No pending data to be sent to the peer
// Check to see if we have finished handshaking
if (SSL.isInInit(ssl) == 0) {
handshakeFinished = true;
cipher = SSL.getCipherForSSL(ssl);
protocol = SSL.getNextProtoNegotiated(ssl);
return FINISHED;
}
// No pending data and still handshaking
// Must be waiting on the peer to send more data
return NEED_UNWRAP;
}
// Check if we are in the shutdown phase
if (engineClosed) {
// Waiting to send the close_notify message
if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
return NEED_WRAP;
}
// Must be waiting to receive the close_notify message
return NEED_UNWRAP;
}
return NOT_HANDSHAKING;
}
@Override
public void setUseClientMode(boolean clientMode) {
if (clientMode) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getUseClientMode() {
return false;
}
@Override
public void setNeedClientAuth(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getNeedClientAuth() {
return false;
}
@Override
public void setWantClientAuth(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getWantClientAuth() {
return false;
}
@Override
public void setEnableSessionCreation(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getEnableSessionCreation() {
return false;
}
}

View File

@ -0,0 +1,285 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Creates a new {@link OpenSslEngine}. Internally, this factory keeps the SSL_CTX object of OpenSSL.
* This factory is intended for a shared use by multiple channels:
* <pre>
* public class MyChannelPipelineFactory extends {@link ChannelPipelineFactory} {
*
* private final {@link OpenSslServerContext} sslEngineFactory = ...;
*
* public {@link ChannelPipeline} getPipeline() {
* {@link ChannelPipeline} p = {@link Channels#pipeline() Channels.pipeline()};
* p.addLast("ssl", new {@link SslHandler}(sslEngineFactory.newEngine()));
* ...
* return p;
* }
* }
* </pre>
*
*/
public final class OpenSslServerContext {
private final long aprPool;
private final OpenSslBufferPool bufPool;
private final boolean destroyAprPool;
private final List<String> cipherSpec = new ArrayList<String>();
private final List<String> unmodifiableCipherSpec = Collections.unmodifiableList(cipherSpec);
private final String cipherSpecText;
private final long sessionCacheSize;
private final long sessionTimeout;
private final String nextProtos;
/** The OpenSSL SSL_CTX object */
private final long ctx;
private final OpenSslSessionStats stats;
public OpenSslServerContext(
long aprPool, OpenSslBufferPool bufPool,
String certPath, String keyPath, String keyPassword,
String caPath, String nextProtos, Iterable<String> ciphers,
long sessionCacheSize, long sessionTimeout) throws SSLException {
OpenSsl.ensureAvailability();
if (certPath == null) {
throw new NullPointerException("certPath");
}
if (certPath.length() == 0) {
throw new IllegalArgumentException("certPath is empty.");
}
if (keyPath == null) {
throw new NullPointerException("keyPath");
}
if (keyPath.length() == 0) {
throw new IllegalArgumentException("keyPath is empty.");
}
if (ciphers == null) {
throw new NullPointerException("ciphers");
}
if (keyPassword == null) {
keyPassword = "";
}
if (caPath == null) {
caPath = "";
}
if (nextProtos == null) {
nextProtos = "";
}
for (String c: ciphers) {
if (c == null) {
break;
}
cipherSpec.add(c);
}
this.nextProtos = nextProtos;
// Convert the cipher list into a colon-separated string.
StringBuilder cipherSpecBuf = new StringBuilder();
for (String c: cipherSpec) {
cipherSpecBuf.append(c);
cipherSpecBuf.append(':');
}
cipherSpecBuf.setLength(cipherSpecBuf.length() - 1);
cipherSpecText = cipherSpecBuf.toString();
// Allocate a new APR pool if necessary.
if (aprPool == 0) {
aprPool = Pool.create(0);
destroyAprPool = true;
} else {
destroyAprPool = false;
}
// Allocate a new OpenSSL buffer pool if necessary.
boolean success = false;
try {
if (bufPool == null) {
bufPool = new OpenSslBufferPool(Runtime.getRuntime().availableProcessors() * 2);
}
success = true;
} finally {
if (!success && destroyAprPool) {
Pool.destroy(aprPool);
}
}
this.aprPool = aprPool;
this.bufPool = bufPool;
// Create a new SSL_CTX and configure it.
success = false;
try {
synchronized (OpenSslServerContext.class) {
try {
ctx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
} catch (Exception e) {
throw new SSLException("failed to create an SSL_CTX", e);
}
SSLContext.setOptions(ctx, SSL.SSL_OP_ALL);
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_SSLv2);
SSLContext.setOptions(ctx, SSL.SSL_OP_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 {
SSLContext.setCipherSuite(ctx, cipherSpecText);
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set cipher suite: " + cipherSpecText, e);
}
/* Set certificate verification policy. */
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10);
/* Load the certificate file and private key. */
try {
if (!SSLContext.setCertificate(
ctx, certPath, keyPath, keyPassword, SSL.SSL_AIDX_RSA)) {
throw new SSLException(
"failed to set certificate: " + certPath + " (" + SSL.getLastError() + ')');
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set certificate: " + certPath, e);
}
/* Load certificate chain file, if specified */
if (caPath.length() != 0) {
/* If named same as cert file, we must skip the first cert since it was loaded above. */
boolean skipFirstCert = certPath.equals(caPath);
if (!SSLContext.setCertificateChainFile(ctx, caPath, skipFirstCert)) {
throw new SSLException(
"failed to set certificate chain: " + caPath + " (" + SSL.getLastError() + ')');
}
}
/* Set next protocols for next protocol negotiation extension, if specified */
if (nextProtos.length() != 0) {
SSLContext.setNextProtos(ctx, nextProtos);
}
/* 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);
}
public List<String> cipherSpec() {
return unmodifiableCipherSpec;
}
public String cipherSpecText() {
return cipherSpecText;
}
public String nextProtos() {
return nextProtos;
}
public int sessionCacheSize() {
return sessionCacheSize > Integer.MAX_VALUE? Integer.MAX_VALUE : (int) sessionCacheSize;
}
public int sessionTimeout() {
return sessionTimeout > Integer.MAX_VALUE? Integer.MAX_VALUE : (int) sessionTimeout;
}
public OpenSslSessionStats stats() {
return stats;
}
public void setTicketKeys(byte[] 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 (destroyAprPool && aprPool != 0) {
Pool.destroy(aprPool);
}
}
/**
* Returns a new server-side {@link SSLEngine} with the current configuration.
*/
public SSLEngine newEngine() {
return new OpenSslEngine(ctx, bufPool);
}
}

View File

@ -0,0 +1,122 @@
/*
* 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 org.jboss.netty.handler.ssl;
import org.apache.tomcat.jni.SSLContext;
/**
* Stats exposed by an OpenSSL session context.
*
* @see <a href="https://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html"><code>SSL_CTX_sess_number</code></a>
*/
public final class OpenSslSessionStats {
private final long context;
OpenSslSessionStats(long context) {
this.context = context;
}
/**
* Returns the current number of sessions in the internal session cache.
*/
public long number() {
return SSLContext.sessionNumber(context);
}
/**
* Returns the number of started SSL/TLS handshakes in client mode.
*/
public long connect() {
return SSLContext.sessionConnect(context);
}
/**
* Returns the number of successfully established SSL/TLS sessions in client mode.
*/
public long connectGood() {
return SSLContext.sessionConnectGood(context);
}
/**
* Returns the number of start renegotiations in client mode.
*/
public long connectRenegotiate() {
return SSLContext.sessionConnectRenegotiate(context);
}
/**
* Returns the number of started SSL/TLS handshakes in server mode.
*/
public long accept() {
return SSLContext.sessionAccept(context);
}
/**
* Returns the number of successfully established SSL/TLS sessions in server mode.
*/
public long acceptGood() {
return SSLContext.sessionAcceptGood(context);
}
/**
* Returns the number of start renegotiations in server mode.
*/
public long acceptRenegotiate() {
return SSLContext.sessionAcceptRenegotiate(context);
}
/**
* Returns the number of successfully reused sessions. In client mode, a session set with {@code SSL_set_session}
* successfully reused is counted as a hit. In server mode, a session successfully retrieved from internal or
* external cache is counted as a hit.
*/
public long hits() {
return SSLContext.sessionHits(context);
}
/**
* Returns the number of successfully retrieved sessions from the external session cache in server mode.
*/
public long cbHits() {
return SSLContext.sessionCbHits(context);
}
/**
* Returns the number of sessions proposed by clients that were not found in the internal session cache
* in server mode.
*/
public long misses() {
return SSLContext.sessionMisses(context);
}
/**
* Returns the number of sessions proposed by clients and either found in the internal or external session cache
* in server mode, but that were invalid due to timeout. These sessions are not included in the {@link #hits()}
* count.
*/
public long timeouts() {
return SSLContext.sessionTimeouts(context);
}
/**
* Returns the number of sessions that were removed because the maximum session cache size was exceeded.
*/
public long cacheFull() {
return SSLContext.sessionCacheFull(context);
}
}

View File

@ -632,8 +632,7 @@ public class SslHandler extends FrameDecoder
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
// Make sure the handshake future is notified when a connection has
// been closed during handshake.
@ -648,18 +647,23 @@ public class SslHandler extends FrameDecoder
super.channelDisconnected(ctx, e);
} finally {
unwrapNonAppData(ctx, e.getChannel());
engine.closeOutbound();
if (sentCloseNotify == 0 && handshaken) {
try {
engine.closeInbound();
} catch (SSLException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to clean up SSLEngine.", ex);
}
closeEngine();
}
}
private void closeEngine() {
engine.closeOutbound();
if (sentCloseNotify == 0 && handshaken) {
try {
engine.closeInbound();
} catch (SSLException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to clean up SSLEngine.", ex);
}
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
@ -1250,6 +1254,9 @@ public class SslHandler extends FrameDecoder
for (int i = 0; i < nRecords; i ++) {
inNetBuf.limit(inNetBuf.position() + recordLengths[i]);
frame = unwrapSingle(ctx, channel, buffer, inNetBuf, frame, totalLength);
if (engine.isInboundDone()) {
break;
}
assert !inNetBuf.hasRemaining();
}
@ -1671,6 +1678,7 @@ public class SslHandler extends FrameDecoder
*/
@Override
public void afterRemove(ChannelHandlerContext ctx) throws Exception {
closeEngine();
// there is no need for synchronization here as we do not receive downstream events anymore
Throwable cause = null;

View File

@ -0,0 +1,36 @@
/*
* Copyright 2013 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 org.jboss.netty.util.internal;
import java.nio.ByteBuffer;
public final class EmptyArrays {
public static final byte[] EMPTY_BYTES = new byte[0];
public static final boolean[] EMPTY_BOOLEANS = new boolean[0];
public static final double[] EMPTY_DOUBLES = new double[0];
public static final float[] EMPTY_FLOATS = new float[0];
public static final int[] EMPTY_INTS = new int[0];
public static final short[] EMPTY_SHORTS = new short[0];
public static final long[] EMPTY_LONGS = new long[0];
public static final Object[] EMPTY_OBJECTS = new Object[0];
public static final String[] EMPTY_STRINGS = new String[0];
public static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
public static final ByteBuffer[] EMPTY_BYTE_BUFFERS = new ByteBuffer[0];
private EmptyArrays() { }
}

View File

@ -0,0 +1,235 @@
/*
* 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 org.jboss.netty.util.internal;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Locale;
/**
* Helper class to load JNI resources.
*
*/
public final class NativeLibraryLoader {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class);
private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
private static final String OSNAME;
private static final File WORKDIR;
static {
OSNAME = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
if (workdir != null) {
File f = new File(workdir);
if (!f.exists()) {
// ok to ignore as createTempFile will take care
//noinspection ResultOfMethodCallIgnored
f.mkdirs();
}
try {
f = f.getAbsoluteFile();
} catch (Exception ignored) {
// Good to have an absolute path, but it's OK.
}
WORKDIR = f;
logger.debug("-Dio.netty.netty.workdir: " + WORKDIR);
} else {
WORKDIR = tmpdir();
logger.debug("-Dio.netty.netty.workdir: " + WORKDIR + " (io.netty.tmpdir)");
}
}
private static File tmpdir() {
File f;
try {
f = toDirectory(SystemPropertyUtil.get("io.netty.tmpdir"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f);
return f;
}
f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (java.io.tmpdir)");
return f;
}
// This shouldn't happen, but just in case ..
if (isWindows()) {
f = toDirectory(System.getenv("TEMP"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%TEMP%)");
return f;
}
String userprofile = System.getenv("USERPROFILE");
if (userprofile != null) {
f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\AppData\\Local\\Temp)");
return f;
}
f = toDirectory(userprofile + "\\Local Settings\\Temp");
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\Local Settings\\Temp)");
return f;
}
}
} else {
f = toDirectory(System.getenv("TMPDIR"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " ($TMPDIR)");
return f;
}
}
} catch (Exception ignored) {
// Environment variable inaccessible
}
// Last resort.
if (isWindows()) {
f = new File("C:\\Windows\\Temp");
} else {
f = new File("/tmp");
}
logger.warn("Failed to get the temporary directory; falling back to: " + f);
return f;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static File toDirectory(String path) {
if (path == null) {
return null;
}
File f = new File(path);
if (!f.exists()) {
f.mkdirs();
}
if (!f.isDirectory()) {
return null;
}
try {
return f.getAbsoluteFile();
} catch (Exception ignored) {
return f;
}
}
private static boolean isWindows() {
return OSNAME.startsWith("windows");
}
private static boolean isOSX() {
return OSNAME.startsWith("macosx") || OSNAME.startsWith("osx");
}
/**
* Load the given library with the specified {@link java.lang.ClassLoader}
*/
public static void load(String name, ClassLoader loader) {
String libname = System.mapLibraryName(name);
String path = NATIVE_RESOURCE_HOME + libname;
URL url = loader.getResource(path);
if (url == null && isOSX()) {
if (path.endsWith(".jnilib")) {
url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib");
} else {
url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib");
}
}
if (url == null) {
// Fall back to normal loading of JNI stuff
System.loadLibrary(name);
return;
}
int index = libname.lastIndexOf('.');
String prefix = libname.substring(0, index);
String suffix = libname.substring(index, libname.length());
InputStream in = null;
OutputStream out = null;
File tmpFile = null;
boolean loaded = false;
try {
tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
in = url.openStream();
out = new FileOutputStream(tmpFile);
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
out.flush();
out.close();
out = null;
System.load(tmpFile.getPath());
loaded = true;
} catch (Exception e) {
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError(
"could not load a native library: " + name).initCause(e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignore) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException ignore) {
// ignore
}
}
if (tmpFile != null) {
if (loaded) {
tmpFile.deleteOnExit();
} else {
if (!tmpFile.delete()) {
tmpFile.deleteOnExit();
}
}
}
}
}
private NativeLibraryLoader() {
// Utility
}
}

View File

@ -34,10 +34,19 @@ import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.TestUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import javax.net.ssl.SSLEngine;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@ -46,6 +55,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
@RunWith(Parameterized.class)
public abstract class AbstractSocketSslEchoTest {
static final InternalLogger logger =
InternalLoggerFactory.getInstance(AbstractSocketSslEchoTest.class);
@ -57,6 +67,111 @@ public abstract class AbstractSocketSslEchoTest {
random.nextBytes(data);
}
@Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}")
public static Collection<SSLEngineFactory[]> engines() throws Exception {
List<SSLEngineFactory[]> params = new ArrayList<SSLEngineFactory[]>();
List<SSLEngineFactory> serverEngines = new ArrayList<SSLEngineFactory>();
serverEngines.add(new SSLEngineFactory("JDK") {
@Override
public SSLEngine newEngine() {
return SecureChatSslContextFactory.getServerContext().createSSLEngine();
}
});
List<SSLEngineFactory> clientEngines = new ArrayList<SSLEngineFactory>();
clientEngines.add(new SSLEngineFactory("JDK") {
@Override
public SSLEngine newEngine() {
return SecureChatSslContextFactory.getClientContext().createSSLEngine();
}
});
boolean hasOpenSsl = OpenSsl.isAvailable();
if (hasOpenSsl) {
final String certPath = File.createTempFile("ssl_test_", ".crt").getAbsolutePath();
new File(certPath).deleteOnExit();
final String keyPath = File.createTempFile("ssl_test_", ".pem").getAbsolutePath();
new File(keyPath).deleteOnExit();
ClassLoader cl = AbstractSocketSslEchoTest.class.getClassLoader();
FileOutputStream certOut = new FileOutputStream(certPath);
InputStream certIn = cl.getResourceAsStream("openssl.crt");
for (;;) {
int b = certIn.read();
if (b < 0) {
break;
}
certOut.write(b);
}
certOut.close();
FileOutputStream keyOut = new FileOutputStream(keyPath);
InputStream keyIn = cl.getResourceAsStream("openssl.pem");
for (;;) {
int b = keyIn.read();
if (b < 0) {
break;
}
keyOut.write(b);
}
keyOut.close();
final OpenSslContextBuilder ctxBuilder = new OpenSslContextBuilder();
final OpenSslServerContext ctx = ctxBuilder.certPath(certPath).keyPath(keyPath).newServerContext();
serverEngines.add(new SSLEngineFactory("TCNative") {
@Override
public SSLEngine newEngine() throws Exception {
return ctx.newEngine();
}
});
// TODO: Client mode is not supported yet.
/*
clientEngines.add(new SSLEngineFactory("TCNative") {
public SSLEngine newEngine() {
return new OpenSSLEngine(contextHolder, sslBufferPool);
}
});
*/
} else {
logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
}
for (SSLEngineFactory sf: serverEngines) {
for (SSLEngineFactory cf: clientEngines) {
params.add(new SSLEngineFactory[] { sf, cf });
}
}
return params;
}
protected abstract static class SSLEngineFactory {
private final String name;
SSLEngineFactory(String name) {
this.name = name;
}
public abstract SSLEngine newEngine() throws Exception;
@Override
public String toString() {
return name;
}
}
private final SSLEngineFactory serverEngineFactory;
private final SSLEngineFactory clientEngineFactory;
protected AbstractSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
this.serverEngineFactory = serverEngineFactory;
this.clientEngineFactory = clientEngineFactory;
}
protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor);
protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor);
@ -94,8 +209,8 @@ public abstract class AbstractSocketSslEchoTest {
EchoHandler sh = new EchoHandler(true);
EchoHandler ch = new EchoHandler(false);
SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine();
SSLEngine cse = SecureChatSslContextFactory.getClientContext().createSSLEngine();
SSLEngine sse = serverEngineFactory.newEngine();
SSLEngine cse = clientEngineFactory.newEngine();
sse.setUseClientMode(false);
cse.setUseClientMode(true);

View File

@ -27,15 +27,19 @@ import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.example.securechat.SecureChatSslContextFactory;
import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.TestUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import javax.net.ssl.SSLEngine;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -43,15 +47,29 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
@RunWith(Parameterized.class)
public abstract class AbstractSocketSslGreetingTest {
static final InternalLogger logger =
InternalLoggerFactory.getInstance(AbstractSocketSslGreetingTest.class);
@Parameters(name = "{index}: serverEngine = {0}, clientEngine = {1}")
public static Collection<SSLEngineFactory[]> engines() throws Exception {
return AbstractSocketSslEchoTest.engines();
}
private final ChannelBuffer greeting = ChannelBuffers.wrappedBuffer(new byte[] {'a'});
protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor);
protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor);
private final SSLEngineFactory serverEngineFactory;
private final SSLEngineFactory clientEngineFactory;
protected AbstractSocketSslGreetingTest(
SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
this.serverEngineFactory = serverEngineFactory;
this.clientEngineFactory = clientEngineFactory;
}
@Test
public void testSslEcho() throws Throwable {
@ -61,8 +79,8 @@ public abstract class AbstractSocketSslGreetingTest {
ServerHandler sh = new ServerHandler();
ClientHandler ch = new ClientHandler();
SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine();
SSLEngine cse = SecureChatSslContextFactory.getClientContext().createSSLEngine();
SSLEngine sse = serverEngineFactory.newEngine();
SSLEngine cse = clientEngineFactory.newEngine();
sse.setUseClientMode(false);
cse.setUseClientMode(true);

View File

@ -15,14 +15,18 @@
*/
package org.jboss.netty.handler.ssl;
import java.util.concurrent.Executor;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import java.util.concurrent.Executor;
public class NioNioSocketSslEchoTest extends AbstractSocketSslEchoTest {
public NioNioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new NioClientSocketChannelFactory(executor, executor);
@ -32,5 +36,4 @@ public class NioNioSocketSslEchoTest extends AbstractSocketSslEchoTest {
protected ChannelFactory newServerSocketChannelFactory(Executor executor) {
return new NioServerSocketChannelFactory(executor, executor);
}
}

View File

@ -18,11 +18,16 @@ package org.jboss.netty.handler.ssl;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory;
import java.util.concurrent.Executor;
public class NioNioSocketSslGreetingTest extends AbstractSocketSslGreetingTest {
public NioNioSocketSslGreetingTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new NioClientSocketChannelFactory(executor, executor);

View File

@ -15,14 +15,18 @@
*/
package org.jboss.netty.handler.ssl;
import java.util.concurrent.Executor;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;
import java.util.concurrent.Executor;
public class NioOioSocketSslEchoTest extends AbstractSocketSslEchoTest {
public NioOioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new NioClientSocketChannelFactory(executor, executor);

View File

@ -15,14 +15,18 @@
*/
package org.jboss.netty.handler.ssl;
import java.util.concurrent.Executor;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;
import java.util.concurrent.Executor;
public class OioNioSocketSslEchoTest extends AbstractSocketSslEchoTest {
public OioNioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new OioClientSocketChannelFactory(executor);

View File

@ -18,11 +18,16 @@ package org.jboss.netty.handler.ssl;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;
import org.jboss.netty.handler.ssl.AbstractSocketSslEchoTest.SSLEngineFactory;
import java.util.concurrent.Executor;
public class OioNioSocketSslGreetingTest extends AbstractSocketSslGreetingTest {
public OioNioSocketSslGreetingTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new OioClientSocketChannelFactory(executor);

View File

@ -15,14 +15,18 @@
*/
package org.jboss.netty.handler.ssl;
import java.util.concurrent.Executor;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;
import java.util.concurrent.Executor;
public class OioOioSocketSslEchoTest extends AbstractSocketSslEchoTest {
public OioOioSocketSslEchoTest(SSLEngineFactory serverEngineFactory, SSLEngineFactory clientEngineFactory) {
super(serverEngineFactory, clientEngineFactory);
}
@Override
protected ChannelFactory newClientSocketChannelFactory(Executor executor) {
return new OioClientSocketChannelFactory(executor);

View File

@ -15,10 +15,6 @@
*/
package org.jboss.netty.handler.ssl;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLEngine;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffers;
@ -33,6 +29,9 @@ import org.jboss.netty.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
import javax.net.ssl.SSLEngine;
import java.net.InetSocketAddress;
public class SslCloseTest {
/**
@ -52,7 +51,6 @@ public class SslCloseTest {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
System.out.println("Close channel");
ctx.getChannel().close();
}

View File

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFGzCCAwOgAwIBAgIJAKuufohIAUhRMA0GCSqGSIb3DQEBBQUAMCMxCzAJBgNV
BAYTAktSMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAgFw0xNDA0MjExMzMxMzJaGA8y
Mjg4MDIwNDEzMzEzMlowIzELMAkGA1UEBhMCS1IxFDASBgNVBAMMC2V4YW1wbGUu
Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxtiOHUgcCtSvEvH4
cPgOuBEIy6EPoHlOfUm1S0m7vOy8ESynBuOAn5Be1QP8lB/F69DQoRD41OBbdrA1
pcpIQmCORheKUwv3EZSt3kzVJonqE4HW1FKyQA8Ej5RqsfHtDaCfO8uzAmuJXMg2
2Gj/6fO/BKuPdkrAwywIqAxW2bKSVwKSId1sRilRku+rK9a1d0xiWAGr8YC8KZrv
5101I7A9OcTAJtoI46OB5pcltjLhXKSyrN3wyoxsKVQkGHzB5Uvnua/E9CnOPmW8
SDuQ/1w+/haUftKsFAPdjy5auDEIctti89YcOJaJfMzZP1hxrSAuDaMHKEdjySpx
DXxp3Nvab0uiwYEPCHKE72JjY7/3oaZ6AzNxpjSwyHLCmuFFg4BsL9V1zAuz7E6F
zxrm+CBq3J5U0NNBW9/zBTAqPTjBJSX7KZTvwlWMTs/kKs4pfeJX/Z97JYwUk7Kk
6uVfaoHIIezfksJX8/dzzXn0p11+3OkPqcHw/7dDtxlX5rtontCrhLbq0LhvAsrh
7dEOBnNFXxYzF7yNM7spBeRvqxcNPKNCclQiINeYbO8JCRBw+jmtevUDxqLiRJsu
7Q1SxUlnGD0MQZUvy4p5S+L/amE74qJDgSaCftWW7KKnAB0Z2ZaNHV3aZ+Sn6ExF
CSrTFEJzaXT7Cvbo6pa6XQ2sij0CAwEAAaNQME4wHQYDVR0OBBYEFHWMy8+VATLk
sDQGt9fMc6MssoS2MB8GA1UdIwQYMBaAFHWMy8+VATLksDQGt9fMc6MssoS2MAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggIBAFoGovC4fNFJjiSIo8BDNjOx
XEBdhUpzm+9i6h0PjBriwd2SyQHmldVXsshJmcmnNaC7cMrQG4cSk4DzzHAZIFl9
xQmhktlJTqSzKr+bda27URQqw3rPIntvcQe3SchOG73wOLxtoar3z8G/o3m6kPyB
5l2ZjrI658iN11tjuc/wqmm5VCIOanWmJUDoryh/Ndg4nLcxsx36/iD+V/NV//xO
jTL1HdnLtK895hrW5Q6/1qN3KlerVoI4WOYvfcpNg380vfcxYuhZ+db5WHRJXG+I
V/nnDMmnFHgQbVqat7TZFUoEyac7BqScs5x25uXx6liTGZ9I7kpPrx3mE1fhatyp
poc+gSm1Va6tNo/UI+oo8NRbKWHJE2TSsMAuHDfyKzDi0yMdTPuVzpbLVjCgXVdv
cOUD05AX3imqlucwqhtFciwhvRgB82CZhBONrMr2zwWGedWMv50m+r80AFFvaTht
Xs+LWAtT29RuhhtsRz1yZFxvCiYHclpONGqy09Z1qYPQvqv51tz/SunbCjizhJm9
/qNvQ/oqUPsPz30TV6GE9G6q6exS/S7zZdlwI/fNdnOTRbd0r0bvb8hLXc9Ooup8
7F9Py5ZIvjgpmFj6aZHG2inzw5KVT/sR8kznipuTR5uqQNBlfUypjpY2BX+5sJ7d
UL8VfLsSGIwQN3CFZG8k
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
bduIWhdShKYyRjkDQQXwrb5KQtHeOqU=
-----END PRIVATE KEY-----