diff --git a/pom.xml b/pom.xml index db327349b8..dece63c2f5 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,15 @@ + + ${project.groupId} + netty-tcnative + 1.1.30.Fork1 + ${os.detected.classifier} + compile + true + + org.jboss.marshalling @@ -186,7 +195,7 @@ junit junit - 4.10 + 4.11 test @@ -228,6 +237,14 @@ + + + kr.motd.maven + os-maven-plugin + 1.2.0.Final + + + ${basedir}/src/main/resources diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java new file mode 100644 index 0000000000..d7a0b72d04 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java @@ -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() { } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java new file mode 100644 index 0000000000..fbf2d91a95 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslBufferPool.java @@ -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 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(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() + + ']'; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java new file mode 100644 index 0000000000..fa09d56fc5 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslContextBuilder.java @@ -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 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 cipherSpec) { + if (cipherSpec == null) { + this.cipherSpec = null; + return this; + } + + List list = new ArrayList(); + 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(); + } + + 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); + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java new file mode 100644 index 0000000000..8a985a0f96 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java @@ -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 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; + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java new file mode 100644 index 0000000000..950ae281bb --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslServerContext.java @@ -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: + *
+ * 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;
+ *     }
+ * }
+ * 
+ * + */ +public final class OpenSslServerContext { + + private final long aprPool; + private final OpenSslBufferPool bufPool; + private final boolean destroyAprPool; + + private final List cipherSpec = new ArrayList(); + private final List 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 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 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); + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/OpenSslSessionStats.java b/src/main/java/org/jboss/netty/handler/ssl/OpenSslSessionStats.java new file mode 100644 index 0000000000..a1143a0361 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/ssl/OpenSslSessionStats.java @@ -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 SSL_CTX_sess_number + */ +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); + } +} diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java index 477485926a..4bb4a0ce18 100644 --- a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java +++ b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java @@ -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; diff --git a/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java b/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java new file mode 100644 index 0000000000..65cb0f2dd6 --- /dev/null +++ b/src/main/java/org/jboss/netty/util/internal/EmptyArrays.java @@ -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() { } +} diff --git a/src/main/java/org/jboss/netty/util/internal/NativeLibraryLoader.java b/src/main/java/org/jboss/netty/util/internal/NativeLibraryLoader.java new file mode 100644 index 0000000000..97f6dca922 --- /dev/null +++ b/src/main/java/org/jboss/netty/util/internal/NativeLibraryLoader.java @@ -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 + } +} diff --git a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java index 0eafb0a016..f56cf00cfa 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslEchoTest.java @@ -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 engines() throws Exception { + List params = new ArrayList(); + + List serverEngines = new ArrayList(); + serverEngines.add(new SSLEngineFactory("JDK") { + @Override + public SSLEngine newEngine() { + return SecureChatSslContextFactory.getServerContext().createSSLEngine(); + } + }); + + List clientEngines = new ArrayList(); + 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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java index d4b0a16107..4ce7b4ad0a 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/AbstractSocketSslGreetingTest.java @@ -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 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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java index 9ba12d33ea..a51cdf54fa 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslEchoTest.java @@ -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); } - } diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java index 891cdf2e38..3aac984624 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/NioNioSocketSslGreetingTest.java @@ -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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java index 8309788f35..35d771f982 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/NioOioSocketSslEchoTest.java @@ -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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java index e413bd88f7..bfeea1bb83 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslEchoTest.java @@ -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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java index dcd2597ff8..8227ac96d9 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/OioNioSocketSslGreetingTest.java @@ -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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java b/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java index e742cb592b..345f36f375 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/OioOioSocketSslEchoTest.java @@ -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); diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java b/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java index cbea97a195..3664ac4d47 100644 --- a/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java +++ b/src/test/java/org/jboss/netty/handler/ssl/SslCloseTest.java @@ -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(); } diff --git a/src/test/resources/openssl.crt b/src/test/resources/openssl.crt new file mode 100644 index 0000000000..cda3ea6464 --- /dev/null +++ b/src/test/resources/openssl.crt @@ -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----- diff --git a/src/test/resources/openssl.pem b/src/test/resources/openssl.pem new file mode 100644 index 0000000000..76aa8ebff3 --- /dev/null +++ b/src/test/resources/openssl.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +bduIWhdShKYyRjkDQQXwrb5KQtHeOqU= +-----END PRIVATE KEY-----