diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java
index 29baa16e69e..e56ab890162 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java
@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.ssl;
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
+import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleKey;
import org.springframework.boot.ssl.SslManagerBundle;
@@ -27,6 +28,7 @@ import org.springframework.boot.ssl.jks.JksSslStoreDetails;
import org.springframework.boot.ssl.pem.PemSslStore;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
@@ -97,18 +99,31 @@ public final class PropertiesSslBundle implements SslBundle {
* @return an {@link SslBundle} instance
*/
public static SslBundle get(PemSslBundleProperties properties) {
- PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore());
+ return get(properties, new ApplicationResourceLoader());
+ }
+
+ /**
+ * Get an {@link SslBundle} for the given {@link PemSslBundleProperties}.
+ * @param properties the source properties
+ * @param resourceLoader the resource loader used to load content
+ * @return an {@link SslBundle} instance
+ * @since 3.3.5
+ */
+ public static SslBundle get(PemSslBundleProperties properties, ResourceLoader resourceLoader) {
+ PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore(), resourceLoader);
if (keyStore != null) {
keyStore = keyStore.withAlias(properties.getKey().getAlias())
.withPassword(properties.getKey().getPassword());
}
- PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore());
+ PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore(), resourceLoader);
SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore);
return new PropertiesSslBundle(storeBundle, properties);
}
- private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) {
- PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties));
+ private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties,
+ ResourceLoader resourceLoader) {
+ PemSslStoreDetails details = asPemSslStoreDetails(properties);
+ PemSslStore pemSslStore = PemSslStore.load(details, resourceLoader);
if (properties.isVerifyKeys()) {
CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey());
Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()),
@@ -128,14 +143,25 @@ public final class PropertiesSslBundle implements SslBundle {
* @return an {@link SslBundle} instance
*/
public static SslBundle get(JksSslBundleProperties properties) {
- SslStoreBundle storeBundle = asSslStoreBundle(properties);
+ return get(properties, new ApplicationResourceLoader());
+ }
+
+ /**
+ * Get an {@link SslBundle} for the given {@link JksSslBundleProperties}.
+ * @param properties the source properties
+ * @param resourceLoader the resource loader used to load content
+ * @return an {@link SslBundle} instance
+ * @since 3.3.5
+ */
+ public static SslBundle get(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
+ SslStoreBundle storeBundle = asSslStoreBundle(properties, resourceLoader);
return new PropertiesSslBundle(storeBundle, properties);
}
- private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) {
+ private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties, ResourceLoader resourceLoader) {
JksSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore());
JksSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore());
- return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
+ return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails, resourceLoader);
}
private static JksSslStoreDetails asStoreDetails(JksSslBundleProperties.Store properties) {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java
index 1348f16b37b..c53356a71e1 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +21,12 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundleRegistry;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ResourceLoader;
/**
* {@link EnableAutoConfiguration Auto-configuration} for SSL.
@@ -36,9 +38,12 @@ import org.springframework.context.annotation.Bean;
@EnableConfigurationProperties(SslProperties.class)
public class SslAutoConfiguration {
+ private final ApplicationResourceLoader resourceLoader;
+
private final SslProperties sslProperties;
- SslAutoConfiguration(SslProperties sslProperties) {
+ SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
+ this.resourceLoader = new ApplicationResourceLoader(resourceLoader.getClassLoader());
this.sslProperties = sslProperties;
}
@@ -49,7 +54,7 @@ public class SslAutoConfiguration {
@Bean
SslPropertiesBundleRegistrar sslPropertiesSslBundleRegistrar(FileWatcher fileWatcher) {
- return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher);
+ return new SslPropertiesBundleRegistrar(this.sslProperties, fileWatcher, this.resourceLoader);
}
@Bean
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java
index c8f595b0d25..6687e81a663 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java
@@ -21,12 +21,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleRegistry;
+import org.springframework.core.io.ResourceLoader;
/**
* A {@link SslBundleRegistrar} that registers SSL bundles based
@@ -42,9 +44,12 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar {
private final FileWatcher fileWatcher;
- SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher) {
+ private final ResourceLoader resourceLoader;
+
+ SslPropertiesBundleRegistrar(SslProperties properties, FileWatcher fileWatcher, ResourceLoader resourceLoader) {
this.properties = properties.getBundle();
this.fileWatcher = fileWatcher;
+ this.resourceLoader = resourceLoader;
}
@Override
@@ -54,9 +59,9 @@ class SslPropertiesBundleRegistrar implements SslBundleRegistrar {
}
private
void registerBundles(SslBundleRegistry registry, Map properties,
- Function bundleFactory, Function, Set> watchedPaths) {
+ BiFunction bundleFactory, Function, Set> watchedPaths) {
properties.forEach((bundleName, bundleProperties) -> {
- Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties);
+ Supplier bundleSupplier = () -> bundleFactory.apply(bundleProperties, this.resourceLoader);
try {
registry.registerBundle(bundleName, bundleSupplier.get());
if (bundleProperties.isReloadOnUpdate()) {
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java
index d6b770a3d92..66d1c6d1d66 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,15 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ssl.SslBundle;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
/**
* Tests for {@link PropertiesSslBundle}.
@@ -137,6 +142,22 @@ class PropertiesSslBundleTests {
.withMessageContaining("Private key in keystore matches none of the certificates");
}
+ @Test
+ void getWithResourceLoader() {
+ PemSslBundleProperties properties = new PemSslBundleProperties();
+ properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
+ properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
+ properties.getKeystore().setVerifyKeys(true);
+ properties.getKey().setAlias("test-alias");
+ ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
+ SslBundle bundle = PropertiesSslBundle.get(properties, resourceLoader);
+ assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias"));
+ then(resourceLoader).should(atLeastOnce())
+ .getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
+ then(resourceLoader).should(atLeastOnce())
+ .getResource("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
+ }
+
private Consumer storeContainingCertAndKey(String keyAlias) {
return ThrowingConsumer.of((keyStore) -> {
assertThat(keyStore).isNotNull();
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java
index 759bb474609..dafabd8801b 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrarTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
+import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundleRegistry;
+import org.springframework.core.io.DefaultResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -31,6 +33,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
/**
@@ -44,6 +48,8 @@ class SslPropertiesBundleRegistrarTests {
private FileWatcher fileWatcher;
+ private DefaultResourceLoader resourceLoader;
+
private SslProperties properties;
private SslBundleRegistry registry;
@@ -52,7 +58,8 @@ class SslPropertiesBundleRegistrarTests {
void setUp() {
this.properties = new SslProperties();
this.fileWatcher = Mockito.mock(FileWatcher.class);
- this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher);
+ this.resourceLoader = spy(new DefaultResourceLoader());
+ this.registrar = new SslPropertiesBundleRegistrar(this.properties, this.fileWatcher, this.resourceLoader);
this.registry = Mockito.mock(SslBundleRegistry.class);
}
@@ -85,6 +92,21 @@ class SslPropertiesBundleRegistrarTests {
.watch(assertArg((set) -> pathEndingWith(set, "rsa-cert.pem", "rsa-key.pem")), any());
}
+ @Test
+ void shouldUseResourceLoader() {
+ PemSslBundleProperties pem = new PemSslBundleProperties();
+ pem.getTruststore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
+ pem.getTruststore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
+ this.properties.getBundle().getPem().put("bundle1", pem);
+ DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry();
+ this.registrar.registerBundles(registry);
+ registry.getBundle("bundle1").createSslContext();
+ then(this.resourceLoader).should(atLeastOnce())
+ .getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-cert.pem");
+ then(this.resourceLoader).should(atLeastOnce())
+ .getResource("classpath:org/springframework/boot/autoconfigure/ssl/ed25519-key.pem");
+ }
+
@Test
void shouldFailIfPemKeystoreCertificateIsEmbedded() {
PemSslBundleProperties pem = new PemSslBundleProperties();
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/jks/JksSslStoreBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/jks/JksSslStoreBundle.java
index c5b5c510d56..7bc6b5f78d3 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/jks/JksSslStoreBundle.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/jks/JksSslStoreBundle.java
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.ssl.SslStoreBundle;
-import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -45,6 +45,8 @@ public class JksSslStoreBundle implements SslStoreBundle {
private final JksSslStoreDetails keyStoreDetails;
+ private final ResourceLoader resourceLoader;
+
private final Supplier keyStore;
private final Supplier trustStore;
@@ -55,8 +57,22 @@ public class JksSslStoreBundle implements SslStoreBundle {
* @param trustStoreDetails the trust store details
*/
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
+ this(keyStoreDetails, trustStoreDetails, new ApplicationResourceLoader());
+ }
+
+ /**
+ * Create a new {@link JksSslStoreBundle} instance.
+ * @param keyStoreDetails the key store details
+ * @param trustStoreDetails the trust store details
+ * @param resourceLoader the resource loader used to load content
+ * @since 3.3.5
+ */
+ public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails,
+ ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.keyStoreDetails = keyStoreDetails;
- this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", this.keyStoreDetails));
+ this.resourceLoader = resourceLoader;
+ this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", keyStoreDetails));
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", trustStoreDetails));
}
@@ -116,8 +132,7 @@ public class JksSslStoreBundle implements SslStoreBundle {
private void loadKeyStore(KeyStore store, String location, char[] password) {
Assert.state(StringUtils.hasText(location), () -> "Location must not be empty or null");
try {
- Resource resource = new ApplicationResourceLoader().getResource(location);
- try (InputStream stream = resource.getInputStream()) {
+ try (InputStream stream = this.resourceLoader.getResource(location).getInputStream()) {
store.load(stream, password);
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/LoadedPemSslStore.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/LoadedPemSslStore.java
index 5f001421f57..950cb9bdaeb 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/LoadedPemSslStore.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/LoadedPemSslStore.java
@@ -23,6 +23,7 @@ import java.security.cert.X509Certificate;
import java.util.List;
import java.util.function.Supplier;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.function.SingletonSupplier;
@@ -38,15 +39,19 @@ final class LoadedPemSslStore implements PemSslStore {
private final PemSslStoreDetails details;
+ private final ResourceLoader resourceLoader;
+
private final Supplier> certificatesSupplier;
private final Supplier privateKeySupplier;
- LoadedPemSslStore(PemSslStoreDetails details) {
+ LoadedPemSslStore(PemSslStoreDetails details, ResourceLoader resourceLoader) {
Assert.notNull(details, "Details must not be null");
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.details = details;
- this.certificatesSupplier = supplier(() -> loadCertificates(details));
- this.privateKeySupplier = supplier(() -> loadPrivateKey(details));
+ this.resourceLoader = resourceLoader;
+ this.certificatesSupplier = supplier(() -> loadCertificates(details, resourceLoader));
+ this.privateKeySupplier = supplier(() -> loadPrivateKey(details, resourceLoader));
}
private static Supplier supplier(ThrowingSupplier supplier) {
@@ -57,8 +62,9 @@ final class LoadedPemSslStore implements PemSslStore {
return new UncheckedIOException(message, (IOException) cause);
}
- private static List loadCertificates(PemSslStoreDetails details) throws IOException {
- PemContent pemContent = PemContent.load(details.certificates());
+ private static List loadCertificates(PemSslStoreDetails details, ResourceLoader resourceLoader)
+ throws IOException {
+ PemContent pemContent = PemContent.load(details.certificates(), resourceLoader);
if (pemContent == null) {
return null;
}
@@ -67,8 +73,9 @@ final class LoadedPemSslStore implements PemSslStore {
return certificates;
}
- private static PrivateKey loadPrivateKey(PemSslStoreDetails details) throws IOException {
- PemContent pemContent = PemContent.load(details.privateKey());
+ private static PrivateKey loadPrivateKey(PemSslStoreDetails details, ResourceLoader resourceLoader)
+ throws IOException {
+ PemContent pemContent = PemContent.load(details.privateKey(), resourceLoader);
return (pemContent != null) ? pemContent.getPrivateKey(details.privateKeyPassword()) : null;
}
@@ -99,12 +106,12 @@ final class LoadedPemSslStore implements PemSslStore {
@Override
public PemSslStore withAlias(String alias) {
- return new LoadedPemSslStore(this.details.withAlias(alias));
+ return new LoadedPemSslStore(this.details.withAlias(alias), this.resourceLoader);
}
@Override
public PemSslStore withPassword(String password) {
- return new LoadedPemSslStore(this.details.withPassword(password));
+ return new LoadedPemSslStore(this.details.withPassword(password), this.resourceLoader);
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java
index 3a7e08e43d7..cadb6b215ad 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java
@@ -29,8 +29,7 @@ import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
-import org.springframework.boot.io.ApplicationResourceLoader;
-import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
@@ -108,10 +107,11 @@ public final class PemContent {
* Load {@link PemContent} from the given content (either the PEM content itself or a
* reference to the resource to load).
* @param content the content to load
+ * @param resourceLoader the resource loader used to load content
* @return a new {@link PemContent} instance
* @throws IOException on IO error
*/
- static PemContent load(String content) throws IOException {
+ static PemContent load(String content, ResourceLoader resourceLoader) throws IOException {
if (content == null) {
return null;
}
@@ -119,8 +119,7 @@ public final class PemContent {
return new PemContent(content);
}
try {
- Resource resource = new ApplicationResourceLoader().getResource(content);
- return load(resource.getInputStream());
+ return load(resourceLoader.getResource(content).getInputStream());
}
catch (IOException | UncheckedIOException ex) {
throw new IOException("Error reading certificate or key from file '%s'".formatted(content), ex);
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStore.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStore.java
index d58fc9a71be..7081aba6d16 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStore.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStore.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
+import org.springframework.boot.io.ApplicationResourceLoader;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
/**
@@ -93,10 +95,22 @@ public interface PemSslStore {
* @return a loaded {@link PemSslStore} or {@code null}.
*/
static PemSslStore load(PemSslStoreDetails details) {
+ return load(details, new ApplicationResourceLoader());
+ }
+
+ /**
+ * Return a {@link PemSslStore} instance loaded using the given
+ * {@link PemSslStoreDetails}.
+ * @param details the PEM store details
+ * @param resourceLoader the resource loader used to load content
+ * @return a loaded {@link PemSslStore} or {@code null}.
+ * @since 3.3.5
+ */
+ static PemSslStore load(PemSslStoreDetails details, ResourceLoader resourceLoader) {
if (details == null || details.isEmpty()) {
return null;
}
- return new LoadedPemSslStore(details);
+ return new LoadedPemSslStore(details, resourceLoader);
}
/**
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/jks/JksSslStoreBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/jks/JksSslStoreBundleTests.java
index c081224dec4..2e6fd4b54e0 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/jks/JksSslStoreBundleTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/jks/JksSslStoreBundleTests.java
@@ -26,11 +26,16 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.web.embedded.test.MockPkcs11Security;
+import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
/**
* Tests for {@link JksSslStoreBundle}.
@@ -163,6 +168,16 @@ class JksSslStoreBundleTests {
.withMessageContaining("does-not-exist.p12");
}
+ @Test
+ void usesResourceLoader() {
+ JksSslStoreDetails keyStoreDetails = null;
+ JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails("jks", null, "classpath:test.jks", "secret");
+ ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
+ JksSslStoreBundle bundle = new JksSslStoreBundle(keyStoreDetails, trustStoreDetails, resourceLoader);
+ assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("jks", "test-alias", "password"));
+ then(resourceLoader).should(atLeastOnce()).getResource("classpath:test.jks");
+ }
+
private Consumer storeContainingCertAndKey(String keyAlias, String keyPassword) {
return storeContainingCertAndKey(KeyStore.getDefaultType(), keyAlias, keyPassword);
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/LoadedPemSslStoreTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/LoadedPemSslStoreTests.java
index f78b777921c..c590a721cef 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/LoadedPemSslStoreTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/LoadedPemSslStoreTests.java
@@ -20,7 +20,14 @@ import java.io.UncheckedIOException;
import org.junit.jupiter.api.Test;
+import org.springframework.boot.io.ApplicationResourceLoader;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
/**
* Tests for {@link LoadedPemSslStore}.
@@ -33,7 +40,7 @@ class LoadedPemSslStoreTests {
void certificatesAreLoadedLazily() {
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
.withPrivateKey("classpath:test-key.pem");
- LoadedPemSslStore store = new LoadedPemSslStore(details);
+ LoadedPemSslStore store = new LoadedPemSslStore(details, new ApplicationResourceLoader());
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
}
@@ -41,7 +48,7 @@ class LoadedPemSslStoreTests {
void privateKeyIsLoadedLazily() {
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
.withPrivateKey("classpath:missing-test-key.pem");
- LoadedPemSslStore store = new LoadedPemSslStore(details);
+ LoadedPemSslStore store = new LoadedPemSslStore(details, new ApplicationResourceLoader());
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::privateKey);
}
@@ -49,7 +56,7 @@ class LoadedPemSslStoreTests {
void withAliasIsLazy() {
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
.withPrivateKey("classpath:test-key.pem");
- PemSslStore store = new LoadedPemSslStore(details).withAlias("alias");
+ PemSslStore store = new LoadedPemSslStore(details, new ApplicationResourceLoader()).withAlias("alias");
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
}
@@ -57,8 +64,17 @@ class LoadedPemSslStoreTests {
void withPasswordIsLazy() {
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
.withPrivateKey("classpath:test-key.pem");
- PemSslStore store = new LoadedPemSslStore(details).withPassword("password");
+ PemSslStore store = new LoadedPemSslStore(details, new ApplicationResourceLoader()).withPassword("password");
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
}
+ @Test
+ void usesResourceLoader() {
+ PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:test-cert.pem");
+ ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
+ LoadedPemSslStore store = new LoadedPemSslStore(details, resourceLoader);
+ store.certificates();
+ then(resourceLoader).should(atLeastOnce()).getResource("classpath:test-cert.pem");
+ }
+
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemContentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemContentTests.java
index fac38bc5fd5..9387362aa7c 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemContentTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemContentTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,16 @@ import java.util.List;
import org.junit.jupiter.api.Test;
+import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
/**
* Tests for {@link PemContent}.
@@ -46,7 +52,8 @@ class PemContentTests {
@Test
void getCertificateReturnsCertificates() throws Exception {
- PemContent content = PemContent.load(contentFromClasspath("/test-cert-chain.pem"));
+ PemContent content = PemContent.load(contentFromClasspath("/test-cert-chain.pem"),
+ new ApplicationResourceLoader());
List certificates = content.getCertificates();
assertThat(certificates).isNotNull();
assertThat(certificates).hasSize(2);
@@ -63,8 +70,8 @@ class PemContentTests {
@Test
void getPrivateKeyReturnsPrivateKey() throws Exception {
- PemContent content = PemContent
- .load(contentFromClasspath("/org/springframework/boot/web/server/pkcs8/dsa.key"));
+ PemContent content = PemContent.load(contentFromClasspath("/org/springframework/boot/web/server/pkcs8/dsa.key"),
+ new ApplicationResourceLoader());
PrivateKey privateKey = content.getPrivateKey();
assertThat(privateKey).isNotNull();
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
@@ -88,7 +95,7 @@ class PemContentTests {
@Test
void loadWithStringWhenContentIsNullReturnsNull() throws Exception {
- assertThat(PemContent.load((String) null)).isNull();
+ assertThat(PemContent.load((String) null, new ApplicationResourceLoader())).isNull();
}
@Test
@@ -111,19 +118,19 @@ class PemContentTests {
+lGuHKdhNOVW9CmqPD1y76o6c8PQKuF7KZEoY2jvy3GeIfddBvqXgZ4PbWvFz1jO
32C9XWHwRA4=
-----END CERTIFICATE-----""";
- assertThat(PemContent.load(content)).hasToString(content);
+ assertThat(PemContent.load(content, new ApplicationResourceLoader())).hasToString(content);
}
@Test
void loadWithStringWhenClasspathLocationReturnsContent() throws IOException {
- String actual = PemContent.load("classpath:test-cert.pem").toString();
+ String actual = PemContent.load("classpath:test-cert.pem", new ApplicationResourceLoader()).toString();
String expected = contentFromClasspath("test-cert.pem");
assertThat(actual).isEqualTo(expected);
}
@Test
void loadWithStringWhenFileLocationReturnsContent() throws IOException {
- String actual = PemContent.load("src/test/resources/test-cert.pem").toString();
+ String actual = PemContent.load("src/test/resources/test-cert.pem", new ApplicationResourceLoader()).toString();
String expected = contentFromClasspath("test-cert.pem");
assertThat(actual).isEqualTo(expected);
}
@@ -136,6 +143,13 @@ class PemContentTests {
assertThat(actual).isEqualTo(expected);
}
+ @Test
+ void loadWithResourceLoaderUsesResourceLoader() throws IOException {
+ ResourceLoader resourceLoader = spy(new DefaultResourceLoader());
+ PemContent.load("classpath:test-cert.pem", resourceLoader);
+ then(resourceLoader).should(atLeastOnce()).getResource("classpath:test-cert.pem");
+ }
+
@Test
void ofWhenNullReturnsNull() {
assertThat(PemContent.of(null)).isNull();