Skip to content

Commit 21b4eb5

Browse files
committed
Merge branch '3.2.x' into 3.3.x
Closes gh-42169
2 parents 4d2aa2d + a89ae3f commit 21b4eb5

File tree

6 files changed

+100
-43
lines changed

6 files changed

+100
-43
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/jks/JksSslStoreBundle.java

+12-8
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
import java.security.NoSuchAlgorithmException;
2424
import java.security.NoSuchProviderException;
2525
import java.security.cert.CertificateException;
26+
import java.util.function.Supplier;
2627

2728
import org.springframework.boot.io.ApplicationResourceLoader;
2829
import org.springframework.boot.ssl.SslStoreBundle;
2930
import org.springframework.core.io.Resource;
3031
import org.springframework.core.style.ToStringCreator;
3132
import org.springframework.util.Assert;
3233
import org.springframework.util.StringUtils;
34+
import org.springframework.util.function.SingletonSupplier;
3335

3436
/**
3537
* {@link SslStoreBundle} backed by a Java keystore.
@@ -43,9 +45,9 @@ public class JksSslStoreBundle implements SslStoreBundle {
4345

4446
private final JksSslStoreDetails keyStoreDetails;
4547

46-
private final KeyStore keyStore;
48+
private final Supplier<KeyStore> keyStore;
4749

48-
private final KeyStore trustStore;
50+
private final Supplier<KeyStore> trustStore;
4951

5052
/**
5153
* Create a new {@link JksSslStoreBundle} instance.
@@ -54,13 +56,13 @@ public class JksSslStoreBundle implements SslStoreBundle {
5456
*/
5557
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
5658
this.keyStoreDetails = keyStoreDetails;
57-
this.keyStore = createKeyStore("key", this.keyStoreDetails);
58-
this.trustStore = createKeyStore("trust", trustStoreDetails);
59+
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", this.keyStoreDetails));
60+
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", trustStoreDetails));
5961
}
6062

6163
@Override
6264
public KeyStore getKeyStore() {
63-
return this.keyStore;
65+
return this.keyStore.get();
6466
}
6567

6668
@Override
@@ -70,7 +72,7 @@ public String getKeyStorePassword() {
7072

7173
@Override
7274
public KeyStore getTrustStore() {
73-
return this.trustStore;
75+
return this.trustStore.get();
7476
}
7577

7678
private KeyStore createKeyStore(String name, JksSslStoreDetails details) {
@@ -127,10 +129,12 @@ private void loadKeyStore(KeyStore store, String location, char[] password) {
127129
@Override
128130
public String toString() {
129131
ToStringCreator creator = new ToStringCreator(this);
130-
creator.append("keyStore.type", (this.keyStore != null) ? this.keyStore.getType() : "none");
132+
KeyStore keyStore = this.keyStore.get();
133+
creator.append("keyStore.type", (keyStore != null) ? keyStore.getType() : "none");
131134
String keyStorePassword = getKeyStorePassword();
132135
creator.append("keyStorePassword", (keyStorePassword != null) ? "******" : null);
133-
creator.append("trustStore.type", (this.trustStore != null) ? this.trustStore.getType() : "none");
136+
KeyStore trustStore = this.trustStore.get();
137+
creator.append("trustStore.type", (trustStore != null) ? trustStore.getType() : "none");
134138
return creator.toString();
135139
}
136140

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -97,4 +97,14 @@ public PrivateKey privateKey() {
9797
return this.privateKeySupplier.get();
9898
}
9999

100+
@Override
101+
public PemSslStore withAlias(String alias) {
102+
return new LoadedPemSslStore(this.details.withAlias(alias));
103+
}
104+
105+
@Override
106+
public PemSslStore withPassword(String password) {
107+
return new LoadedPemSslStore(this.details.withPassword(password));
108+
}
109+
100110
}

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

+17-12
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import java.security.cert.CertificateException;
2525
import java.security.cert.X509Certificate;
2626
import java.util.List;
27+
import java.util.function.Supplier;
2728

2829
import org.springframework.boot.ssl.SslStoreBundle;
2930
import org.springframework.core.style.ToStringCreator;
3031
import org.springframework.util.Assert;
3132
import org.springframework.util.StringUtils;
33+
import org.springframework.util.function.SingletonSupplier;
3234

3335
/**
3436
* {@link SslStoreBundle} backed by PEM-encoded certificates and private keys.
@@ -42,9 +44,9 @@ public class PemSslStoreBundle implements SslStoreBundle {
4244

4345
private static final String DEFAULT_ALIAS = "ssl";
4446

45-
private final KeyStore keyStore;
47+
private final Supplier<KeyStore> keyStore;
4648

47-
private final KeyStore trustStore;
49+
private final Supplier<KeyStore> trustStore;
4850

4951
/**
5052
* Create a new {@link PemSslStoreBundle} instance.
@@ -66,8 +68,9 @@ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails
6668
*/
6769
@Deprecated(since = "3.2.0", forRemoval = true)
6870
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
69-
this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias);
70-
this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias);
71+
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", PemSslStore.load(keyStoreDetails), alias));
72+
this.trustStore = SingletonSupplier
73+
.of(() -> createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias));
7174
}
7275

