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