diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslBundle.java index 218e44b68f6..711374d4de3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslBundle.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -16,9 +16,16 @@ package org.springframework.boot.ssl; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import org.springframework.core.style.ToStringCreator; import org.springframework.util.StringUtils; @@ -27,6 +34,7 @@ import org.springframework.util.StringUtils; * A bundle of trust material that can be used to establish an SSL connection. * * @author Scott Frederick + * @author Moritz Halbritter * @since 3.1.0 */ public interface SslBundle { @@ -174,4 +182,40 @@ public interface SslBundle { }; } + /** + * Factory method to create a new {@link SslBundle} which uses the system defaults. + * @return a new {@link SslBundle} instance + * @since 3.5.0 + */ + static SslBundle systemDefault() { + try { + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(null, null); + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + SSLContext sslContext = SSLContext.getDefault(); + return of(null, null, null, null, new SslManagerBundle() { + @Override + public KeyManagerFactory getKeyManagerFactory() { + return keyManagerFactory; + } + + @Override + public TrustManagerFactory getTrustManagerFactory() { + return trustManagerFactory; + } + + @Override + public SSLContext createSslContext(String protocol) { + return sslContext; + } + }); + } + catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException ex) { + throw new IllegalStateException("Could not initialize system default SslBundle: " + ex.getMessage(), ex); + } + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslOptions.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslOptions.java index 5ae5d952ff0..55cf4b72f54 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslOptions.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -38,7 +38,7 @@ public interface SslOptions { /** * {@link SslOptions} that returns {@code null} results. */ - SslOptions NONE = of((Set) null, (Set) null); + SslOptions NONE = of(null, (Set) null); /** * Return if any SSL options have been specified. 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 95e7ac99961..948da58824d 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 @@ -33,6 +33,7 @@ import java.util.stream.Collectors; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * PEM encoded content that can provide {@link X509Certificate certificates} and @@ -109,11 +110,11 @@ public final class PemContent { * 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 + * @return a new {@link PemContent} instance or {@code null} * @throws IOException on IO error */ static PemContent load(String content, ResourceLoader resourceLoader) throws IOException { - if (content == null) { + if (!StringUtils.hasLength(content)) { return null; } if (isPresentInText(content)) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslBundleTests.java index 090d0cc5bcf..bca23e18fb2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslBundleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslBundleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -16,6 +16,10 @@ package org.springframework.boot.ssl; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +30,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link SslBundle}. * * @author Phillip Webb + * @author Moritz Halbritter */ class SslBundleTests { @@ -52,4 +57,17 @@ class SslBundleTests { assertThat(bundle.getManagers()).isSameAs(managers); } + @Test + void shouldCreateSystemDefaultBundle() { + SslBundle sslBundle = SslBundle.systemDefault(); + SSLContext sslContext = sslBundle.createSslContext(); + assertThat(sslContext).isNotNull(); + TrustManager[] trustManagers = sslBundle.getManagers().getTrustManagers(); + assertThat(trustManagers).isNotEmpty(); + TrustManager trustManager = trustManagers[0]; + assertThat(trustManager).isInstanceOf(X509TrustManager.class); + X509TrustManager x509TrustManager = (X509TrustManager) trustManager; + assertThat(x509TrustManager.getAcceptedIssuers()).isNotEmpty(); + } + }