org.jboss.netty.handler.ssl.OpenSslServerContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of netty Show documentation
Show all versions of netty Show documentation
The Netty project is an effort to provide an asynchronous event-driven
network application framework and tools for rapid development of
maintainable high performance and high scalability protocol servers and
clients. In other words, Netty is a NIO client server framework which
enables quick and easy development of network applications such as protocol
servers and clients. It greatly simplifies and streamlines network
programming such as TCP and UDP socket server.
/*
* 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.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/
public final class OpenSslServerContext extends SslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
private static final List DEFAULT_CIPHERS;
static {
List ciphers = new ArrayList();
// XXX: Make sure to sync this list with JdkSslEngineFactory.
Collections.addAll(
ciphers,
"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");
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
if (logger.isDebugEnabled()) {
logger.debug("Default cipher suite (OpenSSL): " + ciphers);
}
}
private final long aprPool;
private final List ciphers = new ArrayList();
private final List unmodifiableCiphers = Collections.unmodifiableList(ciphers);
private final long sessionCacheSize;
private final long sessionTimeout;
private final List nextProtocols;
/** The OpenSSL SSL_CTX object */
private final long ctx;
private final OpenSslSessionStats stats;
/**
* Creates a new instance.
*
* @param certChainFile an X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
*/
public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException {
this(certChainFile, keyFile, null);
}
/**
* Creates a new instance.
*
* @param certChainFile an X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
*/
public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
this(null, certChainFile, keyFile, keyPassword, null, null, 0, 0);
}
/**
* Creates a new instance.
*
* @param bufPool the buffer pool which will be used by this context.
* {@code null} to use the default buffer pool.
* @param certChainFile an X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
* {@code 0} to use the default value.
*/
public OpenSslServerContext(
SslBufferPool bufPool,
File certChainFile, File keyFile, String keyPassword,
Iterable ciphers, Iterable nextProtocols,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(bufPool);
OpenSsl.ensureAvailability();
if (certChainFile == null) {
throw new NullPointerException("certChainFile");
}
if (!certChainFile.isFile()) {
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
}
if (keyFile == null) {
throw new NullPointerException("keyPath");
}
if (!keyFile.isFile()) {
throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
}
if (ciphers == null) {
ciphers = DEFAULT_CIPHERS;
}
if (keyPassword == null) {
keyPassword = "";
}
if (nextProtocols == null) {
nextProtocols = Collections.emptyList();
}
for (String c: ciphers) {
if (c == null) {
break;
}
this.ciphers.add(c);
}
List nextProtoList = new ArrayList();
for (String p: nextProtocols) {
if (p == null) {
break;
}
nextProtoList.add(p);
}
this.nextProtocols = Collections.unmodifiableList(nextProtoList);
// Allocate a new APR pool.
aprPool = Pool.create(0);
// Create a new SSL_CTX and configure it.
boolean 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 {
// Convert the cipher list into a colon-separated string.
StringBuilder cipherBuf = new StringBuilder();
for (String c: this.ciphers) {
cipherBuf.append(c);
cipherBuf.append(':');
}
cipherBuf.setLength(cipherBuf.length() - 1);
SSLContext.setCipherSuite(ctx, cipherBuf.toString());
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set cipher suite: " + this.ciphers, e);
}
/* Set certificate verification policy. */
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, 10);
/* Load the certificate file and private key. */
try {
if (!SSLContext.setCertificate(
ctx, certChainFile.getPath(), keyFile.getPath(), keyPassword, SSL.SSL_AIDX_RSA)) {
throw new SSLException("failed to set certificate: " +
certChainFile + " and " + keyFile + " (" + SSL.getLastError() + ')');
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e);
}
/* Load the certificate chain. We must skip the first cert since it was loaded above. */
if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
String error = SSL.getLastError();
if (!error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
throw new SSLException(
"failed to set certificate chain: " + certChainFile + " (" + SSL.getLastError() + ')');
}
}
/* Set next protocols for next protocol negotiation extension, if specified */
if (!nextProtoList.isEmpty()) {
// Convert the protocol list into a comma-separated string.
StringBuilder nextProtocolBuf = new StringBuilder();
for (String p: nextProtoList) {
nextProtocolBuf.append(p);
nextProtocolBuf.append(',');
}
nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
SSLContext.setNextProtos(ctx, nextProtocolBuf.toString());
}
/* Set session cache size, if specified */
if (sessionCacheSize > 0) {
this.sessionCacheSize = sessionCacheSize;
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
} else {
// Get the default session cache size using SSLContext.setSessionCacheSize()
this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
// Revert the session cache size to the default value.
SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
}
/* Set session timeout, if specified */
if (sessionTimeout > 0) {
this.sessionTimeout = sessionTimeout;
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
} else {
// Get the default session timeout using SSLContext.setSessionCacheTimeout()
this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
// Revert the session timeout to the default value.
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
}
}
success = true;
} finally {
if (!success) {
destroyPools();
}
}
stats = new OpenSslSessionStats(ctx);
}
@Override
SslBufferPool newBufferPool() {
return new SslBufferPool(true, true);
}
@Override
public boolean isClient() {
return false;
}
@Override
public List cipherSuites() {
return unmodifiableCiphers;
}
@Override
public long sessionCacheSize() {
return sessionCacheSize;
}
@Override
public long sessionTimeout() {
return sessionTimeout;
}
@Override
public List nextProtocols() {
return nextProtocols;
}
/**
* Returns the {@code SSL_CTX} object of this context.
*/
public long context() {
return ctx;
}
/**
* Returns the stats of this context.
*/
public OpenSslSessionStats stats() {
return stats;
}
/**
* Returns a new server-side {@link SSLEngine} with the current configuration.
*/
@Override
public SSLEngine newEngine() {
if (nextProtocols.isEmpty()) {
return new OpenSslEngine(ctx, bufferPool(), null);
} else {
return new OpenSslEngine(
ctx, bufferPool(), nextProtocols.get(nextProtocols.size() - 1));
}
}
@Override
public SSLEngine newEngine(String peerHost, int peerPort) {
throw new UnsupportedOperationException();
}
/**
* Sets the SSL session ticket keys of this context.
*/
public void setTicketKeys(byte[] keys) {
if (keys != null) {
throw new NullPointerException("keys");
}
SSLContext.setSessionTicketKeys(ctx, keys);
}
@Override
@SuppressWarnings("FinalizeDeclaration")
protected void finalize() throws Throwable {
super.finalize();
synchronized (OpenSslServerContext.class) {
if (ctx != 0) {
SSLContext.free(ctx);
}
}
destroyPools();
}
private void destroyPools() {
if (aprPool != 0) {
Pool.destroy(aprPool);
}
}
}