diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index 003c9b8b276b..a447c7fec03e 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -659,7 +659,7 @@ public KeyStore getTruststore() throws IOException {
if (truststoreFile != null){
try {
result = SSLUtilBase.getStore(getTruststoreType(), getTruststoreProvider(),
- getTruststoreFile(), getTruststorePassword());
+ getTruststoreFile(), getTruststorePassword(), null);
} catch (IOException ioe) {
Throwable cause = ioe.getCause();
if (cause instanceof UnrecoverableKeyException) {
@@ -668,7 +668,7 @@ public KeyStore getTruststore() throws IOException {
cause);
// Re-try
result = SSLUtilBase.getStore(getTruststoreType(), getTruststoreProvider(),
- getTruststoreFile(), null);
+ getTruststoreFile(), null, null);
} else {
// Something else went wrong - re-throw
throw ioe;
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
index ff635bf5880b..0c0ebb7274c7 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
@@ -59,10 +59,12 @@ public class SSLHostConfigCertificate implements Serializable {
private final SSLHostConfig sslHostConfig;
private final Type type;
private String certificateKeyPassword = null;
+ private String certificateKeyPasswordFile = null;
// JSSE
private String certificateKeyAlias;
private String certificateKeystorePassword = DEFAULT_KEYSTORE_PASSWORD;
+ private String certificateKeystorePasswordFile = null;
private String certificateKeystoreFile = DEFAULT_KEYSTORE_FILE;
private String certificateKeystoreProvider = DEFAULT_KEYSTORE_PROVIDER;
private String certificateKeystoreType = DEFAULT_KEYSTORE_TYPE;
@@ -131,6 +133,16 @@ public void setCertificateKeyPassword(String certificateKeyPassword) {
}
+ public String getCertificateKeyPasswordFile() {
+ return certificateKeyPasswordFile;
+ }
+
+
+ public void setCertificateKeyPasswordFile(String certificateKeyPasswordFile) {
+ this.certificateKeyPasswordFile = certificateKeyPasswordFile;
+ }
+
+
// JSSE
public void setCertificateKeyAlias(String certificateKeyAlias) {
@@ -171,6 +183,19 @@ public String getCertificateKeystorePassword() {
}
+ public void setCertificateKeystorePasswordFile(String certificateKeystorePasswordFile) {
+ sslHostConfig.setProperty(
+ "Certificate.certificateKeystorePasswordFile", SSLHostConfig.Type.JSSE);
+ setStoreType("Certificate.certificateKeystorePasswordFile", StoreType.KEYSTORE);
+ this.certificateKeystorePasswordFile = certificateKeystorePasswordFile;
+ }
+
+
+ public String getCertificateKeystorePasswordFile() {
+ return certificateKeystorePasswordFile;
+ }
+
+
public void setCertificateKeystoreProvider(String certificateKeystoreProvider) {
sslHostConfig.setProperty(
"Certificate.certificateKeystoreProvider", SSLHostConfig.Type.JSSE);
@@ -208,7 +233,7 @@ public KeyStore getCertificateKeystore() throws IOException {
if (result == null && storeType == StoreType.KEYSTORE) {
result = SSLUtilBase.getStore(getCertificateKeystoreType(),
getCertificateKeystoreProvider(), getCertificateKeystoreFile(),
- getCertificateKeystorePassword());
+ getCertificateKeystorePassword(), getCertificateKeystorePasswordFile());
}
return result;
diff --git a/java/org/apache/tomcat/util/net/SSLUtilBase.java b/java/org/apache/tomcat/util/net/SSLUtilBase.java
index 91c2929baa54..70735293ebe6 100644
--- a/java/org/apache/tomcat/util/net/SSLUtilBase.java
+++ b/java/org/apache/tomcat/util/net/SSLUtilBase.java
@@ -16,9 +16,12 @@
*/
package org.apache.tomcat.util.net;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.security.DomainLoadStoreParameter;
import java.security.Key;
import java.security.KeyStore;
@@ -181,10 +184,10 @@ static List getEnabled(String name, Log log, boolean warnOnSkip, Collecti
/*
- * Gets the key- or truststore with the specified type, path, and password.
+ * Gets the key- or truststore with the specified type, path, password and password file.
*/
static KeyStore getStore(String type, String provider, String path,
- String pass) throws IOException {
+ String pass, String passFile) throws IOException {
KeyStore ks = null;
InputStream istream = null;
@@ -217,9 +220,21 @@ static KeyStore getStore(String type, String provider, String path,
// - for JKS or PKCS12 only use null if pass is null
// (because JKS will auto-switch to PKCS12)
char[] storePass = null;
- if (pass != null && (!"".equals(pass) ||
+ String passToUse = null;
+ if (passFile != null) {
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(
+ ConfigFileLoader.getSource().getResource(passFile).getInputStream(),
+ StandardCharsets.UTF_8))) {
+ passToUse = reader.readLine();
+ }
+ } else {
+ passToUse = pass;
+ }
+
+ if (passToUse != null && (!"".equals(passToUse) ||
"JKS".equalsIgnoreCase(type) || "PKCS12".equalsIgnoreCase(type))) {
- storePass = pass.toCharArray();
+ storePass = passToUse.toCharArray();
}
ks.load(istream, storePass);
}
@@ -278,9 +293,13 @@ public void configureSessionContext(SSLSessionContext sslSessionContext) {
public KeyManager[] getKeyManagers() throws Exception {
String keyAlias = certificate.getCertificateKeyAlias();
String algorithm = sslHostConfig.getKeyManagerAlgorithm();
+ String keyPassFile = certificate.getCertificateKeyPasswordFile();
String keyPass = certificate.getCertificateKeyPassword();
// This has to be here as it can't be moved to SSLHostConfig since the
// defaults vary between JSSE and OpenSSL.
+ if (keyPassFile == null) {
+ keyPassFile = certificate.getCertificateKeystorePasswordFile();
+ }
if (keyPass == null) {
keyPass = certificate.getCertificateKeystorePassword();
}
@@ -299,8 +318,22 @@ public KeyManager[] getKeyManagers() throws Exception {
* required key works around that.
* Other keys stores (hardware, MS, etc.) will be used as is.
*/
+ char[] keyPassArray = null;
+ String keyPassToUse = null;
+ if (keyPassFile != null) {
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(
+ ConfigFileLoader.getSource().getResource(keyPassFile).getInputStream(),
+ StandardCharsets.UTF_8))) {
+ keyPassToUse = reader.readLine();
+ }
+ } else {
+ keyPassToUse = keyPass;
+ }
- char[] keyPassArray = keyPass.toCharArray();
+ if (keyPassToUse != null) {
+ keyPassArray = keyPassToUse.toCharArray();
+ }
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
if (kmf.getProvider().getInfo().contains("FIPS")) {
@@ -319,7 +352,7 @@ public KeyManager[] getKeyManagers() throws Exception {
PEMFile privateKeyFile = new PEMFile(
certificate.getCertificateKeyFile() != null ? certificate.getCertificateKeyFile() : certificate.getCertificateFile(),
- keyPass);
+ keyPass, keyPassFile, null);
PEMFile certificateFile = new PEMFile(certificate.getCertificateFile());
Collection chain = new ArrayList<>(certificateFile.getCertificates());
@@ -335,7 +368,7 @@ public KeyManager[] getKeyManagers() throws Exception {
// Switch to in-memory key store
ksUsed = KeyStore.getInstance("JKS");
ksUsed.load(null, null);
- ksUsed.setKeyEntry(keyAlias, privateKeyFile.getPrivateKey(), keyPass.toCharArray(),
+ ksUsed.setKeyEntry(keyAlias, privateKeyFile.getPrivateKey(), keyPassArray,
chain.toArray(new Certificate[0]));
} else {
if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
diff --git a/java/org/apache/tomcat/util/net/jsse/PEMFile.java b/java/org/apache/tomcat/util/net/jsse/PEMFile.java
index 397282239882..7bed5bddbd3f 100644
--- a/java/org/apache/tomcat/util/net/jsse/PEMFile.java
+++ b/java/org/apache/tomcat/util/net/jsse/PEMFile.java
@@ -101,15 +101,30 @@ public PEMFile(String filename, String password, String keyAlgorithm)
this(filename, ConfigFileLoader.getSource().getResource(filename).getInputStream(), password, keyAlgorithm);
}
+ public PEMFile(String filename, String password, String passwordFilename, String keyAlgorithm)
+ throws IOException, GeneralSecurityException {
+ this(filename, ConfigFileLoader.getSource().getResource(filename).getInputStream(), password,
+ passwordFilename, passwordFilename != null ? ConfigFileLoader.getSource().getResource(passwordFilename).getInputStream() : null,
+ keyAlgorithm);
+ }
+
+ public PEMFile(String filename, InputStream fileStream, String password, String keyAlgorithm)
+ throws IOException, GeneralSecurityException {
+ this(filename, fileStream, password, null, null, keyAlgorithm);
+ }
+
/**
* @param filename the filename to mention in error messages, not used for anything else.
* @param fileStream the stream containing the pem(s).
* @param password password to load the pem objects.
+ * @param passwordFilename the password filename to mention in error messages, not used for anything else.
+ * @param passwordFileStream stream containing the password to load the pem objects.
* @param keyAlgorithm the algorithm to help to know how to load the objects (guessed if null).
* @throws IOException if input can't be read.
* @throws GeneralSecurityException if input can't be parsed/loaded.
*/
- public PEMFile(String filename, InputStream fileStream, String password, String keyAlgorithm)
+ public PEMFile(String filename, InputStream fileStream, String password, String passwordFilename,
+ InputStream passwordFileStream, String keyAlgorithm)
throws IOException, GeneralSecurityException {
List parts = new ArrayList<>();
try (BufferedReader reader =
@@ -140,6 +155,16 @@ public PEMFile(String filename, InputStream fileStream, String password, String
}
}
+ String passwordToUse = null;
+ if (passwordFileStream != null) {
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(passwordFileStream, StandardCharsets.UTF_8))) {
+ passwordToUse = reader.readLine();
+ }
+ } else {
+ passwordToUse = password;
+ }
+
for (Part part : parts) {
switch (part.type) {
case Part.PRIVATE_KEY:
@@ -149,7 +174,7 @@ public PEMFile(String filename, InputStream fileStream, String password, String
privateKey = part.toPrivateKey(null, "EC", Format.RFC5915, filename);
break;
case Part.ENCRYPTED_PRIVATE_KEY:
- privateKey = part.toPrivateKey(password, keyAlgorithm, Format.PKCS8, filename);
+ privateKey = part.toPrivateKey(passwordToUse, keyAlgorithm, Format.PKCS8, filename);
break;
case Part.RSA_PRIVATE_KEY:
if (part.algorithm == null) {
@@ -157,7 +182,7 @@ public PEMFile(String filename, InputStream fileStream, String password, String
// (probably default) key password provided.
privateKey = part.toPrivateKey(null, keyAlgorithm, Format.PKCS1, filename);
} else {
- privateKey = part.toPrivateKey(password, keyAlgorithm, Format.PKCS1, filename);
+ privateKey = part.toPrivateKey(passwordToUse, keyAlgorithm, Format.PKCS1, filename);
}
break;
case Part.CERTIFICATE:
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
index 6ceaf89bb55d..a687e14571ff 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -16,6 +16,9 @@
*/
package org.apache.tomcat.util.net.openssl;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
import java.lang.ref.Cleaner;
import java.lang.ref.Cleaner.Cleanable;
import java.nio.charset.StandardCharsets;
@@ -466,10 +469,22 @@ public void addCertificate(SSLHostConfigCertificate certificate) throws Exceptio
// Load Server key and certificate
if (certificate.getCertificateFile() != null) {
// Set certificate
+ String passwordToUse = null;
+ if (certificate.getCertificateKeyPasswordFile() != null) {
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(
+ new FileInputStream(
+ SSLHostConfig.adjustRelativePath(certificate.getCertificateKeyPasswordFile())),
+ StandardCharsets.UTF_8))) {
+ passwordToUse = reader.readLine();
+ }
+ } else {
+ passwordToUse = certificate.getCertificateKeyPassword();
+ }
SSLContext.setCertificate(state.ctx,
SSLHostConfig.adjustRelativePath(certificate.getCertificateFile()),
SSLHostConfig.adjustRelativePath(certificate.getCertificateKeyFile()),
- certificate.getCertificateKeyPassword(), getCertificateIndex(certificate));
+ passwordToUse, getCertificateIndex(certificate));
// Set certificate chain file
SSLContext.setCertificateChainFile(state.ctx,
SSLHostConfig.adjustRelativePath(certificate.getCertificateChainFile()), false);
diff --git a/test/org/apache/tomcat/util/net/TestSsl.java b/test/org/apache/tomcat/util/net/TestSsl.java
index f3f889ed0e39..debabab9b7e4 100644
--- a/test/org/apache/tomcat/util/net/TestSsl.java
+++ b/test/org/apache/tomcat/util/net/TestSsl.java
@@ -224,7 +224,7 @@ public void testKeyPass() throws Exception {
ctxt.addApplicationListener(WsContextListener.class.getName());
TesterSupport.initSsl(tomcat, TesterSupport.LOCALHOST_KEYPASS_JKS,
- TesterSupport.JKS_PASS, TesterSupport.JKS_KEY_PASS);
+ TesterSupport.JKS_PASS, null, TesterSupport.JKS_KEY_PASS, null);
TesterSupport.configureSSLImplementation(tomcat, sslImplementationName);
@@ -243,6 +243,26 @@ public void testKeyPass() throws Exception {
TesterSupport.getLastClientAuthRequestedIssuerCount() == 0);
}
+ @Test
+ public void testKeyPassFile() throws Exception {
+ TesterSupport.configureClientSsl();
+
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File(getBuildDirectory(), "webapps/examples");
+ Context ctxt = tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
+ ctxt.addApplicationListener(WsContextListener.class.getName());
+
+ TesterSupport.initSsl(tomcat, TesterSupport.LOCALHOST_KEYPASS_JKS,
+ null, TesterSupport.JKS_PASS_FILE, null, TesterSupport.JKS_KEY_PASS_FILE);
+
+ tomcat.start();
+ ByteChunk res = getUrl("https://localhost:" + getPort() +
+ "/examples/servlets/servlet/HelloWorldExample");
+ Assert.assertTrue(res.toString().indexOf("") > 0);
+ Assert.assertTrue("Checking no client issuer has been requested",
+ TesterSupport.getLastClientAuthRequestedIssuerCount() == 0);
+ }
@Test
public void testRenegotiateWorks() throws Exception {
diff --git a/test/org/apache/tomcat/util/net/TesterSupport.java b/test/org/apache/tomcat/util/net/TesterSupport.java
index 199be62beb83..03eb1d9bb995 100644
--- a/test/org/apache/tomcat/util/net/TesterSupport.java
+++ b/test/org/apache/tomcat/util/net/TesterSupport.java
@@ -78,7 +78,9 @@ public final class TesterSupport {
public static final String LOCALHOST_RSA_JKS = SSL_DIR + "localhost-rsa.jks";
public static final String LOCALHOST_KEYPASS_JKS = SSL_DIR + "localhost-rsa-copy1.jks";
public static final String JKS_PASS = "changeit";
+ public static final String JKS_PASS_FILE = SSL_DIR + "keystore-password";
public static final String JKS_KEY_PASS = "tomcatpass";
+ public static final String JKS_KEY_PASS_FILE = SSL_DIR + "key-password";
public static final String CA_CERT_PEM = SSL_DIR + CA_ALIAS + "-cert.pem";
public static final String LOCALHOST_EC_CERT_PEM = SSL_DIR + "localhost-ec-cert.pem";
public static final String LOCALHOST_EC_KEY_PEM = SSL_DIR + "localhost-ec-key.pem";
@@ -133,11 +135,11 @@ public static boolean isTlsv13Available() {
}
public static void initSsl(Tomcat tomcat) {
- initSsl(tomcat, LOCALHOST_RSA_JKS, null, null);
+ initSsl(tomcat, LOCALHOST_RSA_JKS, null, null, null, null);
}
protected static void initSsl(Tomcat tomcat, String keystore,
- String keystorePass, String keyPass) {
+ String keystorePass, String keystorePassFile, String keyPass, String keyPassFile) {
Connector connector = tomcat.getConnector();
connector.setSecure(true);
@@ -159,9 +161,15 @@ protected static void initSsl(Tomcat tomcat, String keystore,
sslHostConfig.setSslProtocol("tls");
certificate.setCertificateKeystoreFile(new File(keystore).getAbsolutePath());
sslHostConfig.setTruststoreFile(new File(CA_JKS).getAbsolutePath());
+ if (keystorePassFile != null) {
+ certificate.setCertificateKeystorePasswordFile(new File(keystorePassFile).getAbsolutePath());
+ }
if (keystorePass != null) {
certificate.setCertificateKeystorePassword(keystorePass);
}
+ if (keyPassFile != null) {
+ certificate.setCertificateKeyPasswordFile(new File(keyPassFile).getAbsolutePath());
+ }
if (keyPass != null) {
certificate.setCertificateKeyPassword(keyPass);
}
diff --git a/test/org/apache/tomcat/util/net/jsse/TestPEMFile.java b/test/org/apache/tomcat/util/net/jsse/TestPEMFile.java
index 272295c3abeb..dc10b2b9dc86 100644
--- a/test/org/apache/tomcat/util/net/jsse/TestPEMFile.java
+++ b/test/org/apache/tomcat/util/net/jsse/TestPEMFile.java
@@ -19,13 +19,21 @@
import java.io.File;
import java.io.IOException;
import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import org.junit.Assert;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+@RunWith(Parameterized.class)
public class TestPEMFile {
private static final String KEY_PASSWORD = "changeit";
+ private static final String KEY_PASSWORD_FILE = "key-password";
private static final String KEY_PKCS1 = "key-pkcs1.pem";
private static final String KEY_ENCRYPTED_PKCS1_DES_CBC = "key-encrypted-pkcs1-des-cbc.pem";
@@ -33,16 +41,34 @@ public class TestPEMFile {
private static final String KEY_ENCRYPTED_PKCS1_AES256 = "key-encrypted-pkcs1-aes256.pem";
private static final String KEY_ENCRYPTED_PKCS8 = "key-encrypted-pkcs8.pem";
+ @Parameterized.Parameters
+ public static Collection
If not specified, the default behaviour for JSSE is to use the
certificateKeystorePassword. For OpenSSL the default
- behaviour is not to use a password.
+ behaviour is not to use a password, but OpenSSL will prompt for one,
+ if required.
+
+
+
+
The password file used to access the private key associated with the server
+ certificate from the specified file. This attribute takes precedence over
+ certificateKeyPassword.
+
If not specified, the default behaviour for JSSE is to use the
+ certificateKeystorePasswordFile. For OpenSSL the default
+ behaviour is not to use a password (file), but OpenSSL will prompt for one,
+ if required.
@@ -1496,6 +1507,13 @@
changeit will be used.
+
+
JSSE only.
+
The password file to use to access the keystore containing the server's
+ private key and certificate. This attribute takes precedence over
+ certificateKeystorePassword.
+
+
JSSE only.
The name of the keystore provider to be used for the server