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: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to