Author: remm
Date: Tue Apr 26 09:02:06 2016
New Revision: 1740969

URL: http://svn.apache.org/viewvc?rev=1740969&view=rev
Log:
59295: Add support for using pem encoded certificates with JSSE SSL. Submitted 
by Emmanuel Bourg with additional tweaks.
This will need another rather painful SSL docs update, once the change is 
validated.

Added:
    tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java   (with 
props)
Modified:
    tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties
    tomcat/trunk/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
    tomcat/trunk/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
    tomcat/trunk/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties?rev=1740969&r1=1740968&r2=1740969&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/LocalStrings.properties Tue 
Apr 26 09:02:06 2016
@@ -121,6 +121,7 @@ sslHostConfig.certificateVerificationInv
 sslHostConfig.certificate.notype=Multiple certificates were specified and at 
least one is missing the required attribute type
 sslHostConfig.mismatch=The property [{0}] was set on the SSLHostConfig named 
[{1}] and is for connectors of type [{2}] but the SSLHostConfig is being used 
with a connector of type [{3}]
 sslHostConfig.prefix_missing=The protocol [{0}] was added to the list of 
protocols on the SSLHostConfig named [{1}]. Check if a +/- prefix is missing.
+sslHostConfigCertificate.mismatch=The property [{0}] was set on the 
SSLHostConfigCertificate named [{1}] and is for certificate storage type [{2}] 
but the certificate is being used with a storage of type [{3}]
 
 sslImplementation.cnfe= Unable to create SSLImplementation for class [{0}]
 

