Skip to content

Commit

Permalink
BZ 66670: Add SSLHostConfig#certificateKeyPasswordFile and SSLHostCon…
Browse files Browse the repository at this point in the history
…fig#certificateKeystorePasswordFile
  • Loading branch information
michael-o committed Oct 23, 2023
1 parent 1cefadc commit b1d20cc
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 23 deletions.
4 changes: 2 additions & 2 deletions java/org/apache/tomcat/util/net/SSLHostConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
27 changes: 26 additions & 1 deletion java/org/apache/tomcat/util/net/SSLHostConfigCertificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
47 changes: 40 additions & 7 deletions java/org/apache/tomcat/util/net/SSLUtilBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -181,10 +184,10 @@ static <T> List<T> 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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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")) {
Expand All @@ -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<Certificate> chain = new ArrayList<>(certificateFile.getCertificates());
Expand All @@ -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)) {
Expand Down
31 changes: 28 additions & 3 deletions java/org/apache/tomcat/util/net/jsse/PEMFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Part> parts = new ArrayList<>();
try (BufferedReader reader =
Expand Down Expand Up @@ -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:
Expand All @@ -149,15 +174,15 @@ 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) {
// If no encryption algorithm was detected, ignore any
// (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:
Expand Down
17 changes: 16 additions & 1 deletion java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
22 changes: 21 additions & 1 deletion test/org/apache/tomcat/util/net/TestSsl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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("<a href=\"../helloworld.html\">") > 0);
Assert.assertTrue("Checking no client issuer has been requested",
TesterSupport.getLastClientAuthRequestedIssuerCount() == 0);
}

@Test
public void testRenegotiateWorks() throws Exception {
Expand Down
12 changes: 10 additions & 2 deletions test/org/apache/tomcat/util/net/TesterSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
Loading

0 comments on commit b1d20cc

Please sign in to comment.