Skip to content

Commit 4996721

Browse files
committed
Use context class loader when loading auto-configured SSL bundles
Update `SslAutoConfiguration` to the `ApplicationContext` class loader when loading SSL resources. Prior to this commit, the thread context class loader was used to load resources which could be incorrect. Specifically, when using a `ForkJoinPool` the thread context classloader defaults to the JRE `AppClassLoader` which does not include uber jar content. The underlying `JksSslStoreBundle` class and `PemSslStore.load(...)` method have been updated so support using a provided `ResourceLoader`. Fixes gh-42468
1 parent 61fbb12 commit 4996721

File tree

12 files changed

+207
-48
lines changed

12 files changed

+207
-48
lines changed

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

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

1919
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
20+
import org.springframework.boot.io.ApplicationResourceLoader;
2021
import org.springframework.boot.ssl.SslBundle;
2122
import org.springframework.boot.ssl.SslBundleKey;
2223
import org.springframework.boot.ssl.SslManagerBundle;
@@ -27,6 +28,7 @@
2728
import org.springframework.boot.ssl.pem.PemSslStore;
2829
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
2930
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
31+
import org.springframework.core.io.ResourceLoader;
3032
import org.springframework.core.style.ToStringCreator;
3133
import org.springframework.util.Assert;
3234

@@ -97,18 +99,31 @@ public SslManagerBundle getManagers() {
9799
* @return an {@link SslBundle} instance
98100
*/
99101
public static SslBundle get(PemSslBundleProperties properties) {
100-
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore());
102+
return get(properties, new ApplicationResourceLoader());
103+
}
104+
105+
/**
106+
* Get an {@link SslBundle} for the given {@link PemSslBundleProperties}.
107+
* @param properties the source properties
108+
* @param resourceLoader the resource loader used to load content
109+
* @return an {@link SslBundle} instance
110+
* @since 3.3.5
111+
*/
112+
public static SslBundle get(PemSslBundleProperties properties, ResourceLoader resourceLoader) {
113+
PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore(), resourceLoader);
101114
if (keyStore != null) {
102115
keyStore = keyStore.withAlias(properties.getKey().getAlias())
103116
.withPassword(properties.getKey().getPassword());
104117
}
105-
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore());
118+
PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore(), resourceLoader);
106119
SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore);
107120
return new PropertiesSslBundle(storeBundle, properties);
108121
}
109122

110-
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) {
111-
PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties));
123+
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties,
124+
ResourceLoader resourceLoader) {
125+
PemSslStoreDetails details = asPemSslStoreDetails(properties);
126+
PemSslStore pemSslStore = PemSslStore.load(details, resourceLoader);
112127
if (properties.isVerifyKeys()) {
113128
CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey());
114129
Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()),
@@ -128,14 +143,25 @@ private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.St
128143
* @return an {@link SslBundle} instance
129144
*/
130145
public static SslBundle get(JksSslBundleProperties properties) {
131-
SslStoreBundle storeBundle = asSslStoreBundle(properties);
146+
return get(properties, new ApplicationResourceLoader());
147+
}
148+
149+
/**
150+
* Get an {@link SslBundle} for the given {@link JksSslBundleProperties}.
151+
* @param properties the source properties
152+
* @param resourceLoader the resource loader used to load content
153+
* @return an {@link SslBundle} instance
154+
* @since 3.3.5
155+
*/
156+
public static SslBundle get(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
157+
SslStoreBundle storeBundle = asSslStoreBundle(properties, resourceLoader);
132158
return new PropertiesSslBundle(storeBundle, properties);
133159
}
134160

135-
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) {
161+
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
136162
JksSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
137163
JksSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
138-
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
164+
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails, resourceLoader);
139165
}
140166

