Skip to content

Commit 2c6fca8

Browse files
committed
Allow alias and password to be configured on a per PEM store basis
Closes gh-38124
1 parent 8bf847e commit 2c6fca8

File tree

5 files changed

+121
-49
lines changed

5 files changed

+121
-49
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ public static SslBundle get(JksSslBundleProperties properties) {
107107
}
108108

109109
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
110-
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
111-
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
112-
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.getKey().getAlias(), null,
113-
properties.isVerifyKeys());
110+
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore())
111+
.withAlias(properties.getKey().getAlias());
112+
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore())
113+
.withAlias(properties.getKey().getAlias());
114+
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.isVerifyKeys());
114115
}
115116

116117
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.List;
2727

2828
import org.springframework.boot.ssl.SslStoreBundle;
29-
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
3029
import org.springframework.util.Assert;
3130
import org.springframework.util.CollectionUtils;
3231
import org.springframework.util.StringUtils;
@@ -61,39 +60,31 @@ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails
6160
* @param keyStoreDetails the key store details
6261
* @param trustStoreDetails the trust store details
6362
* @param alias the alias to use or {@code null} to use a default alias
63+
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of
64+
* {@link PemSslStoreDetails#alias()} in the {@code keyStoreDetails} and
65+
* {@code trustStoreDetails}
6466
*/
67+
@Deprecated(since = "3.2.0", forRemoval = true)
6568
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
66-
this(keyStoreDetails, trustStoreDetails, alias, null);
69+
this(keyStoreDetails, trustStoreDetails, alias, false);
6770
}
6871

6972
/**
7073
* Create a new {@link PemSslStoreBundle} instance.
7174
* @param keyStoreDetails the key store details
7275
* @param trustStoreDetails the trust store details
73-
* @param alias the alias to use or {@code null} to use a default alias
74-
* @param keyPassword the password to protect the key (if one is added)
76+
* @param verifyKeys whether to verify that the private key matches the public key
7577
* @since 3.2.0
7678
*/
77-
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
78-
String keyPassword) {
79-
this(keyStoreDetails, trustStoreDetails, alias, keyPassword, false);
79+
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails,
80+
boolean verifyKeys) {
81+
this(keyStoreDetails, trustStoreDetails, null, verifyKeys);
8082
}
8183

82-
/**
83-
* Create a new {@link PemSslStoreBundle} instance.
84-
* @param keyStoreDetails the key store details
85-
* @param trustStoreDetails the trust store details
86-
* @param alias the key alias to use or {@code null} to use a default alias
87-
* @param keyPassword the password to protect the key (if one is added)
88-
* @param verifyKeys whether to verify that the private key matches the public key
89-
* @since 3.2.0
90-
*/
91-
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
92-
String keyPassword, boolean verifyKeys) {
93-
this.keyStore = createKeyStore("key", keyStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS, keyPassword,
94-
verifyKeys);
95-
this.trustStore = createKeyStore("trust", trustStoreDetails, (alias != null) ? alias : DEFAULT_ALIAS,
96-
keyPassword, verifyKeys);
84+
private PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
85+
boolean verifyKeys) {
86+
this.keyStore = createKeyStore("key", keyStoreDetails, alias, verifyKeys);
87+
this.trustStore = createKeyStore("trust", trustStoreDetails, alias, verifyKeys);
9788
}
9889

9990
@Override
@@ -111,21 +102,22 @@ public KeyStore getTrustStore() {
111102
return this.trustStore;
112103
}
113104

