mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +08:00
Allow alias and password to be configured on a per PEM store basis
Closes gh-38124
This commit is contained in:
parent
8bf847e549
commit
2c6fca8df7
@ -107,10 +107,11 @@ public final class PropertiesSslBundle implements SslBundle {
|
||||
}
|
||||
|
||||
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
|
||||
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
|
||||
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
|
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias(), null,
|
||||
properties.isVerifyKeys());
|
||||
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore())
|
||||
.withAlias(properties.getKey().getAlias());
|
||||
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore())
|
||||
.withAlias(properties.getKey().getAlias());
|
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.isVerifyKeys());
|
||||
}
|
||||
|
||||
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {
|
||||
|
@ -26,7 +26,6 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@ -61,39 +60,31 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
* @param keyStoreDetails the key store details
|
||||
* @param trustStoreDetails the trust store details
|
||||
* @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) {
|
||||
this(keyStoreDetails, trustStoreDetails, alias, null);
|
||||
this(keyStoreDetails, trustStoreDetails, alias, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link PemSslStoreBundle} instance.
|
||||
* @param keyStoreDetails the key 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
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
|
||||
String keyPassword, boolean verifyKeys) {
|
||||
this.keyStore = createKeyStore("key", keyStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS, keyPassword,
|
||||
verifyKeys);
|
||||
this.trustStore = createKeyStore("trust", trustStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS,
|
||||
keyPassword, verifyKeys);
|
||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails,
|
||||
boolean verifyKeys) {
|
||||
this(keyStoreDetails, trustStoreDetails, null, 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
|
||||
@ -111,13 +102,14 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
return this.trustStore;
|
||||
}
|
||||
|
||||
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, String keyPassword,
|
||||
boolean verifyKeys) {
|
||||
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, boolean verifyKeys) {
|
||||
if (details == null || details.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
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);
|
||||
X509Certificate[] certificates = loadCertificates(details);
|
||||
PrivateKey privateKey = loadPrivateKey(details);
|
||||
@ -125,7 +117,7 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
if (verifyKeys) {
|
||||
verifyKeys(privateKey, certificates);
|
||||
}
|
||||
addPrivateKey(store, privateKey, alias, keyPassword, certificates);
|
||||
addPrivateKey(store, privateKey, alias, details.password(), certificates);
|
||||
}
|
||||
else {
|
||||
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
|
||||
* {@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
|
||||
@ -35,28 +39,94 @@ import org.springframework.util.StringUtils;
|
||||
* @author Phillip Webb
|
||||
* @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) {
|
||||
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.
|
||||
* @param privateKey the new private key
|
||||
* @return a new {@link PemSslStoreDetails} instance
|
||||
*/
|
||||
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.
|
||||
* @param password the new private key password
|
||||
* @param privateKeyPassword the new private key password
|
||||
* @return a new {@link PemSslStoreDetails} instance
|
||||
*/
|
||||
public PemSslStoreDetails withPrivateKeyPassword(String password) {
|
||||
return new PemSslStoreDetails(this.type, this.certificate, this.privateKey, password);
|
||||
public PemSslStoreDetails withPrivateKeyPassword(String privateKeyPassword) {
|
||||
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, this.privateKey,
|
||||
privateKeyPassword);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
|
@ -62,10 +62,12 @@ public final class WebServerSslBundle implements SslBundle {
|
||||
|
||||
private static SslStoreBundle createPemStoreBundle(Ssl ssl) {
|
||||
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(),
|
||||
ssl.getCertificatePrivateKey());
|
||||
ssl.getCertificatePrivateKey())
|
||||
.withAlias(ssl.getKeyAlias());
|
||||
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(),
|
||||
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey());
|
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias());
|
||||
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey())
|
||||
.withAlias(ssl.getKeyAlias());
|
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
}
|
||||
|
||||
private static SslStoreBundle createJksStoreBundle(Ssl ssl) {
|
||||
|
@ -166,6 +166,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
@ -190,21 +191,26 @@ class PemSslStoreBundleTests {
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
|
||||
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")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, "test-alias", "keysecret");
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||
assertThat(bundle.getTrustStore())
|
||||
.satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||
.withPrivateKey("classpath:test-key.pem")
|
||||
.withAlias("tsa")
|
||||
.withPassword("tss");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ksa", "kss".toCharArray()));
|
||||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("tsa", "tss".toCharArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldVerifyKeysIfEnabled() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key1.crt")
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem")
|
||||
.withAlias("test-alias")
|
||||
.withPassword("keysecret");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||
}
|
||||
|
||||
@ -212,8 +218,10 @@ class PemSslStoreBundleTests {
|
||||
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem")
|
||||
.withAlias("test-alias")
|
||||
.withPassword("keysecret");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
|
||||
}
|
||||
|
||||
@ -222,8 +230,7 @@ class PemSslStoreBundleTests {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2.crt")
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, null, null, true))
|
||||
assertThatIllegalStateException().isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, true))
|
||||
.withMessageContaining("Private key matches none of the certificates");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user