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 a76f5c2fa2b..c16a5161e84 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 @@ -16,9 +16,6 @@ package org.springframework.boot.autoconfigure.ssl; -import java.io.IOException; -import java.io.UncheckedIOException; - import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleKey; @@ -99,23 +96,17 @@ public final class PropertiesSslBundle implements SslBundle { * @return an {@link SslBundle} instance */ public static SslBundle get(PemSslBundleProperties properties) { - try { - PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore()); - if (keyStore != null) { - keyStore = keyStore.withAlias(properties.getKey().getAlias()) - .withPassword(properties.getKey().getPassword()); - } - PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore()); - SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore); - return new PropertiesSslBundle(storeBundle, properties); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); + PemSslStore keyStore = getPemSslStore("keystore", properties.getKeystore()); + if (keyStore != null) { + keyStore = keyStore.withAlias(properties.getKey().getAlias()) + .withPassword(properties.getKey().getPassword()); } + PemSslStore trustStore = getPemSslStore("truststore", properties.getTruststore()); + SslStoreBundle storeBundle = new PemSslStoreBundle(keyStore, trustStore); + return new PropertiesSslBundle(storeBundle, properties); } - private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) - throws IOException { + private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties) { PemSslStore pemSslStore = PemSslStore.load(asPemSslStoreDetails(properties)); if (properties.isVerifyKeys()) { CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey()); 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 07f457a1b1f..5edacd360e6 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 @@ -17,12 +17,16 @@ package org.springframework.boot.ssl.pem; import java.io.IOException; +import java.io.UncheckedIOException; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.List; +import java.util.function.Supplier; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.function.SingletonSupplier; +import org.springframework.util.function.ThrowingSupplier; /** * {@link PemSslStore} loaded from {@link PemSslStoreDetails}. @@ -34,15 +38,23 @@ final class LoadedPemSslStore implements PemSslStore { private final PemSslStoreDetails details; - private final List certificates; + private final Supplier> certificatesSupplier; - private final PrivateKey privateKey; + private final Supplier privateKeySupplier; - LoadedPemSslStore(PemSslStoreDetails details) throws IOException { + LoadedPemSslStore(PemSslStoreDetails details) { Assert.notNull(details, "Details must not be null"); this.details = details; - this.certificates = loadCertificates(details); - this.privateKey = loadPrivateKey(details); + this.certificatesSupplier = supplier(() -> loadCertificates(details)); + this.privateKeySupplier = supplier(() -> loadPrivateKey(details)); + } + + private static Supplier supplier(ThrowingSupplier supplier) { + return SingletonSupplier.of(supplier.throwing(LoadedPemSslStore::asUncheckedIOException)); + } + + private static UncheckedIOException asUncheckedIOException(String message, Exception cause) { + return new UncheckedIOException(message, (IOException) cause); } private static List loadCertificates(PemSslStoreDetails details) throws IOException { @@ -77,12 +89,12 @@ final class LoadedPemSslStore implements PemSslStore { @Override public List certificates() { - return this.certificates; + return this.certificatesSupplier.get(); } @Override public PrivateKey privateKey() { - return this.privateKey; + return this.privateKeySupplier.get(); } } 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 e1ed146f3cd..d58fc9a71be 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 @@ -16,7 +16,6 @@ package org.springframework.boot.ssl.pem; -import java.io.IOException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; @@ -92,9 +91,8 @@ public interface PemSslStore { * {@link PemSslStoreDetails}. * @param details the PEM store details * @return a loaded {@link PemSslStore} or {@code null}. - * @throws IOException on IO error */ - static PemSslStore load(PemSslStoreDetails details) throws IOException { + static PemSslStore load(PemSslStoreDetails details) { if (details == null || details.isEmpty()) { return null; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java index 44c6e0fbff4..5834d2f84fc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java @@ -17,7 +17,6 @@ package org.springframework.boot.ssl.pem; import java.io.IOException; -import java.io.UncheckedIOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -66,13 +65,8 @@ public class PemSslStoreBundle implements SslStoreBundle { */ @Deprecated(since = "3.2.0", forRemoval = true) public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) { - try { - this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias); - this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } + this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias); + this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias); } /** 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 new file mode 100644 index 00000000000..b7bccce838a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/LoadedPemSslStoreTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.ssl.pem; + +import java.io.UncheckedIOException; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link LoadedPemSslStore}. + * + * @author Phillip Webb + */ +class LoadedPemSslStoreTests { + + @Test + void certificatesAreLoadedLazily() { + PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem") + .withPrivateKey("classpath:test-key.pem"); + LoadedPemSslStore store = new LoadedPemSslStore(details); + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates); + } + + @Test + void privateKeyIsLoadedLazily() { + PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:test-cert.pem") + .withPrivateKey("classpath:missing-test-key.pem"); + LoadedPemSslStore store = new LoadedPemSslStore(details); + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::privateKey); + } + +}