Allow alias and password to be configured on a per PEM store basis
Closes gh-38124
This commit is contained in:
@@ -107,10 +107,11 @@ public final class PropertiesSslBundle implements SslBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
|
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
|
||||||
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
|
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore())
|
||||||
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
|
.withAlias(properties.getKey().getAlias());
|
||||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias(), null,
|
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore())
|
||||||
properties.isVerifyKeys());
|
.withAlias(properties.getKey().getAlias());
|
||||||
|
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.isVerifyKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {
|
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.boot.ssl.SslStoreBundle;
|
import org.springframework.boot.ssl.SslStoreBundle;
|
||||||
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -61,39 +60,31 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||||||
* @param keyStoreDetails the key store details
|
* @param keyStoreDetails the key store details
|
||||||
* @param trustStoreDetails the trust store details
|
* @param trustStoreDetails the trust store details
|
||||||
* @param alias the alias to use or {@code null} to use a default alias
|
* @param alias the alias to use or {@code null} to use a default alias
|
||||||
|
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of
|
||||||
|
* {@link PemSslStoreDetails#alias()} in the {@code keyStoreDetails} and
|
||||||
|
* {@code trustStoreDetails}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
|
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
|
||||||
this(keyStoreDetails, trustStoreDetails, alias, null);
|
this(keyStoreDetails, trustStoreDetails, alias, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link PemSslStoreBundle} instance.
|
* Create a new {@link PemSslStoreBundle} instance.
|
||||||
* @param keyStoreDetails the key store details
|
* @param keyStoreDetails the key store details
|
||||||
* @param trustStoreDetails the trust store details
|
* @param trustStoreDetails the trust store details
|
||||||
* @param alias the alias to use or {@code null} to use a default alias
|
|
||||||
* @param keyPassword the password to protect the key (if one is added)
|
|
||||||
* @since 3.2.0
|
|
||||||
*/
|
|
||||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
|
|
||||||
String keyPassword) {
|
|
||||||
this(keyStoreDetails, trustStoreDetails, alias, keyPassword, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new {@link PemSslStoreBundle} instance.
|
|
||||||
* @param keyStoreDetails the key store details
|
|
||||||
* @param trustStoreDetails the trust store details
|
|
||||||
* @param alias the key alias to use or {@code null} to use a default alias
|
|
||||||
* @param keyPassword the password to protect the key (if one is added)
|
|
||||||
* @param verifyKeys whether to verify that the private key matches the public key
|
* @param verifyKeys whether to verify that the private key matches the public key
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
*/
|
*/
|
||||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
|
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails,
|
||||||
String keyPassword, boolean verifyKeys) {
|
boolean verifyKeys) {
|
||||||
this.keyStore = createKeyStore("key", keyStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS, keyPassword,
|
this(keyStoreDetails, trustStoreDetails, null, verifyKeys);
|
||||||
verifyKeys);
|
}
|
||||||
this.trustStore = createKeyStore("trust", trustStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS,
|
|
||||||
keyPassword, verifyKeys);
|
private PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
|
||||||
|
boolean verifyKeys) {
|
||||||
|
this.keyStore = createKeyStore("key", keyStoreDetails, alias, verifyKeys);
|
||||||
|
this.trustStore = createKeyStore("trust", trustStoreDetails, alias, verifyKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,13 +102,14 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||||||
return this.trustStore;
|
return this.trustStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, String keyPassword,
|
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, boolean verifyKeys) {
|
||||||
boolean verifyKeys) {
|
|
||||||
if (details == null || details.isEmpty()) {
|
if (details == null || details.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Assert.notNull(details.certificate(), "Certificate content must not be null");
|
Assert.notNull(details.certificate(), "Certificate content must not be null");
|
||||||
|
alias = (details.alias() != null) ? details.alias() : alias;
|
||||||
|
alias = (alias != null) ? alias : DEFAULT_ALIAS;
|
||||||
KeyStore store = createKeyStore(details);
|
KeyStore store = createKeyStore(details);
|
||||||
X509Certificate[] certificates = loadCertificates(details);
|
X509Certificate[] certificates = loadCertificates(details);
|
||||||
PrivateKey privateKey = loadPrivateKey(details);
|
PrivateKey privateKey = loadPrivateKey(details);
|
||||||
@@ -125,7 +117,7 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||||||
if (verifyKeys) {
|
if (verifyKeys) {
|
||||||
verifyKeys(privateKey, certificates);
|
verifyKeys(privateKey, certificates);
|
||||||
}
|
}
|
||||||
addPrivateKey(store, privateKey, alias, keyPassword, certificates);
|
addPrivateKey(store, privateKey, alias, details.password(), certificates);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
addCertificates(store, certificates, alias);
|
addCertificates(store, certificates, alias);
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
|
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
|
||||||
* {@code null} value will use {@link KeyStore#getDefaultType()}).
|
* {@code null} value will use {@link KeyStore#getDefaultType()}).
|
||||||
|
* @param alias the alias used when setting entries in the {@link KeyStore}
|
||||||
|
* @param password the password used
|
||||||
|
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||||
|
* setting key entries} in the {@link KeyStore}
|
||||||
* @param certificate the certificate content (either the PEM content itself or something
|
* @param certificate the certificate content (either the PEM content itself or something
|
||||||
* that can be loaded by {@link ResourceUtils#getURL})
|
* that can be loaded by {@link ResourceUtils#getURL})
|
||||||
* @param privateKey the private key content (either the PEM content itself or something
|
* @param privateKey the private key content (either the PEM content itself or something
|
||||||
@@ -35,28 +39,94 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
*/
|
*/
|
||||||
public record PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) {
|
public record PemSslStoreDetails(String type, String alias, String password, String certificate, String privateKey,
|
||||||
|
String privateKeyPassword) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link PemSslStoreDetails} instance.
|
||||||
|
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
|
||||||
|
* {@code null} value will use {@link KeyStore#getDefaultType()}).
|
||||||
|
* @param alias the alias used when setting entries in the {@link KeyStore}
|
||||||
|
* @param password the password used
|
||||||
|
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||||
|
* setting key entries} in the {@link KeyStore}
|
||||||
|
* @param certificate the certificate content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
* @param privateKey the private key content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
* @param privateKeyPassword a password used to decrypt an encrypted private key
|
||||||
|
* @since 3.2.0
|
||||||
|
*/
|
||||||
|
public PemSslStoreDetails {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link PemSslStoreDetails} instance.
|
||||||
|
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
|
||||||
|
* {@code null} value will use {@link KeyStore#getDefaultType()}).
|
||||||
|
* @param certificate the certificate content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
* @param privateKey the private key content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
* @param privateKeyPassword a password used to decrypt an encrypted private key
|
||||||
|
*/
|
||||||
|
public PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) {
|
||||||
|
this(type, null, null, certificate, privateKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link PemSslStoreDetails} instance.
|
||||||
|
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
|
||||||
|
* {@code null} value will use {@link KeyStore#getDefaultType()}).
|
||||||
|
* @param certificate the certificate content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
* @param privateKey the private key content (either the PEM content itself or
|
||||||
|
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||||
|
*/
|
||||||
public PemSslStoreDetails(String type, String certificate, String privateKey) {
|
public PemSslStoreDetails(String type, String certificate, String privateKey) {
|
||||||
this(type, certificate, privateKey, null);
|
this(type, certificate, privateKey, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link PemSslStoreDetails} instance with a new alias.
|
||||||
|
* @param alias the new alias
|
||||||
|
* @return a new {@link PemSslStoreDetails} instance
|
||||||
|
* @since 3.2.0
|
||||||
|
*/
|
||||||
|
public PemSslStoreDetails withAlias(String alias) {
|
||||||
|
return new PemSslStoreDetails(this.type, alias, this.password, this.certificate, this.privateKey,
|
||||||
|
this.privateKeyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link PemSslStoreDetails} instance with a new password.
|
||||||
|
* @param password the new password
|
||||||
|
* @return a new {@link PemSslStoreDetails} instance
|
||||||
|
* @since 3.2.0
|
||||||
|
*/
|
||||||
|
public PemSslStoreDetails withPassword(String password) {
|
||||||
|
return new PemSslStoreDetails(this.type, this.alias, password, this.certificate, this.privateKey,
|
||||||
|
this.privateKeyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new {@link PemSslStoreDetails} instance with a new private key.
|
* Return a new {@link PemSslStoreDetails} instance with a new private key.
|
||||||
* @param privateKey the new private key
|
* @param privateKey the new private key
|
||||||
* @return a new {@link PemSslStoreDetails} instance
|
* @return a new {@link PemSslStoreDetails} instance
|
||||||
*/
|
*/
|
||||||
public PemSslStoreDetails withPrivateKey(String privateKey) {
|
public PemSslStoreDetails withPrivateKey(String privateKey) {
|
||||||
return new PemSslStoreDetails(this.type, this.certificate, privateKey, this.privateKeyPassword);
|
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, privateKey,
|
||||||
|
this.privateKeyPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new {@link PemSslStoreDetails} instance with a new private key password.
|
* Return a new {@link PemSslStoreDetails} instance with a new private key password.
|
||||||
* @param password the new private key password
|
* @param privateKeyPassword the new private key password
|
||||||
* @return a new {@link PemSslStoreDetails} instance
|
* @return a new {@link PemSslStoreDetails} instance
|
||||||
*/
|
*/
|
||||||
public PemSslStoreDetails withPrivateKeyPassword(String password) {
|
public PemSslStoreDetails withPrivateKeyPassword(String privateKeyPassword) {
|
||||||
return new PemSslStoreDetails(this.type, this.certificate, this.privateKey, password);
|
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, this.privateKey,
|
||||||
|
privateKeyPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isEmpty() {
|
boolean isEmpty() {
|
||||||
|
|||||||
@@ -62,10 +62,12 @@ public final class WebServerSslBundle implements SslBundle {
|
|||||||
|
|
||||||
private static SslStoreBundle createPemStoreBundle(Ssl ssl) {
|
private static SslStoreBundle createPemStoreBundle(Ssl ssl) {
|
||||||
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(),
|
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(),
|
||||||
ssl.getCertificatePrivateKey());
|
ssl.getCertificatePrivateKey())
|
||||||
|
.withAlias(ssl.getKeyAlias());
|
||||||
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(),
|
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(),
|
||||||
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey());
|
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey())
|
||||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias());
|
.withAlias(ssl.getKeyAlias());
|
||||||
|
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SslStoreBundle createJksStoreBundle(Ssl ssl) {
|
private static SslStoreBundle createJksStoreBundle(Ssl ssl) {
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ class PemSslStoreBundleTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("removal")
|
||||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
|
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
|
||||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||||
.withPrivateKey("classpath:test-key.pem");
|
.withPrivateKey("classpath:test-key.pem");
|
||||||
@@ -190,21 +191,26 @@ class PemSslStoreBundleTests {
|
|||||||
@Test
|
@Test
|
||||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
|
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
|
||||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||||
.withPrivateKey("classpath:test-key.pem");
|
.withPrivateKey("classpath:test-key.pem")
|
||||||
|
.withAlias("ksa")
|
||||||
|
.withPassword("kss");
|
||||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||||
.withPrivateKey("classpath:test-key.pem");
|
.withPrivateKey("classpath:test-key.pem")
|
||||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, "test-alias", "keysecret");
|
.withAlias("tsa")
|
||||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
.withPassword("tss");
|
||||||
assertThat(bundle.getTrustStore())
|
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||||
.satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ksa", "kss".toCharArray()));
|
||||||
|
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("tsa", "tss".toCharArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldVerifyKeysIfEnabled() {
|
void shouldVerifyKeysIfEnabled() {
|
||||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key1.crt")
|
.forCertificate("classpath:org/springframework/boot/ssl/pem/key1.crt")
|
||||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
|
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem")
|
||||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
|
.withAlias("test-alias")
|
||||||
|
.withPassword("keysecret");
|
||||||
|
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
|
||||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +218,10 @@ class PemSslStoreBundleTests {
|
|||||||
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
|
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
|
||||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
|
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
|
||||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem");
|
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem")
|
||||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
|
.withAlias("test-alias")
|
||||||
|
.withPassword("keysecret");
|
||||||
|
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
|
||||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,8 +230,7 @@ class PemSslStoreBundleTests {
|
|||||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2.crt")
|
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2.crt")
|
||||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
|
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
|
||||||
assertThatIllegalStateException()
|
assertThatIllegalStateException().isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, true))
|
||||||
.isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, null, null, true))
|
|
||||||
.withMessageContaining("Private key matches none of the certificates");
|
.withMessageContaining("Private key matches none of the certificates");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user