Author: remm
Date: Thu Jun 18 17:13:40 2015
New Revision: 1686279
URL: http://svn.apache.org/r1686279
Log:
Add SSL engine backed by OpenSSL, based on code from Nume de Montmollin and
derived from work done by Netty and Twitter.
Added:
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/CipherSuiteConverter.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLKeyManager.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLProtocols.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLServerSessionContext.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionContext.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionStats.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLX509Certificate.java
Added:
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/CipherSuiteConverter.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/CipherSuiteConverter.java?rev=1686279&view=auto
==============================================================================
---
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/CipherSuiteConverter.java
(added)
+++
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/CipherSuiteConverter.java
Thu Jun 18 17:13:40 2015
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.net.openssl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Converts a Java cipher suite string to an OpenSSL cipher suite string and
vice versa.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Cipher_suite">Wikipedia page
about cipher suite</a>
+ */
+public final class CipherSuiteConverter {
+
+ private static final Log logger =
LogFactory.getLog(CipherSuiteConverter.class);
+ private static final StringManager sm =
StringManager.getManager(CipherSuiteConverter.class);
+
+ /**
+ * A_B_WITH_C_D, where:
+ *
+ * A - TLS or SSL (protocol)
+ * B - handshake algorithm (key exchange and authentication algorithms to
be precise)
+ * C - bulk cipher
+ * D - HMAC algorithm
+ *
+ * This regular expression assumees that:
+ *
+ * 1) A is always TLS or SSL, and
+ * 2) D is always a single word.
+ */
+ private static final Pattern JAVA_CIPHERSUITE_PATTERN =
+ Pattern.compile("^(?:TLS|SSL)_((?:(?!_WITH_).)+)_WITH_(.*)_(.*)$");
+
+ /**
+ * A-B-C, where:
+ *
+ * A - handshake algorithm (key exchange and authentication algorithms to
be precise)
+ * B - bulk cipher
+ * C - HMAC algorithm
+ *
+ * This regular expression assumes that:
+ *
+ * 1) A has some deterministic pattern as shown below, and
+ * 2) C is always a single word
+ */
+ private static final Pattern OPENSSL_CIPHERSUITE_PATTERN =
+ // Be very careful not to break the indentation while editing.
+ Pattern.compile(
+ "^(?:(" + // BEGIN handshake algorithm
+ "(?:(?:EXP-)?" +
+ "(?:" +
+
"(?:DHE|EDH|ECDH|ECDHE|SRP)-(?:DSS|RSA|ECDSA)|" +
+ "(?:ADH|AECDH|KRB5|PSK|SRP)" +
+ ')' +
+ ")|" +
+ "EXP" +
+ ")-)?" + // END handshake algorithm
+ "(.*)-(.*)$");
+
+ private static final Pattern JAVA_AES_CBC_PATTERN =
Pattern.compile("^(AES)_([0-9]+)_CBC$");
+ private static final Pattern JAVA_AES_PATTERN =
Pattern.compile("^(AES)_([0-9]+)_(.*)$");
+ private static final Pattern OPENSSL_AES_CBC_PATTERN =
Pattern.compile("^(AES)([0-9]+)$");
+ private static final Pattern OPENSSL_AES_PATTERN =
Pattern.compile("^(AES)([0-9]+)-(.*)$");
+
+ /**
+ * Java-to-OpenSSL cipher suite conversion map
+ * Note that the Java cipher suite has the protocol prefix (TLS_, SSL_)
+ */
+ private static final ConcurrentMap<String, String> j2o = new
ConcurrentHashMap<>();
+
+ /**
+ * OpenSSL-to-Java cipher suite conversion map.
+ * Note that one OpenSSL cipher suite can be converted to more than one
Java cipher suites because
+ * a Java cipher suite has the protocol name prefix (TLS_, SSL_)
+ */
+ private static final ConcurrentMap<String, Map<String, String>> o2j = new
ConcurrentHashMap<>();
+
+ /**
+ * Clears the cache for testing purpose.
+ */
+ static void clearCache() {
+ j2o.clear();
+ o2j.clear();
+ }
+
+ /**
+ * Tests if the specified key-value pair has been cached in
Java-to-OpenSSL cache.
+ */
+ static boolean isJ2OCached(String key, String value) {
+ return value.equals(j2o.get(key));
+ }
+
+ /**
+ * Tests if the specified key-value pair has been cached in
OpenSSL-to-Java cache.
+ */
+ static boolean isO2JCached(String key, String protocol, String value) {
+ Map<String, String> p2j = o2j.get(key);
+ if (p2j == null) {
+ return false;
+ } else {
+ return value.equals(p2j.get(protocol));
+ }
+ }
+
+ /**
+ * Converts the specified Java cipher suites to the colon-separated
OpenSSL cipher suite specification.
+ */
+ public static String toOpenSsl(Iterable<String> javaCipherSuites) {
+ final StringBuilder buf = new StringBuilder();
+ for (String c: javaCipherSuites) {
+ if (c == null) {
+ break;
+ }
+
+ String converted = toOpenSsl(c);
+ if (converted != null) {
+ c = converted;
+ }
+
+ buf.append(c);
+ buf.append(':');
+ }
+
+ if (buf.length() > 0) {
+ buf.setLength(buf.length() - 1);
+ return buf.toString();
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Converts the specified Java cipher suite to its corresponding OpenSSL
cipher suite name.
+ *
+ * @return {@code null} if the conversion has failed
+ */
+ public static String toOpenSsl(String javaCipherSuite) {
+ String converted = j2o.get(javaCipherSuite);
+ if (converted != null) {
+ return converted;
+ } else {
+ return cacheFromJava(javaCipherSuite);
+ }
+ }
+
+ private static String cacheFromJava(String javaCipherSuite) {
+ String openSslCipherSuite = toOpenSslUncached(javaCipherSuite);
+ if (openSslCipherSuite == null) {
+ return null;
+ }
+
+ // Cache the mapping.
+ j2o.putIfAbsent(javaCipherSuite, openSslCipherSuite);
+
+ // Cache the reverse mapping after stripping the protocol prefix (TLS_
or SSL_)
+ final String javaCipherSuiteSuffix = javaCipherSuite.substring(4);
+ Map<String, String> p2j = new HashMap<String, String>(4);
+ p2j.put("", javaCipherSuiteSuffix);
+ p2j.put("SSL", "SSL_" + javaCipherSuiteSuffix);
+ p2j.put("TLS", "TLS_" + javaCipherSuiteSuffix);
+ o2j.put(openSslCipherSuite, p2j);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(sm.getString("converter.mapping", javaCipherSuite,
openSslCipherSuite));
+ }
+
+ return openSslCipherSuite;
+ }
+
+ static String toOpenSslUncached(String javaCipherSuite) {
+ Matcher m = JAVA_CIPHERSUITE_PATTERN.matcher(javaCipherSuite);
+ if (!m.matches()) {
+ return null;
+ }
+
+ String handshakeAlgo = toOpenSslHandshakeAlgo(m.group(1));
+ String bulkCipher = toOpenSslBulkCipher(m.group(2));
+ String hmacAlgo = toOpenSslHmacAlgo(m.group(3));
+ if (handshakeAlgo.length() == 0) {
+ return bulkCipher + '-' + hmacAlgo;
+ } else {
+ return handshakeAlgo + '-' + bulkCipher + '-' + hmacAlgo;
+ }
+ }
+
+ private static String toOpenSslHandshakeAlgo(String handshakeAlgo) {
+ final boolean export = handshakeAlgo.endsWith("_EXPORT");
+ if (export) {
+ handshakeAlgo = handshakeAlgo.substring(0, handshakeAlgo.length()
- 7);
+ }
+
+ if ("RSA".equals(handshakeAlgo)) {
+ handshakeAlgo = "";
+ } else if (handshakeAlgo.endsWith("_anon")) {
+ handshakeAlgo = 'A' + handshakeAlgo.substring(0,
handshakeAlgo.length() - 5);
+ }
+
+ if (export) {
+ if (handshakeAlgo.length() == 0) {
+ handshakeAlgo = "EXP";
+ } else {
+ handshakeAlgo = "EXP-" + handshakeAlgo;
+ }
+ }
+
+ return handshakeAlgo.replace('_', '-');
+ }
+
+ private static String toOpenSslBulkCipher(String bulkCipher) {
+ if (bulkCipher.startsWith("AES_")) {
+ Matcher m = JAVA_AES_CBC_PATTERN.matcher(bulkCipher);
+ if (m.matches()) {
+ return m.replaceFirst("$1$2");
+ }
+
+ m = JAVA_AES_PATTERN.matcher(bulkCipher);
+ if (m.matches()) {
+ return m.replaceFirst("$1$2-$3");
+ }
+ }
+
+ if ("3DES_EDE_CBC".equals(bulkCipher)) {
+ return "DES-CBC3";
+ }
+
+ if ("RC4_128".equals(bulkCipher) || "RC4_40".equals(bulkCipher)) {
+ return "RC4";
+ }
+
+ if ("DES40_CBC".equals(bulkCipher) || "DES_CBC_40".equals(bulkCipher))
{
+ return "DES-CBC";
+ }
+
+ if ("RC2_CBC_40".equals(bulkCipher)) {
+ return "RC2-CBC";
+ }
+
+ return bulkCipher.replace('_', '-');
+ }
+
+ private static String toOpenSslHmacAlgo(String hmacAlgo) {
+ // Java and OpenSSL use the same algorithm names for:
+ //
+ // * SHA
+ // * SHA256
+ // * MD5
+ //
+ return hmacAlgo;
+ }
+
+ /**
+ * Convert from OpenSSL cipher suite name convention to java cipher suite
name convention.
+ * @param openSslCipherSuite An OpenSSL cipher suite name.
+ * @param protocol The cryptographic protocol (i.e. SSL, TLS, ...).
+ * @return The translated cipher suite name according to java conventions.
This will not be {@code null}.
+ */
+ public static String toJava(String openSslCipherSuite, String protocol) {
+ Map<String, String> p2j = o2j.get(openSslCipherSuite);
+ if (p2j == null) {
+ p2j = cacheFromOpenSsl(openSslCipherSuite);
+ }
+
+ String javaCipherSuite = p2j.get(protocol);
+ if (javaCipherSuite == null) {
+ javaCipherSuite = protocol + '_' + p2j.get("");
+ }
+
+ return javaCipherSuite;
+ }
+
+ private static Map<String, String> cacheFromOpenSsl(String
openSslCipherSuite) {
+ String javaCipherSuiteSuffix = toJavaUncached(openSslCipherSuite);
+ if (javaCipherSuiteSuffix == null) {
+ return null;
+ }
+
+ final String javaCipherSuiteSsl = "SSL_" + javaCipherSuiteSuffix;
+ final String javaCipherSuiteTls = "TLS_" + javaCipherSuiteSuffix;
+
+ // Cache the mapping.
+ final Map<String, String> p2j = new HashMap<String, String>(4);
+ p2j.put("", javaCipherSuiteSuffix);
+ p2j.put("SSL", javaCipherSuiteSsl);
+ p2j.put("TLS", javaCipherSuiteTls);
+ o2j.putIfAbsent(openSslCipherSuite, p2j);
+
+ // Cache the reverse mapping after adding the protocol prefix (TLS_ or
SSL_)
+ j2o.putIfAbsent(javaCipherSuiteTls, openSslCipherSuite);
+ j2o.putIfAbsent(javaCipherSuiteSsl, openSslCipherSuite);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(sm.getString("converter.mapping", javaCipherSuiteTls,
openSslCipherSuite));
+ logger.debug(sm.getString("converter.mapping", javaCipherSuiteSsl,
openSslCipherSuite));
+ }
+
+ return p2j;
+ }
+
+ static String toJavaUncached(String openSslCipherSuite) {
+ Matcher m = OPENSSL_CIPHERSUITE_PATTERN.matcher(openSslCipherSuite);
+ if (!m.matches()) {
+ return null;
+ }
+
+ String handshakeAlgo = m.group(1);
+ final boolean export;
+ if (handshakeAlgo == null) {
+ handshakeAlgo = "";
+ export = false;
+ } else if (handshakeAlgo.startsWith("EXP-")) {
+ handshakeAlgo = handshakeAlgo.substring(4);
+ export = true;
+ } else if ("EXP".equals(handshakeAlgo)) {
+ handshakeAlgo = "";
+ export = true;
+ } else {
+ export = false;
+ }
+
+ handshakeAlgo = toJavaHandshakeAlgo(handshakeAlgo, export);
+ String bulkCipher = toJavaBulkCipher(m.group(2), export);
+ String hmacAlgo = toJavaHmacAlgo(m.group(3));
+
+ return handshakeAlgo + "_WITH_" + bulkCipher + '_' + hmacAlgo;
+ }
+
+ private static String toJavaHandshakeAlgo(String handshakeAlgo, boolean
export) {
+ if (handshakeAlgo.length() == 0) {
+ handshakeAlgo = "RSA";
+ } else if ("ADH".equals(handshakeAlgo)) {
+ handshakeAlgo = "DH_anon";
+ } else if ("AECDH".equals(handshakeAlgo)) {
+ handshakeAlgo = "ECDH_anon";
+ }
+
+ handshakeAlgo = handshakeAlgo.replace('-', '_');
+ if (export) {
+ return handshakeAlgo + "_EXPORT";
+ } else {
+ return handshakeAlgo;
+ }
+ }
+
+ private static String toJavaBulkCipher(String bulkCipher, boolean export) {
+ if (bulkCipher.startsWith("AES")) {
+ Matcher m = OPENSSL_AES_CBC_PATTERN.matcher(bulkCipher);
+ if (m.matches()) {
+ return m.replaceFirst("$1_$2_CBC");
+ }
+
+ m = OPENSSL_AES_PATTERN.matcher(bulkCipher);
+ if (m.matches()) {
+ return m.replaceFirst("$1_$2_$3");
+ }
+ }
+
+ if ("DES-CBC3".equals(bulkCipher)) {
+ return "3DES_EDE_CBC";
+ }
+
+ if ("RC4".equals(bulkCipher)) {
+ if (export) {
+ return "RC4_40";
+ } else {
+ return "RC4_128";
+ }
+ }
+
+ if ("DES-CBC".equals(bulkCipher)) {
+ if (export) {
+ return "DES_CBC_40";
+ } else {
+ return "DES_CBC";
+ }
+ }
+
+ if ("RC2-CBC".equals(bulkCipher)) {
+ if (export) {
+ return "RC2_CBC_40";
+ } else {
+ return "RC2_CBC";
+ }
+ }
+
+ return bulkCipher.replace('-', '_');
+ }
+
+ private static String toJavaHmacAlgo(String hmacAlgo) {
+ // Java and OpenSSL use the same algorithm names for:
+ //
+ // * SHA
+ // * SHA256
+ // * MD5
+ //
+ return hmacAlgo;
+ }
+
+ private CipherSuiteConverter() { }
+}
Added:
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties?rev=1686279&view=auto
==============================================================================
---
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
(added)
+++
tomcat/trunk/java/org/apache/tomcat/util/net/openssl/LocalStrings.properties
Thu Jun 18 17:13:40 2015
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+
+openssl.X509FactoryError=Error getting X509 factory instance
+openssl.errorSSLCtxInit=Error initializing SSL context
+openssl.doubleInit=SSL context already initialized, ignoring
+openssl.certificateVerificationFailed=Certificate verification failed
+openssl.keyManagerMissing=No key manager found
+openssl.trustManagerMissing=No trust manager found
+
+engine.engineClosed=Engine is closed
+engine.renegociationUnsupported=Renegociation is not supported
+engine.oversizedPacket=Encrypted packet is oversized
+engine.ciphersFailure=Failed getting cipher list
+engine.noSSLContext=No SSL context
+engine.writeToSSLFailed=Failed writing to SSL, returned: {0}
+engine.invalidBufferArray=offset: {0}, length: {1} (expected: offset <= offset
+ length <= srcs.length ({2}))
+engine.nullBufferInArray=Null buffer in array
+engine.nullBuffer=Null buffer
+engine.readFromSSLFailed=Read from SSL failed, {0} SSL_read failed:
primingReadResult: {1} OpenSSL error: {2}
+engine.inboundClose=Inbound closed before receiving peer's close_notify
+engine.nullCipherSuite=Null cipher suite
+engine.unsupportedCipher=Unsupported cipher suite: {0} ({1})
+engine.emptyCipherSuite=Empty cipher suite
+engine.failedCipherSuite=Failed to enable cipher suite {0}
+engine.unsupportedProtocol=Protocol {0} is not supported
+engine.unverifiedPeer=Peer unverified
+engine.noSession=SSL session ID not available
+engine.nullName=Null value name
+engine.nullValue=Null value
+engine.handshakeFailure=Failed handshake: {0}
+
+converter.mapping=Cipher suite mapping: {} => {} {0} {1}
+
+keyManager.nullCertificateChain=Null certificate chain
+keyManager.nullPrivateKey=Null private key
+
+sessionContext.nullTicketKeys=Null keys
Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
(added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
Thu Jun 18 17:13:40 2015
@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.net.openssl;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.CertificateVerifier;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.Constants;
+import org.apache.tomcat.util.net.SSLHostConfig;
+import
org.apache.tomcat.util.net.jsse.openssl.OpenSSLCipherConfigurationParser;
+import org.apache.tomcat.util.res.StringManager;
+
+public class OpenSSLContext implements org.apache.tomcat.util.net.SSLContext {
+
+ private static final Log log = LogFactory.getLog(OpenSSLContext.class);
+
+ // Note: this uses the main "net" package strings as many are common with
APR
+ private static final StringManager netSm =
StringManager.getManager(AbstractEndpoint.class);
+ private static final StringManager sm =
StringManager.getManager(OpenSSLContext.class);
+
+ private static final String defaultProtocol = "TLS";
+
+ private final SSLHostConfig sslHostConfig;
+ private OpenSSLServerSessionContext sessionContext;
+
+ private List<String> ciphers = new ArrayList<>();
+
+ public List<String> getCiphers() {
+ return ciphers;
+ }
+
+ private String enabledProtocol;
+
+ public String getEnabledProtocol() {
+ return enabledProtocol;
+ }
+
+ public void setEnabledProtocol(String protocol) {
+ enabledProtocol = (protocol == null) ? defaultProtocol : protocol;
+ }
+
+ private final long aprPool;
+ protected final long ctx;
+
+ @SuppressWarnings("unused")
+ private volatile int aprPoolDestroyed;
+ private static final AtomicIntegerFieldUpdater<OpenSSLContext>
DESTROY_UPDATER
+ = AtomicIntegerFieldUpdater.newUpdater(OpenSSLContext.class,
"aprPoolDestroyed");
+ static final CertificateFactory X509_CERT_FACTORY;
+ private boolean initialized = false;
+
+ static {
+ try {
+ X509_CERT_FACTORY = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new
IllegalStateException(sm.getString("openssl.X509FactoryError"), e);
+ }
+ }
+
+ public OpenSSLContext(SSLHostConfig sslHostConfig) throws SSLException {
+ this.sslHostConfig = sslHostConfig;
+ aprPool = Pool.create(0);
+ boolean success = false;
+ try {
+ if
(SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateFile()) == null) {
+ // This is required
+ throw new
Exception(netSm.getString("endpoint.apr.noSslCertFile"));
+ }
+
+ // SSL protocol
+ int value = SSL.SSL_PROTOCOL_NONE;
+ if (sslHostConfig.getProtocols().size() == 0) {
+ value = SSL.SSL_PROTOCOL_ALL;
+ } else {
+ for (String protocol : sslHostConfig.getProtocols()) {
+ if
(Constants.SSL_PROTO_SSLv2Hello.equalsIgnoreCase(protocol)) {
+ // NO-OP. OpenSSL always supports SSLv2Hello
+ } else if
(Constants.SSL_PROTO_SSLv2.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_SSLV2;
+ } else if
(Constants.SSL_PROTO_SSLv3.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_SSLV3;
+ } else if
(Constants.SSL_PROTO_TLSv1.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_TLSV1;
+ } else if
(Constants.SSL_PROTO_TLSv1_1.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_TLSV1_1;
+ } else if
(Constants.SSL_PROTO_TLSv1_2.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_TLSV1_2;
+ } else if
(Constants.SSL_PROTO_ALL.equalsIgnoreCase(protocol)) {
+ value |= SSL.SSL_PROTOCOL_ALL;
+ } else {
+ // Protocol not recognized, fail to start as it is
safer than
+ // continuing with the default which might enable more
than the
+ // is required
+ throw new Exception(netSm.getString(
+ "endpoint.apr.invalidSslProtocol", protocol));
+ }
+ }
+ }
+
+ // Create SSL Context
+ try {
+ ctx = SSLContext.make(aprPool, value, SSL.SSL_MODE_SERVER);
+ } catch (Exception e) {
+ // If the sslEngine is disabled on the AprLifecycleListener
+ // there will be an Exception here but there is no way to check
+ // the AprLifecycleListener settings from here
+ throw new Exception(
+ netSm.getString("endpoint.apr.failSslContextMake"), e);
+ }
+ success = true;
+ } catch(Exception e) {
+ throw new SSLException(sm.getString("openssl.errorSSLCtxInit"), e);
+ } finally {
+ if (!success) {
+ destroyPools();
+ }
+ }
+ }
+
+ private void destroyPools() {
+ // Guard against multiple destroyPools() calls triggered by
construction exception and finalize() later
+ if (aprPool != 0 && DESTROY_UPDATER.compareAndSet(this, 0, 1)) {
+ Pool.destroy(aprPool);
+ }
+ }
+
+ /**
+ * Setup the SSL_CTX
+ *
+ * @param kms Must contain a KeyManager of the type
+ * {@code OpenSSLKeyManager}
+ * @param tms
+ * @param sr Is not used for this implementation.
+ * @throws SSLException
+ */
+ @Override
+ public synchronized void init(KeyManager[] kms, TrustManager[] tms,
SecureRandom sr) {
+ if (initialized) {
+ log.warn(sm.getString("openssl.doubleInit"));
+ return;
+ }
+ try {
+ boolean legacyRenegSupported = false;
+ try {
+ legacyRenegSupported =
SSL.hasOp(SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+ if (legacyRenegSupported)
+ if (sslHostConfig.getInsecureRenegotiation()) {
+ SSLContext.setOptions(ctx,
SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+ } else {
+ SSLContext.clearOptions(ctx,
SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+ }
+ } catch (UnsatisfiedLinkError e) {
+ // Ignore
+ }
+ if (!legacyRenegSupported) {
+ // OpenSSL does not support unsafe legacy renegotiation.
+ log.warn(netSm.getString("endpoint.warn.noInsecureReneg",
+ SSL.versionString()));
+ }
+ // Use server's preference order for ciphers (rather than
+ // client's)
+ boolean orderCiphersSupported = false;
+ try {
+ orderCiphersSupported =
SSL.hasOp(SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ if (orderCiphersSupported) {
+ if (sslHostConfig.getHonorCipherOrder()) {
+ SSLContext.setOptions(ctx,
SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ } else {
+ SSLContext.clearOptions(ctx,
SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+ }
+ } catch (UnsatisfiedLinkError e) {
+ // Ignore
+ }
+ if (!orderCiphersSupported) {
+ // OpenSSL does not support ciphers ordering.
+ log.warn(netSm.getString("endpoint.warn.noHonorCipherOrder",
+ SSL.versionString()));
+ }
+
+ // Disable compression if requested
+ boolean disableCompressionSupported = false;
+ try {
+ disableCompressionSupported =
SSL.hasOp(SSL.SSL_OP_NO_COMPRESSION);
+ if (disableCompressionSupported) {
+ if (sslHostConfig.getDisableCompression()) {
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_COMPRESSION);
+ } else {
+ SSLContext.clearOptions(ctx,
SSL.SSL_OP_NO_COMPRESSION);
+ }
+ }
+ } catch (UnsatisfiedLinkError e) {
+ // Ignore
+ }
+ if (!disableCompressionSupported) {
+ // OpenSSL does not support ciphers ordering.
+ log.warn(netSm.getString("endpoint.warn.noDisableCompression",
+ SSL.versionString()));
+ }
+
+ // Disable TLS Session Tickets (RFC4507) to protect perfect
forward secrecy
+ boolean disableSessionTicketsSupported = false;
+ try {
+ disableSessionTicketsSupported =
SSL.hasOp(SSL.SSL_OP_NO_TICKET);
+ if (disableSessionTicketsSupported) {
+ if (sslHostConfig.getDisableSessionTickets()) {
+ SSLContext.setOptions(ctx, SSL.SSL_OP_NO_TICKET);
+ } else {
+ SSLContext.clearOptions(ctx, SSL.SSL_OP_NO_TICKET);
+ }
+ }
+ } catch (UnsatisfiedLinkError e) {
+ // Ignore
+ }
+ if (!disableSessionTicketsSupported) {
+ // OpenSSL is too old to support TLS Session Tickets.
+
log.warn(netSm.getString("endpoint.warn.noDisableSessionTickets",
+ SSL.versionString()));
+ }
+
+ // Set session cache size, if specified
+ if (sslHostConfig.getSessionCacheSize() > 0) {
+ SSLContext.setSessionCacheSize(ctx,
sslHostConfig.getSessionCacheSize());
+ } else {
+ // Get the default session cache size using
SSLContext.setSessionCacheSize()
+ long sessionCacheSize = SSLContext.setSessionCacheSize(ctx,
20480);
+ // Revert the session cache size to the default value.
+ SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
+ }
+
+ // Set session timeout, if specified
+ if (sslHostConfig.getSessionTimeout() > 0) {
+ SSLContext.setSessionCacheTimeout(ctx,
sslHostConfig.getSessionTimeout());
+ } else {
+ // Get the default session timeout using
SSLContext.setSessionCacheTimeout()
+ long sessionTimeout = SSLContext.setSessionCacheTimeout(ctx,
300);
+ // Revert the session timeout to the default value.
+ SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
+ }
+
+ // List the ciphers that the client is permitted to negotiate
+ String ciphers = sslHostConfig.getCiphers();
+ if (!("ALL".equals(ciphers)) && ciphers.indexOf(":") == -1) {
+ StringTokenizer tok = new StringTokenizer(ciphers, ",");
+ this.ciphers = new ArrayList<>();
+ while (tok.hasMoreTokens()) {
+ String token = tok.nextToken().trim();
+ if (!"".equals(token)) {
+ this.ciphers.add(token);
+ }
+ }
+ ciphers = CipherSuiteConverter.toOpenSsl(ciphers);
+ } else {
+ this.ciphers =
OpenSSLCipherConfigurationParser.parseExpression(ciphers);
+ }
+ SSLContext.setCipherSuite(ctx, ciphers);
+ // Load Server key and certificate
+ SSLContext.setCertificate(ctx,
+
SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateFile()),
+
SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateKeyFile()),
+ sslHostConfig.getCertificateKeyPassword(),
SSL.SSL_AIDX_RSA);
+ // Set certificate chain file
+ SSLContext.setCertificateChainFile(ctx,
+
SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateChainFile()),
+ false);
+ // Support Client Certificates
+ SSLContext.setCACertificate(ctx,
+
SSLHostConfig.adjustRelativePath(sslHostConfig.getCaCertificateFile()),
+
SSLHostConfig.adjustRelativePath(sslHostConfig.getCaCertificatePath()));
+ // Set revocation
+ SSLContext.setCARevocation(ctx,
+ SSLHostConfig.adjustRelativePath(
+ sslHostConfig.getCertificateRevocationListFile()),
+ SSLHostConfig.adjustRelativePath(
+ sslHostConfig.getCertificateRevocationListPath()));
+ // Client certificate verification
+ int value = 0;
+ switch (sslHostConfig.getCertificateVerification()) {
+ case NONE:
+ value = SSL.SSL_CVERIFY_NONE;
+ break;
+ case OPTIONAL:
+ value = SSL.SSL_CVERIFY_OPTIONAL;
+ break;
+ case OPTIONAL_NO_CA:
+ value = SSL.SSL_CVERIFY_OPTIONAL_NO_CA;
+ break;
+ case REQUIRED:
+ value = SSL.SSL_CVERIFY_REQUIRE;
+ break;
+ }
+ SSLContext.setVerify(ctx, value,
sslHostConfig.getCertificateVerificationDepth());
+
+ if (tms != null) {
+ final X509TrustManager manager = chooseTrustManager(tms);
+ SSLContext.setCertVerifyCallback(ctx, new
CertificateVerifier() {
+ @Override
+ public boolean verify(long ssl, byte[][] chain, String
auth) {
+ X509Certificate[] peerCerts = certificates(chain);
+ try {
+ manager.checkClientTrusted(peerCerts, auth);
+ return true;
+ } catch (Exception e) {
+
log.debug(sm.getString("openssl.certificateVerificationFailed"), e);
+ }
+ return false;
+ }
+ });
+ }
+ String[] protos = new
OpenSSLProtocols(enabledProtocol).getProtocols();
+ SSLContext.setNpnProtos(ctx, protos,
SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL);
+
+ sessionContext = new OpenSSLServerSessionContext(ctx);
+ initialized = true;
+ } catch (Exception e) {
+ log.warn(sm.getString("openssl.errorSSLCtxInit"), e);
+ destroyPools();
+ }
+ }
+
+ static OpenSSLKeyManager chooseKeyManager(KeyManager[] managers) throws
Exception {
+ for (KeyManager manager : managers) {
+ if (manager instanceof OpenSSLKeyManager) {
+ return (OpenSSLKeyManager) manager;
+ }
+ }
+ throw new
IllegalStateException(sm.getString("openssl.keyManagerMissing"));
+ }
+
+ static X509TrustManager chooseTrustManager(TrustManager[] managers) {
+ for (TrustManager m : managers) {
+ if (m instanceof X509TrustManager) {
+ return (X509TrustManager) m;
+ }
+ }
+ throw new
IllegalStateException(sm.getString("openssl.trustManagerMissing"));
+ }
+
+ private static X509Certificate[] certificates(byte[][] chain) {
+ X509Certificate[] peerCerts = new X509Certificate[chain.length];
+ for (int i = 0; i < peerCerts.length; i++) {
+ peerCerts[i] = new OpenSslX509Certificate(chain[i]);
+ }
+ return peerCerts;
+ }
+
+ @Override
+ public SSLSessionContext getServerSessionContext() {
+ return sessionContext;
+ }
+
+ @Override
+ public SSLEngine createSSLEngine() {
+ return new OpenSSLEngine(ctx, defaultProtocol, false, sessionContext);
+ }
+
+ @Override
+ public SSLServerSocketFactory getServerSocketFactory() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SSLParameters getSupportedSSLParameters() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Generates a key specification for an (encrypted) private key.
+ *
+ * @param password characters, if {@code null} or empty an unencrypted key
+ * is assumed
+ * @param key bytes of the DER encoded private key
+ *
+ * @return a key specification
+ *
+ * @throws IOException if parsing {@code key} fails
+ * @throws NoSuchAlgorithmException if the algorithm used to encrypt
+ * {@code key} is unknown
+ * @throws NoSuchPaddingException if the padding scheme specified in the
+ * decryption algorithm is unknown
+ * @throws InvalidKeySpecException if the decryption key based on
+ * {@code password} cannot be generated
+ * @throws InvalidKeyException if the decryption key based on
+ * {@code password} cannot be used to decrypt {@code key}
+ * @throws InvalidAlgorithmParameterException if decryption algorithm
+ * parameters are somehow faulty
+ */
+ protected static PKCS8EncodedKeySpec generateKeySpec(char[] password,
byte[] key)
+ throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException,
+ InvalidKeyException, InvalidAlgorithmParameterException {
+
+ if (password == null || password.length == 0) {
+ return new PKCS8EncodedKeySpec(key);
+ }
+
+ EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new
EncryptedPrivateKeyInfo(key);
+ SecretKeyFactory keyFactory =
SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
+ PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
+ SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
+
+ Cipher cipher =
Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
+ cipher.init(Cipher.DECRYPT_MODE, pbeKey,
encryptedPrivateKeyInfo.getAlgParameters());
+
+ return encryptedPrivateKeyInfo.getKeySpec(cipher);
+ }
+
+ @Override
+ @SuppressWarnings("FinalizeDeclaration")
+ protected final void finalize() throws Throwable {
+ super.finalize();
+ synchronized (OpenSSLContext.class) {
+ if (ctx != 0) {
+ SSLContext.free(ctx);
+ }
+ }
+ destroyPools();
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]