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: [email protected]
For additional commands, e-mail: [email protected]