141167
private static JksSslStoreDetails asStoreDetails(JksSslBundleProperties.Store properties) {

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

Lines changed: 8 additions & 3 deletions
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.
@@ -21,10 +21,12 @@
2121
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2323
import org.springframework.boot.context.properties.EnableConfigurationProperties;
24+
import org.springframework.boot.io.ApplicationResourceLoader;
2425
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
2526
import org.springframework.boot.ssl.SslBundleRegistry;
2627
import org.springframework.boot.ssl.SslBundles;
2728
import org.springframework.context.annotation.Bean;
29+
import org.springframework.core.io.ResourceLoader;
2830

2931
/**
3032
* {@link EnableAutoConfiguration Auto-configuration} for SSL.
@@ -36,9 +38,12 @@
3638
@EnableConfigurationProperties(SslProperties.class)
3739
public class SslAutoConfiguration {
3840

41+
private final ApplicationResourceLoader resourceLoader;
42+
3943
private final SslProperties sslProperties;
4044

41-
SslAutoConfiguration(SslProperties sslProperties) {
45+
SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
46+
this.resourceLoader = new ApplicationResourceLoader(resourceLoader.getClassLoader());
4247
this.sslProperties = sslProperties;
4348
}
4449

@@ -49,7 +54,7 @@ FileWatcher fileWatcher() {
4954

5055
@Bean
5156
SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(FileWatcher fileWatcher) {
52-
return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher);
57+
return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher, this.resourceLoader);
5358
}
5459

5560
@Bean

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.Set;
24+
import java.util.function.BiFunction;
2425
import java.util.function.Function;
2526
import java.util.function.Supplier;
2627
import java.util.stream.Collectors;
2728

2829
import org.springframework.boot.ssl.SslBundle;
2930
import org.springframework.boot.ssl.SslBundleRegistry;
31+
import org.springframework.core.io.ResourceLoader;
3032

3133
/**
3234
* A {@link SslBundleRegistrar} that registers SSL bundles based
@@ -42,9 +44,12 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar {
4244

4345
private final FileWatcher fileWatcher;
4446

45-
SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher) {
47+
private final ResourceLoader resourceLoader;
48+
49+
SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher, ResourceLoader resourceLoader) {
4650
this.properties = properties.getBundle();
4751
this.fileWatcher = fileWatcher;
52+
this.resourceLoader = resourceLoader;
4853
}
4954

5055
@Override
@@ -54,9 +59,9 @@ public void registerBundles(SslBundleRegistry registry) {
5459
}
5560

5661
private <P extends SslBundleProperties> void registerBundles(SslBundleRegistry registry, Map<String, P> properties,
57-
Function<P, SslBundle> bundleFactory, Function<Bundle<P>, Set<Path>> watchedPaths) {
62+
BiFunction<P, ResourceLoader, SslBundle> bundleFactory, Function<Bundle<P>, Set<Path>> watchedPaths) {
5863
properties.forEach((bundleName, bundleProperties) -> {
59-
Supplier<SslBundle> bundleSupplier = () -> bundleFactory.apply(bundleProperties);
64+
Supplier<SslBundle> bundleSupplier = () -> bundleFactory.apply(bundleProperties, this.resourceLoader);
6065
try {
6166
registry.registerBundle(bundleName, bundleSupplier.get());
6267
if (bundleProperties.isReloadOnUpdate()) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java

Lines changed: 22 additions & 1 deletion
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.
@@ -25,10 +25,15 @@
2525
import org.junit.jupiter.api.Test;
2626

2727
import org.springframework.boot.ssl.SslBundle;
28+
import org.springframework.core.io.DefaultResourceLoader;
29+
import org.springframework.core.io.ResourceLoader;
2830
import org.springframework.util.function.ThrowingConsumer;
2931

3032
import static org.assertj.core.api.Assertions.assertThat;
3133
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
34+
import static org.mockito.BDDMockito.then;
35+
import static org.mockito.Mockito.atLeastOnce;
36+
import static org.mockito.Mockito.spy;
3237

3338
/**
3439
* Tests for {@link PropertiesSslBundle}.
@@ -137,6 +142,22 @@ void getWithPemSslBundlePropertiesWhenVerifyKeyStoreWithNoMatchThrowsException()
137142
.withMessageContaining("Private key in keystore matches none of the certificates");
138143
}
139144

145+
@Test
146+
void getWithResourceLoader() {
147+
PemSslBundleProperties properties = new PemSslBundleProperties();
148+
properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
149+
properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
150+
properties.getKeystore().setVerifyKeys(true);
151+
properties.getKey().setAlias("test-alias");
152+
ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
153+
SslBundle bundle = PropertiesSslBundle.get(properties, resourceLoader);
154+
assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias"));
155+
then(resourceLoader).should(atLeastOnce())
156+
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
157+
then(resourceLoader).should(atLeastOnce())
158+
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
159+
}
160+
140161
private Consumer<KeyStore> storeContainingCertAndKey(String keyAlias) {
141162
return ThrowingConsumer.of((keyStore) -> {
142163
assertThat(keyStore).isNotNull();

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java

Lines changed: 24 additions & 2 deletions
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.
@@ -23,14 +23,18 @@
2323
import org.junit.jupiter.api.Test;
2424
import org.mockito.Mockito;
2525

26+
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
2627
import org.springframework.boot.ssl.SslBundleRegistry;
28+
import org.springframework.core.io.DefaultResourceLoader;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
2931
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
3032
import static org.mockito.ArgumentMatchers.any;
3133
import static org.mockito.ArgumentMatchers.assertArg;
3234
import static org.mockito.ArgumentMatchers.eq;
3335
import static org.mockito.BDDMockito.then;
36+
import static org.mockito.Mockito.atLeastOnce;
37+
import static org.mockito.Mockito.spy;
3438
import static org.mockito.Mockito.times;
3539

3640
/**
@@ -44,6 +48,8 @@ class SslPropertiesBundleRegistrarTests {
4448

4549
private FileWatcher fileWatcher;
4650

51+
private DefaultResourceLoader resourceLoader;
52+
4753
private SslProperties properties;
4854

4955
private SslBundleRegistry registry;
@@ -52,7 +58,8 @@ class SslPropertiesBundleRegistrarTests {
5258
void setUp() {
5359
this.properties = new SslProperties();
5460
this.fileWatcher = Mockito.mock(FileWatcher.class);
55-
this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher);
61+
this.resourceLoader = spy(new DefaultResourceLoader());
62+
this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher, this.resourceLoader);
5663
this.registry = Mockito.mock(SslBundleRegistry.class);
5764
}
5865

@@ -85,6 +92,21 @@ void shouldWatchPemBundles() {
8592
.watch(assertArg((set) -> pathEndingWith(set, "rsa-cert.pem", "rsa-key.pem")), any());
8693
}
8794

95+
@Test
96+
void shouldUseResourceLoader() {
97+
PemSslBundleProperties pem = new PemSslBundleProperties();
98+
pem.getTruststore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
99+
pem.getTruststore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
100+
this.properties.getBundle().getPem().put("bundle1", pem);
101+
DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry();
102+
this.registrar.registerBundles(registry);
103+
registry.getBundle("bundle1").createSslContext();
104+
then(this.resourceLoader).should(atLeastOnce())
105+
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
106+
then(this.resourceLoader).should(atLeastOnce())
107+
.getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
108+
}
109+
88110
@Test
89111
void shouldFailIfPemKeystoreCertificateIsEmbedded() {
90112
PemSslBundleProperties pem = new PemSslBundleProperties();

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import org.springframework.boot.io.ApplicationResourceLoader;
2929
import org.springframework.boot.ssl.SslStoreBundle;
30-
import org.springframework.core.io.Resource;
30+
import org.springframework.core.io.ResourceLoader;
3131
import org.springframework.core.style.ToStringCreator;
3232
import org.springframework.util.Assert;
3333
import org.springframework.util.StringUtils;
@@ -45,6 +45,8 @@ public class JksSslStoreBundle implements SslStoreBundle {
4545

4646
private final JksSslStoreDetails keyStoreDetails;
4747

48+
private final ResourceLoader resourceLoader;
49+
4850
private final Supplier<KeyStore> keyStore;
4951

5052
private final Supplier<KeyStore> trustStore;
@@ -55,8 +57,22 @@ public class JksSslStoreBundle implements SslStoreBundle {
5557
* @param trustStoreDetails the trust store details
5658
*/
5759
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
60+
this(keyStoreDetails, trustStoreDetails, new ApplicationResourceLoader());
61+
}
62+
63+
/**
64+
* Create a new {@link JksSslStoreBundle} instance.
65+
* @param keyStoreDetails the key store details
66+
* @param trustStoreDetails the trust store details
67+
* @param resourceLoader the resource loader used to load content
68+
* @since 3.3.5
69+
*/
70+
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails,
71+
ResourceLoader resourceLoader) {
72+
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
5873
this.keyStoreDetails = keyStoreDetails;
59-
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", this.keyStoreDetails));
74+
this.resourceLoader = resourceLoader;
75+
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", keyStoreDetails));
6076
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", trustStoreDetails));
6177
}
6278

