Skip to content

Commit 26dc140

Browse files
committed
Update LoadedPemSslStore to use lazy loading
Update `LoadedPemSslStore` so that it loads content lazily. This restores the behavior of Spring Boot 3.1 and allows bundles to be defined with files that don't exist as long as they are never accessed. Fixes gh-38659
1 parent bb37a86 commit 26dc140

File tree

5 files changed

+78
-35
lines changed

5 files changed

+78
-35
lines changed

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.boot.autoconfigure.ssl;
1818

19-
import java.io.IOException;
20-
import java.io.UncheckedIOException;
21-
2219
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
2320
import org.springframework.boot.ssl.SslBundle;
2421
import org.springframework.boot.ssl.SslBundleKey;
@@ -99,23 +96,17 @@ public SslManagerBundle getManagers() {
9996
* @return an {@link SslBundle} instance
10097
*/
10198
public static SslBundle get(PemSslBundleProperties properties) {
102-
try {
103-
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore());
104-
if (keyStore != null) {
105-
keyStore = keyStore.withAlias(properties.getKey().getAlias())
106-
.withPassword(properties.getKey().getPassword());
107-
}
108-
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore());
109-
SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore);
110-
return new PropertiesSslBundle(storeBundle, properties);
111-
}
112-
catch (IOException ex) {
113-
throw new UncheckedIOException(ex);
99+
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore());
100+
if (keyStore != null) {
101+
keyStore = keyStore.withAlias(properties.getKey().getAlias())
102+
.withPassword(properties.getKey().getPassword());
114103
}
104+
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore());
105+
SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore);
106+
return new PropertiesSslBundle(storeBundle, properties);
115107
}
116108

117-
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties)
118-
throws IOException {
109+
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) {
119110
PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties));
120111
if (properties.isVerifyKeys()) {
121112
CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey());

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package org.springframework.boot.ssl.pem;
1818

1919
import java.io.IOException;
20+
import java.io.UncheckedIOException;
2021
import java.security.PrivateKey;
2122
import java.security.cert.X509Certificate;
2223
import java.util.List;
24+
import java.util.function.Supplier;
2325

2426
import org.springframework.util.Assert;
2527
import org.springframework.util.CollectionUtils;
28+
import org.springframework.util.function.SingletonSupplier;
29+
import org.springframework.util.function.ThrowingSupplier;
2630

2731
/**
2832
* {@link PemSslStore} loaded from {@link PemSslStoreDetails}.
@@ -34,15 +38,23 @@ final class LoadedPemSslStore implements PemSslStore {
3438

3539
private final PemSslStoreDetails details;
3640

37-
private final List<X509Certificate> certificates;
41+
private final Supplier<List<X509Certificate>> certificatesSupplier;
3842

39-
private final PrivateKey privateKey;
43+
private final Supplier<PrivateKey> privateKeySupplier;
4044

41-
LoadedPemSslStore(PemSslStoreDetails details) throws IOException {
45+
LoadedPemSslStore(PemSslStoreDetails details) {
4246
Assert.notNull(details, "Details must not be null");
4347
this.details = details;
44-
this.certificates = loadCertificates(details);
45-
this.privateKey = loadPrivateKey(details);
48+
this.certificatesSupplier = supplier(() -> loadCertificates(details));
49+
this.privateKeySupplier = supplier(() -> loadPrivateKey(details));
50+
}
51+
52+
private static <T> Supplier<T> supplier(ThrowingSupplier<T> supplier) {
53+
return SingletonSupplier.of(supplier.throwing(LoadedPemSslStore::asUncheckedIOException));
54+
}
55+
56+
private static UncheckedIOException asUncheckedIOException(String message, Exception cause) {
57+
return new UncheckedIOException(message, (IOException) cause);
4658
}
4759

4860
private static List<X509Certificate> loadCertificates(PemSslStoreDetails details) throws IOException {
@@ -77,12 +89,12 @@ public String password() {
7789

7890
@Override
7991
public List<X509Certificate> certificates() {
80-
return this.certificates;
92+
return this.certificatesSupplier.get();
8193
}
8294

8395
@Override
8496
public PrivateKey privateKey() {
85-
return this.privateKey;
97+
return this.privateKeySupplier.get();
8698
}
8799

88100
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.ssl.pem;
1818

19-
import java.io.IOException;
2019
import java.security.KeyStore;
2120
import java.security.PrivateKey;
2221
import java.security.cert.X509Certificate;
@@ -92,9 +91,8 @@ default PemSslStore withPassword(String password) {
9291
* {@link PemSslStoreDetails}.
9392
* @param details the PEM store details
9493
* @return a loaded {@link PemSslStore} or {@code null}.
95-
* @throws IOException on IO error
9694
*/
97-
static PemSslStore load(PemSslStoreDetails details) throws IOException {
95+
static PemSslStore load(PemSslStoreDetails details) {
9896
if (details == null || details.isEmpty()) {
9997
return null;
10098
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.ssl.pem;
1818

1919
import java.io.IOException;
20-
import java.io.UncheckedIOException;
2120
import java.security.KeyStore;
2221
import java.security.KeyStoreException;
2322
import java.security.NoSuchAlgorithmException;
@@ -66,13 +65,8 @@ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails
6665
*/
6766
@Deprecated(since = "3.2.0", forRemoval = true)
6867
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
69-
try {
70-
this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias);
71-
this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias);
72-
}
73-
catch (IOException ex) {
74-
throw new UncheckedIOException(ex);
75-
}
68+
this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias);
69+
this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias);
7670
}
7771

7872
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.ssl.pem;
18+
19+
import java.io.UncheckedIOException;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
24+
25+
/**
26+
* Tests for {@link LoadedPemSslStore}.
27+
*
28+
* @author Phillip Webb
29+
*/
30+
class LoadedPemSslStoreTests {
31+
32+
@Test
33+
void certificatesAreLoadedLazily() {
34+
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
35+
.withPrivateKey("classpath:test-key.pem");
36+
LoadedPemSslStore store = new LoadedPemSslStore(details);
37+
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
38+
}
39+
40+
@Test
41+
void privateKeyIsLoadedLazily() {
42+
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
43+
.withPrivateKey("classpath:missing-test-key.pem");
44+
LoadedPemSslStore store = new LoadedPemSslStore(details);
45+
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::privateKey);
46+
}
47+
48+
}

0 commit comments

Comments
 (0)