Modified: 
tomcat/trunk/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java?rev=1740969&r1=1740968&r2=1740969&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java 
Tue Apr 26 09:02:06 2016
@@ -19,11 +19,17 @@ package org.apache.tomcat.util.net;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.openssl.ciphers.Authentication;
+import org.apache.tomcat.util.res.StringManager;
 
 
 public class SSLHostConfigCertificate {
 
+    private static final Log log = 
LogFactory.getLog(SSLHostConfigCertificate.class);
+    private static final StringManager sm = 
StringManager.getManager(SSLHostConfigCertificate.class);
+
     public static final Type DEFAULT_TYPE = Type.UNDEFINED;
 
     static final String DEFAULT_KEYSTORE_PROVIDER =
@@ -53,6 +59,8 @@ public class SSLHostConfigCertificate {
     private String certificateFile;
     private String certificateKeyFile;
 
+    // Certificate store type
+    private StoreType storeType = null;
 
     public SSLHostConfigCertificate() {
         this(null, Type.UNDEFINED);
@@ -113,6 +121,7 @@ public class SSLHostConfigCertificate {
     public void setCertificateKeystoreFile(String certificateKeystoreFile) {
         sslHostConfig.setProperty(
                 "Certificate.certificateKeystoreFile", 
SSLHostConfig.Type.JSSE);
+        setStoreType("Certificate.certificateKeystoreFile", 
StoreType.KEYSTORE);
         this.certificateKeystoreFile = certificateKeystoreFile;
     }
 
@@ -125,6 +134,7 @@ public class SSLHostConfigCertificate {
     public void setCertificateKeystorePassword(String 
certificateKeystorePassword) {
         sslHostConfig.setProperty(
                 "Certificate.certificateKeystorePassword", 
SSLHostConfig.Type.JSSE);
+        setStoreType("Certificate.certificateKeystorePassword", 
StoreType.KEYSTORE);
         this.certificateKeystorePassword = certificateKeystorePassword;
     }
 
@@ -137,6 +147,7 @@ public class SSLHostConfigCertificate {
     public void setCertificateKeystoreProvider(String 
certificateKeystoreProvider) {
         sslHostConfig.setProperty(
                 "Certificate.certificateKeystoreProvider", 
SSLHostConfig.Type.JSSE);
+        setStoreType("Certificate.certificateKeystoreProvider", 
StoreType.KEYSTORE);
         this.certificateKeystoreProvider = certificateKeystoreProvider;
     }
 
@@ -149,6 +160,7 @@ public class SSLHostConfigCertificate {
     public void setCertificateKeystoreType(String certificateKeystoreType) {
         sslHostConfig.setProperty(
                 "Certificate.certificateKeystoreType", 
SSLHostConfig.Type.JSSE);
+        setStoreType("Certificate.certificateKeystoreType", 
StoreType.KEYSTORE);
         this.certificateKeystoreType = certificateKeystoreType;
     }
 
@@ -161,8 +173,7 @@ public class SSLHostConfigCertificate {
     // OpenSSL
 
     public void setCertificateChainFile(String certificateChainFile) {
-        sslHostConfig.setProperty(
-                "Certificate.certificateChainFile", 
SSLHostConfig.Type.OPENSSL);
+        setStoreType("Certificate.certificateChainFile", StoreType.PEM);
         this.certificateChainFile = certificateChainFile;
     }
 
@@ -173,8 +184,7 @@ public class SSLHostConfigCertificate {
 
 
     public void setCertificateFile(String certificateFile) {
-        sslHostConfig.setProperty(
-                "Certificate.certificateFile", SSLHostConfig.Type.OPENSSL);
+        setStoreType("Certificate.certificateFile", StoreType.PEM);
         this.certificateFile = certificateFile;
     }
 
@@ -185,8 +195,7 @@ public class SSLHostConfigCertificate {
 
 
     public void setCertificateKeyFile(String certificateKeyFile) {
-        sslHostConfig.setProperty(
-                "Certificate.certificateKeyFile", SSLHostConfig.Type.OPENSSL);
+        setStoreType("Certificate.certificateKeyFile", StoreType.PEM);
         this.certificateKeyFile = certificateKeyFile;
     }
 
@@ -196,6 +205,15 @@ public class SSLHostConfigCertificate {
     }
 
 
+    private void setStoreType(String name, StoreType type) {
+        if (storeType == null) {
+            storeType = type;
+        } else if (storeType != type) {
+            log.warn(sm.getString("sslHostConfigCertificate.mismatch",
+                    name, sslHostConfig.getHostName(), type, this.storeType));
+        }
+    }
+
     // Nested types
 
     public static enum Type {
@@ -220,4 +238,7 @@ public class SSLHostConfigCertificate {
             return compatibleAuthentications.contains(au);
         }
     }
+
+    private static enum StoreType { KEYSTORE, PEM };
+
 }

Modified: tomcat/trunk/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java?rev=1740969&r1=1740968&r2=1740969&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/jsse/JSSEUtil.java Tue Apr 26 
09:02:06 2016
@@ -28,11 +28,13 @@ import java.security.cert.CRLException;
 import java.security.cert.CertPathParameters;
 import java.security.cert.CertStore;
 import java.security.cert.CertStoreParameters;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.PKIXBuilderParameters;
 import java.security.cert.X509CertSelector;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -275,7 +277,32 @@ public class JSSEUtil extends SSLUtilBas
 
         KeyManager[] kms = null;
 
-        KeyStore ks = getStore(keystoreType, keystoreProvider, keystoreFile, 
keystorePass);
+        KeyStore ks;
+
+        if (certificate.getCertificateFile() == null) {
+            ks = getStore(keystoreType, keystoreProvider, keystoreFile, 
keystorePass);
+        } else {
+            // create an in-memory keystore and import the private key
+            // and the certificate chain from the PEM files
+            ks = KeyStore.getInstance("JKS");
+            ks.load(null, null);
+
+            if (certificate.getCertificateKeyFile() == null) {
+                throw new 
IllegalStateException(sm.getString("jsse.noPrivateKey"));
+            }
+            PEMFile privateKeyFile = new 
PEMFile(SSLHostConfig.adjustRelativePath(certificate.getCertificateKeyFile()), 
keyPass);
+            PEMFile certificateFile = new 
PEMFile(SSLHostConfig.adjustRelativePath(certificate.getCertificateFile()));
+
+            Collection<Certificate> chain = new ArrayList<>();
+            chain.addAll(certificateFile.getCertificates());
+            if (certificate.getCertificateChainFile() != null) {
+                PEMFile certificateChainFile = new 
PEMFile(SSLHostConfig.adjustRelativePath(certificate.getCertificateChainFile()));
+                chain.addAll(certificateChainFile.getCertificates());
+            }
+
+            ks.setKeyEntry(keyAlias, privateKeyFile.getPrivateKey(), 
keyPass.toCharArray(), chain.toArray(new Certificate[chain.size()]));
+        }
+
         if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
             throw new IOException(sm.getString("jsse.alias_no_key_entry", 
keyAlias));
         }

Modified: 
tomcat/trunk/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties?rev=1740969&r1=1740968&r2=1740969&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties 
(original)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties 
Tue Apr 26 09:02:06 2016
@@ -25,6 +25,9 @@ jsse.excludeDefaultProtocol=The SSL prot
 jsse.noDefaultCiphers=Unable to determine a default for ciphers for [{0}]. Set 
an explicit value to ensure the connector can start.
 jsse.noDefaultProtocols=Unable to determine a default for sslEnabledProtocols. 
Set an explicit value to ensure the connector can start.
 jsse.exceptionOnClose=Failure to close socket.
+jsse.noPrivateKey=No private key specified for certificate.
+jsse.pemParseError=Unable to parse the private key from [{0}]
+
 jsseSupport.clientCertError=Error trying to obtain a certificate from the 
client
 jseeSupport.certTranslationError=Error translating certificate [{0}]
 jsseSupport.noCertWant=No client certificate sent for want

Added: tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java?rev=1740969&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java Tue Apr 26 
09:02:06 2016
@@ -0,0 +1,150 @@
+/*
+ *  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.jsse;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * RFC 1421 PEM file containing X509 certificates or private keys (PKCS#8 only,
+ * i.e. with boundaries containing "BEGIN PRIVATE KEY" or "BEGIN ENCRYPTED 
PRIVATE KEY",
+ * not "BEGIN RSA PRIVATE KEY" or other variations).
+ */
+class PEMFile {
+
+    private static final StringManager sm = 
StringManager.getManager(PEMFile.class);
+
+    private String filename;
+    private List<X509Certificate> certificates = new ArrayList<>();
+    private PrivateKey privateKey;
+
+    public List<X509Certificate> getCertificates() {
+        return certificates;
+    }
+
+    public PrivateKey getPrivateKey() {
+        return privateKey;
+    }
+
+    public PEMFile(String filename) throws IOException, 
GeneralSecurityException {
+        this(filename, null);
+    }
+
+    public PEMFile(String filename, String password) throws IOException, 
GeneralSecurityException {
+        this.filename = filename;
+
+        List<Part> parts = new ArrayList<>();
+        try (BufferedReader in = new BufferedReader(new FileReader(filename))) 
{
+            Part part = null;
+            String line;
+            while ((line = in.readLine()) != null) {
+                if (line.startsWith(Part.BEGIN_BOUNDARY)) {
+                    part = new Part();
+                    part.type = line.substring(Part.BEGIN_BOUNDARY.length(), 
line.length() - 5).trim();
+                } else if (line.startsWith(Part.END_BOUNDARY)) {
+                    parts.add(part);
+                    part = null;
+                } else if (part != null && !line.contains(":") && 
!line.startsWith(" ")) {
+                    part.content += line;
+                }
+            }
+        }
+
+        for (Part part : parts) {
+            switch (part.type) {
+                case "PRIVATE KEY":
+                    privateKey = part.toPrivateKey(null);
+                    break;
+                case "ENCRYPTED PRIVATE KEY":
+                    privateKey = part.toPrivateKey(password);
+                    break;
+                case "CERTIFICATE":
+                case "X509 CERTIFICATE":
+                    certificates.add(part.toCertificate());
+                    break;
+            }
+        }
+    }
+
+    private class Part {
+        public static final String BEGIN_BOUNDARY = "-----BEGIN ";
+        public static final String END_BOUNDARY   = "-----END ";
+
+        public String type;
+        public String content = "";
+
+        private byte[] decode() {
+            return Base64.decodeBase64(content);
+        }
+
+        public X509Certificate toCertificate() throws CertificateException {
+            CertificateFactory factory = 
CertificateFactory.getInstance("X.509");
+            return (X509Certificate) factory.generateCertificate(new 
ByteArrayInputStream(decode()));
+        }
+
+        public PrivateKey toPrivateKey(String password) throws 
GeneralSecurityException, IOException {
+            KeySpec keySpec;
+
+            if (password == null) {
+                keySpec = new PKCS8EncodedKeySpec(decode());
+            } else {
+                EncryptedPrivateKeyInfo privateKeyInfo = new 
EncryptedPrivateKeyInfo(decode());
+                SecretKeyFactory secretKeyFactory = 
SecretKeyFactory.getInstance(privateKeyInfo.getAlgName());
+                SecretKey secretKey = secretKeyFactory.generateSecret(new 
PBEKeySpec(password.toCharArray()));
+
+                Cipher cipher = 
Cipher.getInstance(privateKeyInfo.getAlgName());
+                cipher.init(Cipher.DECRYPT_MODE, secretKey, 
privateKeyInfo.getAlgParameters());
+
+                keySpec = privateKeyInfo.getKeySpec(cipher);
+            }
+
+            InvalidKeyException exception = new 
InvalidKeyException(sm.getString("jsse.pemParseError", filename));
+            for (String algorithm : new String[] {"RSA", "DSA", "EC"}) {
+                try {
+                    return 
KeyFactory.getInstance(algorithm).generatePrivate(keySpec);
+                } catch (InvalidKeySpecException e) {
+                    exception.addSuppressed(e);
+                }
+            }
+
+            throw exception;
+        }
+    }
+}

Propchange: tomcat/trunk/java/org/apache/tomcat/util/net/jsse/PEMFile.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1740969&r1=1740968&r2=1740969&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Tue Apr 26 09:02:06 2016
@@ -200,6 +200,10 @@
         the <code>Content-Langauge</code> HTTP header to ensure the locale is
         correctly represented. Patch provided by zikfat. (markt)
       </fix>
+      <update>
+        <bug>59295</bug>: Add support for using pem encoded certificates with
+        JSSE SSL. Submitted by Emmanuel Bourg with additional tweaks. (remm)
+      </update>
     </changelog>
   </subsection>
   <subsection name="WebSocket">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to