From 7abc7df7b84c31604df982688006d43ee800e13a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 9 Jun 2022 21:01:23 +0100 Subject: [PATCH] Allow PEM certificates to be used without a key store password Closes gh-31253 --- .../src/docs/asciidoc/howto/webserver.adoc | 1 - .../embedded/jetty/SslServerCustomizer.java | 6 +++- .../embedded/netty/SslServerCustomizer.java | 10 +++--- .../tomcat/SslConnectorCustomizer.java | 4 +++ .../undertow/SslBuilderCustomizer.java | 10 +++--- .../CertificateFileSslStoreProvider.java | 31 ++++++++++++------- .../boot/web/server/SslStoreProvider.java | 11 ++++++- ...AbstractReactiveWebServerFactoryTests.java | 1 - .../AbstractServletWebServerFactoryTests.java | 1 - 9 files changed, 48 insertions(+), 27 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc index 2b3649b0b9..c6e083404e 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/webserver.adoc @@ -205,7 +205,6 @@ The following example shows setting SSL properties using PEM-encoded certificate certificate: "classpath:my-cert.crt" certificate-private-key: "classpath:my-cert.key" trust-certificate: "classpath:ca-cert.crt" - key-store-password: "secret" ---- See {spring-boot-module-code}/web/server/Ssl.java[`Ssl`] for details of all of the supported properties. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java index d0648c66b5..4c6015d5f3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -183,6 +183,10 @@ class SslServerCustomizer implements JettyServerCustomizer { } if (sslStoreProvider != null) { try { + String keyPassword = sslStoreProvider.getKeyPassword(); + if (keyPassword != null) { + factory.setKeyManagerPassword(keyPassword); + } factory.setKeyStore(sslStoreProvider.getKeyStore()); factory.setTrustStore(sslStoreProvider.getTrustStore()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java index 469c2db3df..821bd9577f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -115,11 +115,11 @@ public class SslServerCustomizer implements NettyServerCustomizer { ? KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) : new ConfigurableAliasKeyManagerFactory(ssl.getKeyAlias(), KeyManagerFactory.getDefaultAlgorithm()); - char[] keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword().toCharArray() : null; - if (keyPassword == null && ssl.getKeyStorePassword() != null) { - keyPassword = ssl.getKeyStorePassword().toCharArray(); + String keyPassword = (sslStoreProvider != null) ? sslStoreProvider.getKeyPassword() : null; + if (keyPassword == null) { + keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword(); } - keyManagerFactory.init(keyStore, keyPassword); + keyManagerFactory.init(keyStore, (keyPassword != null) ? keyPassword.toCharArray() : null); return keyManagerFactory; } catch (Exception ex) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java index 3377f7213f..000ba757db 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java @@ -93,6 +93,10 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer { configureEnabledProtocols(protocol, ssl); if (sslStoreProvider != null) { configureSslStoreProvider(protocol, sslHostConfig, certificate, sslStoreProvider); + String keyPassword = sslStoreProvider.getKeyPassword(); + if (keyPassword != null) { + certificate.setCertificateKeyPassword(ciphers); + } } else { configureSslKeyStore(certificate, ssl); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java index 796dd920d9..a4674e8b39 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,11 +112,11 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer { SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias()); KeyManagerFactory keyManagerFactory = KeyManagerFactory .getInstance(KeyManagerFactory.getDefaultAlgorithm()); - char[] keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword().toCharArray() : null; - if (keyPassword == null && ssl.getKeyStorePassword() != null) { - keyPassword = ssl.getKeyStorePassword().toCharArray(); + String keyPassword = (sslStoreProvider != null) ? sslStoreProvider.getKeyPassword() : null; + if (keyPassword == null) { + keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword(); } - keyManagerFactory.init(keyStore, keyPassword); + keyManagerFactory.init(keyStore, (keyPassword != null) ? keyPassword.toCharArray() : null); if (ssl.getKeyAlias() != null) { return getConfigurableAliasKeyManagers(ssl, keyManagerFactory.getKeyManagers()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/CertificateFileSslStoreProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/CertificateFileSslStoreProvider.java index 56480fb8f3..2041759e54 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/CertificateFileSslStoreProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/CertificateFileSslStoreProvider.java @@ -32,7 +32,11 @@ import java.security.cert.X509Certificate; */ public final class CertificateFileSslStoreProvider implements SslStoreProvider { - private static final char[] NO_PASSWORD = {}; + /** + * The password of the private key entry in the {@link #getKeyStore provided + * KeyStore}. + */ + private static final String KEY_PASSWORD = ""; private static final String DEFAULT_KEY_ALIAS = "spring-boot-web"; @@ -45,7 +49,7 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider { @Override public KeyStore getKeyStore() throws Exception { return createKeyStore(this.ssl.getCertificate(), this.ssl.getCertificatePrivateKey(), - this.ssl.getKeyStorePassword(), this.ssl.getKeyStoreType(), this.ssl.getKeyAlias()); + this.ssl.getKeyStoreType(), this.ssl.getKeyAlias()); } @Override @@ -54,7 +58,12 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider { return null; } return createKeyStore(this.ssl.getTrustCertificate(), this.ssl.getTrustCertificatePrivateKey(), - this.ssl.getTrustStorePassword(), this.ssl.getTrustStoreType(), this.ssl.getKeyAlias()); + this.ssl.getTrustStoreType(), this.ssl.getKeyAlias()); + } + + @Override + public String getKeyPassword() { + return KEY_PASSWORD; } /** @@ -62,20 +71,18 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider { * specified file path and an optional private key. * @param certPath the path to the certificate authority file * @param keyPath the path to the private file - * @param password the key store password * @param storeType the {@code KeyStore} type to create * @param keyAlias the alias to use when adding keys to the {@code KeyStore} * @return the {@code KeyStore} */ - private KeyStore createKeyStore(String certPath, String keyPath, String password, String storeType, - String keyAlias) { + private KeyStore createKeyStore(String certPath, String keyPath, String storeType, String keyAlias) { try { KeyStore keyStore = KeyStore.getInstance((storeType != null) ? storeType : KeyStore.getDefaultType()); keyStore.load(null); X509Certificate[] certificates = CertificateParser.parse(certPath); PrivateKey privateKey = (keyPath != null) ? PrivateKeyParser.parse(keyPath) : null; try { - addCertificates(keyStore, certificates, privateKey, password, keyAlias); + addCertificates(keyStore, certificates, privateKey, keyAlias); } catch (KeyStoreException ex) { throw new IllegalStateException("Error adding certificates to KeyStore: " + ex.getMessage(), ex); @@ -88,11 +95,10 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider { } private void addCertificates(KeyStore keyStore, X509Certificate[] certificates, PrivateKey privateKey, - String password, String keyAlias) throws KeyStoreException { + String keyAlias) throws KeyStoreException { String alias = (keyAlias != null) ? keyAlias : DEFAULT_KEY_ALIAS; if (privateKey != null) { - keyStore.setKeyEntry(alias, privateKey, ((password != null) ? password.toCharArray() : NO_PASSWORD), - certificates); + keyStore.setKeyEntry(alias, privateKey, KEY_PASSWORD.toCharArray(), certificates); } else { for (int index = 0; index < certificates.length; index++) { @@ -102,9 +108,10 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider { } /** - * Create a {@link SslStoreProvider} if the appropriate SSL properties are configured. + * Create an {@link SslStoreProvider} if the appropriate SSL properties are + * configured. * @param ssl the SSL properties - * @return a {@code SslStoreProvider} or {@code null} + * @return an {@code SslStoreProvider} or {@code null} */ public static SslStoreProvider from(Ssl ssl) { if (ssl != null && ssl.isEnabled()) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/SslStoreProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/SslStoreProvider.java index 083e1df55f..ab483ec268 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/SslStoreProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/SslStoreProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,4 +41,13 @@ public interface SslStoreProvider { */ KeyStore getTrustStore() throws Exception; + /** + * Return the password of the private key in the key store. + * @return the key password + * @since 2.7.1 + */ + default String getKeyPassword() { + return null; + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java index 6e6f479669..0bf2ecbd5a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java @@ -285,7 +285,6 @@ public abstract class AbstractReactiveWebServerFactoryTests { ssl.setCertificate("classpath:test-cert.pem"); ssl.setCertificatePrivateKey("classpath:test-key.pem"); ssl.setTrustCertificate("classpath:test-cert.pem"); - ssl.setKeyStorePassword("secret"); testClientAuthSuccess(ssl, buildTrustAllSslWithClientKeyConnector("test.p12", "secret")); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java index 9f1f8bbde5..bccb0550b1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java @@ -732,7 +732,6 @@ public abstract class AbstractServletWebServerFactoryTests { ssl.setCertificate(cert); ssl.setCertificatePrivateKey(privateKey); ssl.setTrustCertificate(cert); - ssl.setKeyStorePassword("secret"); return ssl; }