ASF Bugzilla – Attachment 33789 Details for
Bug 59344
PEM file support for JSSE
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
PEM support implementation
pem-support.patch (text/plain), 21.24 KB, created by
Emmanuel Bourg
on 2016-04-21 14:31:41 UTC
(
hide
)
Description:
PEM support implementation
Filename:
MIME Type:
Creator:
Emmanuel Bourg
Created:
2016-04-21 14:31:41 UTC
Size:
21.24 KB
patch
obsolete
>Index: java/org/apache/tomcat/util/net/jsse/JSSEUtil.java >=================================================================== >--- java/org/apache/tomcat/util/net/jsse/JSSEUtil.java (révision 1740329) >+++ java/org/apache/tomcat/util/net/jsse/JSSEUtil.java (copie de travail) >@@ -28,11 +28,13 @@ > 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,30 @@ > > 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); >+ >+ 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)); > } >Index: java/org/apache/tomcat/util/net/jsse/PEMFile.java >=================================================================== >--- java/org/apache/tomcat/util/net/jsse/PEMFile.java (révision 0) >+++ java/org/apache/tomcat/util/net/jsse/PEMFile.java (copie de travail) >@@ -0,0 +1,153 @@ >+/* >+ * 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; >+ >+/** >+ * 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). >+ * >+ * @author Emmanuel Bourg >+ * @version $Revision$, $Date$ >+ */ >+class PEMFile { >+ >+ 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("Unable to parse the private key from " + filename); >+ for (String algorithm : new String[] {"RSA", "DSA", "EC"}) { >+ try { >+ return KeyFactory.getInstance(algorithm).generatePrivate(keySpec); >+ } catch (InvalidKeySpecException e) { >+ exception.addSuppressed(e); >+ } >+ } >+ >+ throw exception; >+ } >+ } >+} >Index: java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java >=================================================================== >--- java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java (révision 1740329) >+++ java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java (copie de travail) >@@ -161,8 +161,6 @@ > // OpenSSL > > public void setCertificateChainFile(String certificateChainFile) { >- sslHostConfig.setProperty( >- "Certificate.certificateChainFile", SSLHostConfig.Type.OPENSSL); > this.certificateChainFile = certificateChainFile; > } > >@@ -173,8 +171,6 @@ > > > public void setCertificateFile(String certificateFile) { >- sslHostConfig.setProperty( >- "Certificate.certificateFile", SSLHostConfig.Type.OPENSSL); > this.certificateFile = certificateFile; > } > >@@ -185,8 +181,6 @@ > > > public void setCertificateKeyFile(String certificateKeyFile) { >- sslHostConfig.setProperty( >- "Certificate.certificateKeyFile", SSLHostConfig.Type.OPENSSL); > this.certificateKeyFile = certificateKeyFile; > } > >Index: test/org/apache/tomcat/util/net/localhost-key-encrypted.pem >=================================================================== >--- test/org/apache/tomcat/util/net/localhost-key-encrypted.pem (révision 0) >+++ test/org/apache/tomcat/util/net/localhost-key-encrypted.pem (copie de travail) >@@ -0,0 +1,29 @@ >+-----BEGIN ENCRYPTED PRIVATE KEY----- >+MIIE6TAbBgkqhkiG9w0BBQMwDgQIstlpai4ZAGMCAggABIIEyN1NxOUCP5JSqIw1 >+/Y/1ixm7i1O5ZF5eZAVNY5vZEGgyTtXiHXMPAFy/5dYc/juZiYgLK2bY5e5K0mcj >+Y9VHsEs4bihn3iVYiUrIrCFwsRiQ9UdKVM37mHPxnJUJOwTutmgEIIgfZNxEUdGA >+J3GtK4z1cUc9ZSO/V2QnVta6xS82pCo3IJ1S96BaZi6l8rIkOQjju6LHoAtbKl9S >+wOk3iPdv9ab96xEqA5b7Qq9li2bavc/NPo5QMSDi+s2ap9rMdyneg6rQ6DxmDdHJ >+M2tLRjx2yfvz/O9+Bb4Pdm2jd7zIFyZPyzsQidXYxHa7J0R+D+WtYGC6jRQvAr9L >+xPfPok1Dnlyr+Z7yxQKZk5TgpXAYKayzjWC1gCFAHJge/5HwGg1QX7BQOXYtT81w >+mWOENR5ZX3uXIIZhV/WMyfT6PCs4Dmjq/jW1ZIOnFM5QQh9UfYG1uFTsFa5kH0xe >+XsP9c/cTaMv7nULZ6WEPiovJKVVVQluW2SiCIf5CUBw4LJK3Hv+oKyf/h2yxZwAa >+XBTV2lSmRkOHfPsFbyeHjfrQtdxtfkHmLVqpScqExOFkK4MPmtmiWcfkjPzUEA4w >+Gpg+prD0gO0kwfs50/nzBID6mF5+J+7C4+06lAWzyi9Hg6KkeRERy+lOfGKv+WWH >+SdyAElHf/h27M3AcLoFa1zWqVs1z6HlT2a8UA0z72qDf/61QYVeUi9dDRR0ctEdg >+2RBg588Ycz79VinfxEh1LVs4kay7zWxeaihVMl3pJBhZhFY35GgnSmElTFRVlo+4 >+sj4mfzTWgKBu8wh0fAwZmUO6KhTWFrIJD/T/f89tWZIFqbzoiWUrSklGH6jZ3k0i >+u3zq06vB+LSSrMYidkt2A7uWSetX1kbE9ngl99+CH5c7+zzKGwj46qJNFiGtx+Ww >+0oMTWEiRM5j8DuKWm109xvfVcHQlyMvkrrKKQKXBbXN7TiztJPOeCm31+N3pcY2G >+4XG/c4f8S39mUX4ICqSq7YsYtXIIRgO2cKjPGY4JAR4273mmZ4BU4qEkAExG4yQY >+SHeGmZbhOvRdoJ/gFDB9izhplM9Kt2aJ29L9jBiShXWDUV1MPobbrq6ThUFiC32l >+xyxJrHOACVSl1+FHjrEPC/8/QiMXIo12J9Cbyeo5QOJNfQ9o+PUu0hmt2Qopo9jV >+yX468YEN6MiTg6sccJoAPWWxxJcGhetw0dwELERlvK+4tTfYbnTtk/2gvhanCpfe >+iHFg/x2k6xQxReY3HXnO6qNAJjrkvQt+oAvG35+/BlkJcC4q/A5QnMUxpCXrfKLJ >+CT0l9P2dJVOvPKbnYwfqEFUT2FLtuSzEVQbjhG+xtPuW2kH38MydOECgGJVvw19j >+BkWp2QTS648bRMedoiM80e/LRbOsj9Py6CjHWw9to9qlSQ9AlLxQpZrzwT0Q76oD >+RPHNpYSaVuJMGSQPy0W+d2uYr5ptgC/7i1k1EvMmIe7FmZSrCvaHGxLT7iBZztws >+am0aJzHVZEAyzAnU4K0a5+T4YfT0gHdkxxjmqvKhssDcmPlTPj2wF4i3BL5Il2q9 >+Nxq9tLNo11pnLOzB4V6LIZhAXo8to05ezrKUnLb9ocfSOT3h4bn0UFVOz7gV3YGy >+C+VWSUQmTGG47DtD5A== >+-----END ENCRYPTED PRIVATE KEY----- >Index: test/org/apache/tomcat/util/net/TesterSupport.java >=================================================================== >--- test/org/apache/tomcat/util/net/TesterSupport.java (révision 1740329) >+++ test/org/apache/tomcat/util/net/TesterSupport.java (copie de travail) >@@ -59,44 +59,57 @@ > > String protocol = tomcat.getConnector().getProtocolHandlerClassName(); > if (protocol.indexOf("Apr") == -1) { >- Connector connector = tomcat.getConnector(); >- String sslImplementation = System.getProperty("tomcat.test.sslImplementation"); >- if (sslImplementation != null && !"${test.sslImplementation}".equals(sslImplementation)) { >- StandardServer server = (StandardServer) tomcat.getServer(); >- AprLifecycleListener listener = new AprLifecycleListener(); >- listener.setSSLRandomSeed("/dev/urandom"); >- server.addLifecycleListener(listener); >- tomcat.getConnector().setAttribute("sslImplementationName", sslImplementation); >- } >- connector.setProperty("sslProtocol", "tls"); >- File keystoreFile = >- new File("test/org/apache/tomcat/util/net/" + keystore); >- connector.setAttribute("keystoreFile", >- keystoreFile.getAbsolutePath()); >- File truststoreFile = new File( >- "test/org/apache/tomcat/util/net/ca.jks"); >- connector.setAttribute("truststoreFile", >- truststoreFile.getAbsolutePath()); >- if (keystorePass != null) { >- connector.setAttribute("keystorePass", keystorePass); >- } >- if (keyPass != null) { >- connector.setAttribute("keyPass", keyPass); >- } >+ initSslWithKeyStore(tomcat, keystore, keystorePass, keyPass); > } else { >- File keystoreFile = new File( >- "test/org/apache/tomcat/util/net/localhost-cert.pem"); >- tomcat.getConnector().setAttribute("SSLCertificateFile", >- keystoreFile.getAbsolutePath()); >- keystoreFile = new File( >- "test/org/apache/tomcat/util/net/localhost-key.pem"); >- tomcat.getConnector().setAttribute("SSLCertificateKeyFile", >- keystoreFile.getAbsolutePath()); >+ initSslWithPEM(tomcat, >+ "test/org/apache/tomcat/util/net/localhost-cert.pem", >+ null, >+ "test/org/apache/tomcat/util/net/localhost-key.pem", >+ null); > } >- tomcat.getConnector().setSecure(true); >- tomcat.getConnector().setProperty("SSLEnabled", "true"); > } > >+ protected static void initSslWithKeyStore(Tomcat tomcat, String keystore, String keystorePass, String keyPass) { >+ Connector connector = tomcat.getConnector(); >+ String sslImplementation = System.getProperty("tomcat.test.sslImplementation"); >+ if (sslImplementation != null && !"${test.sslImplementation}".equals(sslImplementation)) { >+ StandardServer server = (StandardServer) tomcat.getServer(); >+ AprLifecycleListener listener = new AprLifecycleListener(); >+ listener.setSSLRandomSeed("/dev/urandom"); >+ server.addLifecycleListener(listener); >+ tomcat.getConnector().setAttribute("sslImplementationName", sslImplementation); >+ } >+ connector.setProperty("sslProtocol", "tls"); >+ File keystoreFile = new File("test/org/apache/tomcat/util/net/" + keystore); >+ connector.setAttribute("keystoreFile", keystoreFile.getAbsolutePath()); >+ File truststoreFile = new File("test/org/apache/tomcat/util/net/ca.jks"); >+ connector.setAttribute("truststoreFile", truststoreFile.getAbsolutePath()); >+ if (keystorePass != null) { >+ connector.setAttribute("keystorePass", keystorePass); >+ } >+ if (keyPass != null) { >+ connector.setAttribute("keyPass", keyPass); >+ } >+ >+ connector.setSecure(true); >+ connector.setProperty("SSLEnabled", "true"); >+ } >+ >+ protected static void initSslWithPEM(Tomcat tomcat, String certificateFile, >+ String certificateChainFile, String certificateKeyFile, String keyPass) { >+ Connector connector = tomcat.getConnector(); >+ connector.setAttribute("SSLCertificateFile", new File(certificateFile).getAbsolutePath()); >+ if (certificateChainFile != null) { >+ connector.setAttribute("SSLCertificateChainFile", new File(certificateChainFile).getAbsolutePath()); >+ } >+ connector.setAttribute("SSLCertificateKeyFile", new File(certificateKeyFile).getAbsolutePath()); >+ if (keyPass != null) { >+ connector.setAttribute("keyPass", keyPass); >+ } >+ connector.setSecure(true); >+ connector.setProperty("SSLEnabled", "true"); >+ } >+ > protected static KeyManager[] getUser1KeyManagers() throws Exception { > KeyManagerFactory kmf = KeyManagerFactory.getInstance( > KeyManagerFactory.getDefaultAlgorithm()); >Index: test/org/apache/tomcat/util/net/TestSsl.java >=================================================================== >--- test/org/apache/tomcat/util/net/TestSsl.java (révision 1740329) >+++ test/org/apache/tomcat/util/net/TestSsl.java (copie de travail) >@@ -70,6 +70,52 @@ > } > > @Test >+ public void testSimpleSslWithPEM() throws Exception { >+ TesterSupport.configureClientSsl(); >+ >+ Tomcat tomcat = getTomcatInstance(); >+ >+ File appDir = new File(getBuildDirectory(), "webapps/examples"); >+ org.apache.catalina.Context ctxt = tomcat.addWebapp( >+ null, "/examples", appDir.getAbsolutePath()); >+ ctxt.addApplicationListener(WsContextListener.class.getName()); >+ >+ TesterSupport.initSslWithPEM(tomcat, >+ "test/org/apache/tomcat/util/net/localhost-cert.pem", >+ null, >+ "test/org/apache/tomcat/util/net/localhost-key.pem", >+ null); >+ >+ tomcat.start(); >+ ByteChunk res = getUrl("https://localhost:" + getPort() + >+ "/examples/servlets/servlet/HelloWorldExample"); >+ assertTrue(res.toString().indexOf("<a href=\"../helloworld.html\">") > 0); >+ } >+ >+ @Test >+ public void testSimpleSslWithEncryptedPEM() throws Exception { >+ TesterSupport.configureClientSsl(); >+ >+ Tomcat tomcat = getTomcatInstance(); >+ >+ File appDir = new File(getBuildDirectory(), "webapps/examples"); >+ org.apache.catalina.Context ctxt = tomcat.addWebapp( >+ null, "/examples", appDir.getAbsolutePath()); >+ ctxt.addApplicationListener(WsContextListener.class.getName()); >+ >+ TesterSupport.initSslWithPEM(tomcat, >+ "test/org/apache/tomcat/util/net/localhost-cert.pem", >+ null, >+ "test/org/apache/tomcat/util/net/localhost-key-encrypted.pem", >+ "tomcat"); >+ >+ tomcat.start(); >+ ByteChunk res = getUrl("https://localhost:" + getPort() + >+ "/examples/servlets/servlet/HelloWorldExample"); >+ assertTrue(res.toString().indexOf("<a href=\"../helloworld.html\">") > 0); >+ } >+ >+ @Test > public void testKeyPass() throws Exception { > TesterSupport.configureClientSsl(); > >Index: webapps/docs/config/http.xml >=================================================================== >--- webapps/docs/config/http.xml (révision 1740329) >+++ webapps/docs/config/http.xml (copie de travail) >@@ -1255,7 +1255,6 @@ > <attributes> > > <attribute name="certificateFile" required="true"> >- <p>OpenSSL only.</p> > <p>Name of the file that contains the server certificate. The format is > PEM-encoded. Relative paths will be resolved against > <code>$CATALINA_BASE</code>.</p> >@@ -1263,11 +1262,11 @@ > elements DH parameters and/or an EC curve name for ephemeral keys, as > generated by <code>openssl dhparam</code> and <code>openssl ecparam</code>, > respectively. The output of the respective OpenSSL command can simply >- be concatenated to the certificate file.</p> >+ be concatenated to the certificate file. This is supported when using >+ OpenSSL only.</p> > </attribute> > > <attribute name="certificateChainFile" required="false"> >- <p>OpenSSL only.</p> > <p>Name of the file that contains the certificate chain associated with > the server certificate used. The format is > PEM-encoded. Relative paths will be resolved against >@@ -1290,12 +1289,13 @@ > </attribute> > > <attribute name="certificateKeyFile" required="false"> >- <p>OpenSSL only.</p> > <p>Name of the file that contains the server private key. The format is > PEM-encoded. The default value is the value of > <strong>certificateFile</strong> and in this case both certificate and > private key have to be in this file (NOT RECOMMENDED). Relative paths will >- be resolved against <code>$CATALINA_BASE</code>.</p> >+ be resolved against <code>$CATALINA_BASE</code>. For JSSE only PKCS#8 keys >+ are supported (i.e. those starting with "BEGIN PRIVATE KEY" or >+ "BEGIN ENCRYPTED PRIVATE KEY", not "BEGIN RSA PRIVATE KEY").</p> > </attribute> > > <attribute name="certificateKeyPassword" required="false">
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 59344
:
33788
|
33789
|
33792