114-
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, String keyPassword,
115-
boolean verifyKeys) {
105+
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, boolean verifyKeys) {
116106
if (details == null || details.isEmpty()) {
117107
return null;
118108
}
119109
try {
120110
Assert.notNull(details.certificate(), "Certificate content must not be null");
111+
alias = (details.alias() != null) ? details.alias() : alias;
112+
alias = (alias != null) ? alias : DEFAULT_ALIAS;
121113
KeyStore store = createKeyStore(details);
122114
X509Certificate[] certificates = loadCertificates(details);
123115
PrivateKey privateKey = loadPrivateKey(details);
124116
if (privateKey != null) {
125117
if (verifyKeys) {
126118
verifyKeys(privateKey, certificates);
127119
}
128-
addPrivateKey(store, privateKey, alias, keyPassword, certificates);
120+
addPrivateKey(store, privateKey, alias, details.password(), certificates);
129121
}
130122
else {
131123
addCertificates(store, certificates, alias);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
*
2727
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
2828
* {@code null} value will use {@link KeyStore#getDefaultType()}).
29+
* @param alias the alias used when setting entries in the {@link KeyStore}
30+
* @param password the password used
31+
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
32+
* setting key entries} in the {@link KeyStore}
2933
* @param certificate the certificate content (either the PEM content itself or something
3034
* that can be loaded by {@link ResourceUtils#getURL})
3135
* @param privateKey the private key content (either the PEM content itself or something
@@ -35,28 +39,94 @@
3539
* @author Phillip Webb
3640
* @since 3.1.0
3741
*/
38-
public record PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) {
42+
public record PemSslStoreDetails(String type, String alias, String password, String certificate, String privateKey,
43+
String privateKeyPassword) {
3944

45+
/**
46+
* Create a new {@link PemSslStoreDetails} instance.
47+
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
48+
* {@code null} value will use {@link KeyStore#getDefaultType()}).
49+
* @param alias the alias used when setting entries in the {@link KeyStore}
50+
* @param password the password used
51+
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
52+
* setting key entries} in the {@link KeyStore}
53+
* @param certificate the certificate content (either the PEM content itself or
54+
* something that can be loaded by {@link ResourceUtils#getURL})
55+
* @param privateKey the private key content (either the PEM content itself or
56+
* something that can be loaded by {@link ResourceUtils#getURL})
57+
* @param privateKeyPassword a password used to decrypt an encrypted private key
58+
* @since 3.2.0
59+
*/
60+
public PemSslStoreDetails {
61+
}
62+
63+
/**
64+
* Create a new {@link PemSslStoreDetails} instance.
65+
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
66+
* {@code null} value will use {@link KeyStore#getDefaultType()}).
67+
* @param certificate the certificate content (either the PEM content itself or
68+
* something that can be loaded by {@link ResourceUtils#getURL})
69+
* @param privateKey the private key content (either the PEM content itself or
70+
* something that can be loaded by {@link ResourceUtils#getURL})
71+
* @param privateKeyPassword a password used to decrypt an encrypted private key
72+
*/
73+
public PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) {
74+
this(type, null, null, certificate, privateKey, null);
75+
}
76+
77+
/**
78+
* Create a new {@link PemSslStoreDetails} instance.
79+
* @param type the key store type, for example {@code JKS} or {@code PKCS11}. A
80+
* {@code null} value will use {@link KeyStore#getDefaultType()}).
81+
* @param certificate the certificate content (either the PEM content itself or
82+
* something that can be loaded by {@link ResourceUtils#getURL})
83+
* @param privateKey the private key content (either the PEM content itself or
84+
* something that can be loaded by {@link ResourceUtils#getURL})
85+
*/
4086
public PemSslStoreDetails(String type, String certificate, String privateKey) {
4187
this(type, certificate, privateKey, null);
4288
}
4389

90+
/**
91+
* Return a new {@link PemSslStoreDetails} instance with a new alias.
92+
* @param alias the new alias
93+
* @return a new {@link PemSslStoreDetails} instance
94+
* @since 3.2.0
95+
*/
96+
public PemSslStoreDetails withAlias(String alias) {
97+
return new PemSslStoreDetails(this.type, alias, this.password, this.certificate, this.privateKey,
98+
this.privateKeyPassword);
99+
}
100+
101+
/**
102+
* Return a new {@link PemSslStoreDetails} instance with a new password.
103+
* @param password the new password
104+
* @return a new {@link PemSslStoreDetails} instance
105+
* @since 3.2.0
106+
*/
107+
public PemSslStoreDetails withPassword(String password) {
108+
return new PemSslStoreDetails(this.type, this.alias, password, this.certificate, this.privateKey,
109+
this.privateKeyPassword);
110+
}
111+
44112
/**
45113
* Return a new {@link PemSslStoreDetails} instance with a new private key.
46114
* @param privateKey the new private key
47115
* @return a new {@link PemSslStoreDetails} instance
48116
*/
49117
public PemSslStoreDetails withPrivateKey(String privateKey) {
50-
return new PemSslStoreDetails(this.type, this.certificate, privateKey, this.privateKeyPassword);
118+
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, privateKey,
119+
this.privateKeyPassword);
51120
}
52121

53122
/**
54123
* Return a new {@link PemSslStoreDetails} instance with a new private key password.
55-
* @param password the new private key password
124+
* @param privateKeyPassword the new private key password
56125
* @return a new {@link PemSslStoreDetails} instance
57126
*/
58-
public PemSslStoreDetails withPrivateKeyPassword(String password) {
59-
return new PemSslStoreDetails(this.type, this.certificate, this.privateKey, password);
127+
public PemSslStoreDetails withPrivateKeyPassword(String privateKeyPassword) {
128+
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, this.privateKey,
129+
privateKeyPassword);
60130
}
61131

62132
boolean isEmpty() {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/WebServerSslBundle.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,12 @@ private WebServerSslBundle(SslStoreBundle stores, String keyPassword, Ssl ssl) {
6262

6363
private static SslStoreBundle createPemStoreBundle(Ssl ssl) {
6464
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(),
65-
ssl.getCertificatePrivateKey());
65+
ssl.getCertificatePrivateKey())
66+
.withAlias(ssl.getKeyAlias());
6667
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(),
67-
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey());
68-
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias());
68+
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey())
69+
.withAlias(ssl.getKeyAlias());
70+
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
6971
}
7072

7173
private static SslStoreBundle createJksStoreBundle(Ssl ssl) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ void whenHasEmbeddedKeyStoreDetailsAndTrustStoreDetails() {
166166
}
167167

168168
@Test
169+
@SuppressWarnings("removal")
169170
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
170171
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
171172
.withPrivateKey("classpath:test-key.pem");
@@ -190,30 +191,37 @@ void whenHasStoreType() {
190191
@Test
191192
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
192193
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
193-
.withPrivateKey("classpath:test-key.pem");
194+
.withPrivateKey("classpath:test-key.pem")
195+
.withAlias("ksa")
196+
.withPassword("kss");
194197
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
195-
.withPrivateKey("classpath:test-key.pem");
196-
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, "test-alias", "keysecret");
197-
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
198-
assertThat(bundle.getTrustStore())
199-
.satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
198+
.withPrivateKey("classpath:test-key.pem")
199+
.withAlias("tsa")
200+
.withPassword("tss");
201+
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
202+
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ksa", "kss".toCharArray()));
203+
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("tsa", "tss".toCharArray()));
200204
}
201205

202206
@Test
203207
void shouldVerifyKeysIfEnabled() {
204208
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
205209
.forCertificate("classpath:org/springframework/boot/ssl/pem/key1.crt")
206-
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
207-
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
210+
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem")
211+
.withAlias("test-alias")
212+
.withPassword("keysecret");
213+
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
208214
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
209215
}
210216

211217
@Test
212218
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
213219
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
214220
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
215-
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem");
216-
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, "test-alias", "keysecret", true);
221+
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem")
222+
.withAlias("test-alias")
223+
.withPassword("keysecret");
224+
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, null, true);
217225
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("test-alias", "keysecret".toCharArray()));
218226
}
219227

@@ -222,8 +230,7 @@ void shouldFailIfVerifyKeysIsEnabledAndKeysDontMatch() {
222230
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
223231
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2.crt")
224232
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key1.pem");
225-
assertThatIllegalStateException()
226-
.isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, null, null, true))
233+
assertThatIllegalStateException().isThrownBy(() -> new PemSslStoreBundle(keyStoreDetails, null, true))
227234
.withMessageContaining("Private key matches none of the certificates");
228235
}
229236

0 commit comments

Comments
 (0)