@@ -116,8 +132,7 @@ private void loadHardwareKeyStore(KeyStore store, String location, char[] passwo
116132
private void loadKeyStore(KeyStore store, String location, char[] password) {
117133
Assert.state(StringUtils.hasText(location), () -> "Location must not be empty or null");
118134
try {
119-
Resource resource = new ApplicationResourceLoader().getResource(location);
120-
try (InputStream stream = resource.getInputStream()) {
135+
try (InputStream stream = this.resourceLoader.getResource(location).getInputStream()) {
121136
store.load(stream, password);
122137
}
123138
}

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.function.Supplier;
2525

26+
import org.springframework.core.io.ResourceLoader;
2627
import org.springframework.util.Assert;
2728
import org.springframework.util.CollectionUtils;
2829
import org.springframework.util.function.SingletonSupplier;
@@ -38,15 +39,19 @@ final class LoadedPemSslStore implements PemSslStore {
3839

3940
private final PemSslStoreDetails details;
4041

42+
private final ResourceLoader resourceLoader;
43+
4144
private final Supplier<List<X509Certificate>> certificatesSupplier;
4245

4346
private final Supplier<PrivateKey> privateKeySupplier;
4447

45-
LoadedPemSslStore(PemSslStoreDetails details) {
48+
LoadedPemSslStore(PemSslStoreDetails details, ResourceLoader resourceLoader) {
4649
Assert.notNull(details, "Details must not be null");
50+
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
4751
this.details = details;
48-
this.certificatesSupplier = supplier(() -> loadCertificates(details));
49-
this.privateKeySupplier = supplier(() -> loadPrivateKey(details));
52+
this.resourceLoader = resourceLoader;
53+
this.certificatesSupplier = supplier(() -> loadCertificates(details, resourceLoader));
54+
this.privateKeySupplier = supplier(() -> loadPrivateKey(details, resourceLoader));
5055
}
5156

5257
private static <T> Supplier<T> supplier(ThrowingSupplier<T> supplier) {
@@ -57,8 +62,9 @@ private static UncheckedIOException asUncheckedIOException(String message, Excep
5762
return new UncheckedIOException(message, (IOException) cause);
5863
}
5964

60-
private static List<X509Certificate> loadCertificates(PemSslStoreDetails details) throws IOException {
61-
PemContent pemContent = PemContent.load(details.certificates());
65+
private static List<X509Certificate> loadCertificates(PemSslStoreDetails details, ResourceLoader resourceLoader)
66+
throws IOException {
67+
PemContent pemContent = PemContent.load(details.certificates(), resourceLoader);
6268
if (pemContent == null) {
6369
return null;
6470
}
@@ -67,8 +73,9 @@ private static List<X509Certificate> loadCertificates(PemSslStoreDetails details
6773
return certificates;
6874
}
6975

70-
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) throws IOException {
71-
PemContent pemContent = PemContent.load(details.privateKey());
76+
private static PrivateKey loadPrivateKey(PemSslStoreDetails details, ResourceLoader resourceLoader)
77+
throws IOException {
78+
PemContent pemContent = PemContent.load(details.privateKey(), resourceLoader);
7279
return (pemContent != null) ? pemContent.getPrivateKey(details.privateKeyPassword()) : null;
7380
}
7481

@@ -99,12 +106,12 @@ public PrivateKey privateKey() {
99106

100107
@Override
101108
public PemSslStore withAlias(String alias) {
102-
return new LoadedPemSslStore(this.details.withAlias(alias));
109+
return new LoadedPemSslStore(this.details.withAlias(alias), this.resourceLoader);
103110
}
104111

105112
@Override
106113
public PemSslStore withPassword(String password) {
107-
return new LoadedPemSslStore(this.details.withPassword(password));
114+
return new LoadedPemSslStore(this.details.withPassword(password), this.resourceLoader);
108115
}
109116

110117
}

0 commit comments

Comments
 (0)