7376
/**
@@ -81,13 +84,13 @@ public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore) {
8184
}
8285

8386
private PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore, String alias) {
84-
this.keyStore = createKeyStore("key", pemKeyStore, alias);
85-
this.trustStore = createKeyStore("trust", pemTrustStore, alias);
87+
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", pemKeyStore, alias));
88+
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", pemTrustStore, alias));
8689
}
8790

8891
@Override
8992
public KeyStore getKeyStore() {
90-
return this.keyStore;
93+
return this.keyStore.get();
9194
}
9295

9396
@Override
@@ -97,19 +100,19 @@ public String getKeyStorePassword() {
97100

98101
@Override
99102
public KeyStore getTrustStore() {
100-
return this.trustStore;
103+
return this.trustStore.get();
101104
}
102105

103106
private static KeyStore createKeyStore(String name, PemSslStore pemSslStore, String alias) {
104107
if (pemSslStore == null) {
105108
return null;
106109
}
107110
try {
108-
Assert.notEmpty(pemSslStore.certificates(), "Certificates must not be empty");
111+
List<X509Certificate> certificates = pemSslStore.certificates();
112+
Assert.notEmpty(certificates, "Certificates must not be empty");
109113
alias = (pemSslStore.alias() != null) ? pemSslStore.alias() : alias;
110114
alias = (alias != null) ? alias : DEFAULT_ALIAS;
111115
KeyStore store = createKeyStore(pemSslStore.type());
112-
List<X509Certificate> certificates = pemSslStore.certificates();
113116
PrivateKey privateKey = pemSslStore.privateKey();
114117
if (privateKey != null) {
115118
addPrivateKey(store, privateKey, alias, pemSslStore.password(), certificates);
@@ -149,9 +152,11 @@ private static void addCertificates(KeyStore keyStore, List<X509Certificate> cer
149152
@Override
150153
public String toString() {
151154
ToStringCreator creator = new ToStringCreator(this);
152-
creator.append("keyStore.type", (this.keyStore != null) ? this.keyStore.getType() : "none");
155+
KeyStore keyStore = this.keyStore.get();
156+
KeyStore trustStore = this.trustStore.get();
157+
creator.append("keyStore.type", (keyStore != null) ? keyStore.getType() : "none");
153158
creator.append("keyStorePassword", null);
154-
creator.append("trustStore.type", (this.trustStore != null) ? this.trustStore.getType() : "none");
159+
creator.append("trustStore.type", (trustStore != null) ? trustStore.getType() : "none");
155160
return creator.toString();
156161
}
157162

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/jks/JksSslStoreBundleTests.java

+26-20
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,10 @@ void whenStoresHaveNoValues() {
6363
}
6464

6565
@Test
66-
void whenTypePKCS11AndLocationThrowsException() {
67-
assertThatIllegalStateException().isThrownBy(() -> {
68-
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
69-
JksSslStoreDetails trustStoreDetails = null;
70-
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
71-
})
66+
void whenTypePKCS11AndLocationGetKeyStoreThrowsException() {
67+
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
68+
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
69+
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
7270
.withMessageContaining(
7371
"Unable to create key store: Location is 'test.jks', but must be empty or null for PKCS11 hardware key stores");
7472
}
@@ -109,22 +107,28 @@ void whenHasTrustStoreType() {
109107

110108
@Test
111109
void whenHasKeyStoreProvider() {
112-
assertThatIllegalStateException().isThrownBy(() -> {
113-
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
114-
"classpath:test.jks", "secret");
115-
JksSslStoreDetails trustStoreDetails = null;
116-
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
117-
}).withMessageContaining("com.example.KeyStoreProvider");
110+
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
111+
"classpath:test.jks", "secret");
112+
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
113+
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
114+
.withMessageContaining("com.example.KeyStoreProvider");
118115
}
119116

120117
@Test
121118
void whenHasTrustStoreProvider() {
122-
assertThatIllegalStateException().isThrownBy(() -> {
123-
JksSslStoreDetails keyStoreDetails = null;
124-
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
125-
"classpath:test.jks", "secret");
126-
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
127-
}).withMessageContaining("com.example.KeyStoreProvider");
119+
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
120+
"classpath:test.jks", "secret");
121+
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(null, trustStoreDetails);
122+
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getTrustStore)
123+
.withMessageContaining("com.example.KeyStoreProvider");
124+
}
125+
126+
@Test
127+
void storeCreationIsLazy() {
128+
JksSslStoreDetails details = new JksSslStoreDetails(null, null, "does-not-exist", null);
129+
JksSslStoreBundle bundle = new JksSslStoreBundle(details, details);
130+
assertThatIllegalStateException().isThrownBy(bundle::getKeyStore);
131+
assertThatIllegalStateException().isThrownBy(bundle::getTrustStore);
128132
}
129133

130134
@Test
@@ -141,7 +145,8 @@ void whenLocationsAreBase64Encoded() throws IOException {
141145
@Test
142146
void invalidBase64EncodedLocationThrowsException() {
143147
JksSslStoreDetails keyStoreDetails = JksSslStoreDetails.forLocation("base64:not base 64");
144-
assertThatIllegalStateException().isThrownBy(() -> new JksSslStoreBundle(keyStoreDetails, null))
148+
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
149+
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
145150
.withMessageContaining("key store")
146151
.withMessageContaining("base64:not base 64")
147152
.havingRootCause()
@@ -152,7 +157,8 @@ void invalidBase64EncodedLocationThrowsException() {
152157
@Test
153158
void invalidLocationThrowsException() {
154159
JksSslStoreDetails trustStoreDetails = JksSslStoreDetails.forLocation("does-not-exist.p12");
155-
assertThatIllegalStateException().isThrownBy(() -> new JksSslStoreBundle(null, trustStoreDetails))
160+
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(null, trustStoreDetails);
161+
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getTrustStore)
156162
.withMessageContaining("trust store")
157163
.withMessageContaining("does-not-exist.p12");
158164
}

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,4 +45,20 @@ void privateKeyIsLoadedLazily() {
4545
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::privateKey);
4646
}
4747

48+
@Test
49+
void withAliasIsLazy() {
50+
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
51+
.withPrivateKey("classpath:test-key.pem");
52+
PemSslStore store = new LoadedPemSslStore(details).withAlias("alias");
53+
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
54+
}
55+
56+
@Test
57+
void withPasswordIsLazy() {
58+
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
59+
.withPrivateKey("classpath:test-key.pem");
60+
PemSslStore store = new LoadedPemSslStore(details).withPassword("password");
61+
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
62+
}
63+
4864
}

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,10 @@
2727
import org.springframework.util.function.ThrowingConsumer;
2828

2929
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.BDDMockito.given;
31+
import static org.mockito.BDDMockito.then;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.times;
3034

3135
/**
3236
* Tests for {@link PemSslStoreBundle}.
@@ -215,6 +219,18 @@ void createWithPemSslStoreCreatesInstance() {
215219
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("ssl"));
216220
}
217221

222+
@Test
223+
void storeCreationIsLazy() {
224+
PemSslStore pemSslStore = mock(PemSslStore.class);
225+
PemSslStoreBundle bundle = new PemSslStoreBundle(pemSslStore, pemSslStore);
226+
given(pemSslStore.certificates()).willReturn(PemContent.of(CERTIFICATE).getCertificates());
227+
then(pemSslStore).shouldHaveNoInteractions();
228+
bundle.getKeyStore();
229+
then(pemSslStore).should().certificates();
230+
bundle.getTrustStore();
231+
then(pemSslStore).should(times(2)).certificates();
232+
}
233+
218234
private Consumer<KeyStore> storeContainingCert(String keyAlias) {
219235
return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
220236
}

0 commit comments

Comments
 (0)