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:
parent
ef9ee90b1a
commit
4cff4b99fd
19
pom.xml
19
pom.xml
@ -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>
|
||||
|
77
src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java
Normal file
77
src/main/java/org/jboss/netty/handler/ssl/OpenSsl.java
Normal 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() { }
|
||||
}
|
@ -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() +
|
||||
']';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
801
src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java
Normal file
801
src/main/java/org/jboss/netty/handler/ssl/OpenSslEngine.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
36
src/main/java/org/jboss/netty/util/internal/EmptyArrays.java
Normal file
36
src/main/java/org/jboss/netty/util/internal/EmptyArrays.java
Normal 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() { }
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
30
src/test/resources/openssl.crt
Normal file
30
src/test/resources/openssl.crt
Normal 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-----
|
52
src/test/resources/openssl.pem
Normal file
52
src/test/resources/openssl.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
bduIWhdShKYyRjkDQQXwrb5KQtHeOqU=
|
||||
-----END PRIVATE KEY-----
|
Loading…
x
Reference in New Issue
Block a user