diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/FixedTrustManagerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/FixedTrustManagerFactory.java new file mode 100644 index 0000000000..2b48a35358 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/FixedTrustManagerFactory.java @@ -0,0 +1,73 @@ +/* + * 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. + * 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; + +import java.security.KeyStore; +import java.security.Provider; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManagerFactorySpi; + +import org.springframework.boot.SpringBootVersion; + +/** + * {@link TrustManagerFactory} which uses a fixed set of {@link TrustManager + * TrustManagers}. + * + * @author Moritz Halbritter + */ +final class FixedTrustManagerFactory extends TrustManagerFactory { + + private static final Provider PROVIDER = new Provider("FixedTrustManagerFactory", SpringBootVersion.getVersion(), + "") { + }; + + private FixedTrustManagerFactory(FixedTrustManagersSpi spi, String algorithm) { + super(spi, PROVIDER, algorithm); + } + + static FixedTrustManagerFactory of(TrustManagerFactory trustManagerFactory, TrustManager... trustManagers) { + return new FixedTrustManagerFactory(new FixedTrustManagersSpi(trustManagers), + trustManagerFactory.getAlgorithm()); + } + + private static final class FixedTrustManagersSpi extends TrustManagerFactorySpi { + + private final TrustManager[] trustManagers; + + private FixedTrustManagersSpi(TrustManager[] trustManagers) { + this.trustManagers = trustManagers; + } + + @Override + protected void engineInit(KeyStore ks) { + } + + @Override + protected void engineInit(ManagerFactoryParameters spec) { + } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return this.trustManagers; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslManagerBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslManagerBundle.java index 602b9ade5e..330ab2eb18 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslManagerBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/SslManagerBundle.java @@ -16,6 +16,11 @@ 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; @@ -30,6 +35,7 @@ import org.springframework.util.Assert; * {@link SslStoreBundle}. * * @author Scott Frederick + * @author Moritz Halbritter * @since 3.1.0 * @see SslStoreBundle * @see SslBundle#getManagers() @@ -118,4 +124,59 @@ public interface SslManagerBundle { return new DefaultSslManagerBundle(storeBundle, key); } + /** + * Factory method to create a new {@link SslManagerBundle} using the given + * {@link TrustManagerFactory} and the default {@link KeyManagerFactory}. + * @param trustManagerFactory the trust manager factory + * @return a new {@link SslManagerBundle} instance + * @since 3.5.0 + */ + static SslManagerBundle from(TrustManagerFactory trustManagerFactory) { + Assert.notNull(trustManagerFactory, "TrustManagerFactory must not be null"); + KeyManagerFactory defaultKeyManagerFactory = createDefaultKeyManagerFactory(); + return of(defaultKeyManagerFactory, trustManagerFactory); + } + + /** + * Factory method to create a new {@link SslManagerBundle} using the given + * {@link TrustManager TrustManagers} and the default {@link KeyManagerFactory}. + * @param trustManagers the trust managers to use + * @return a new {@link SslManagerBundle} instance + * @since 3.5.0 + */ + static SslManagerBundle from(TrustManager... trustManagers) { + Assert.notNull(trustManagers, "TrustManagers must not be null"); + KeyManagerFactory defaultKeyManagerFactory = createDefaultKeyManagerFactory(); + TrustManagerFactory defaultTrustManagerFactory = createDefaultTrustManagerFactory(); + return of(defaultKeyManagerFactory, FixedTrustManagerFactory.of(defaultTrustManagerFactory, trustManagers)); + } + + private static TrustManagerFactory createDefaultTrustManagerFactory() { + String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory trustManagerFactory; + try { + trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm); + trustManagerFactory.init((KeyStore) null); + } + catch (NoSuchAlgorithmException | KeyStoreException ex) { + throw new IllegalStateException( + "Unable to create TrustManagerFactory for default '%s' algorithm".formatted(defaultAlgorithm), ex); + } + return trustManagerFactory; + } + + private static KeyManagerFactory createDefaultKeyManagerFactory() { + String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory keyManagerFactory; + try { + keyManagerFactory = KeyManagerFactory.getInstance(defaultAlgorithm); + keyManagerFactory.init(null, null); + } + catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException ex) { + throw new IllegalStateException( + "Unable to create KeyManagerFactory for default '%s' algorithm".formatted(defaultAlgorithm), ex); + } + return keyManagerFactory; + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/FixedTrustManagerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/FixedTrustManagerFactoryTests.java new file mode 100644 index 0000000000..ec16487b0e --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/FixedTrustManagerFactoryTests.java @@ -0,0 +1,49 @@ +/* + * 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. + * 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; + +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link FixedTrustManagerFactory}. + * + * @author Moritz Halbritter + */ +class FixedTrustManagerFactoryTests { + + @Test + void shouldReturnTrustmanagers() throws Exception { + TrustManager trustManager1 = mock(TrustManager.class); + TrustManager trustManager2 = mock(TrustManager.class); + FixedTrustManagerFactory factory = FixedTrustManagerFactory.of(getDefaultTrustManagerFactory(), trustManager1, + trustManager2); + assertThat(factory.getTrustManagers()).containsExactly(trustManager1, trustManager2); + } + + private static TrustManagerFactory getDefaultTrustManagerFactory() throws NoSuchAlgorithmException { + return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslManagerBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslManagerBundleTests.java index 9aa17cd265..fa37425970 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslManagerBundleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/SslManagerBundleTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.ssl; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import org.junit.jupiter.api.Test; @@ -31,12 +32,13 @@ import static org.mockito.Mockito.mock; * Tests for {@link SslManagerBundle}. * * @author Phillip Webb + * @author Moritz Halbritter */ class SslManagerBundleTests { - private KeyManagerFactory keyManagerFactory = mock(KeyManagerFactory.class); + private final KeyManagerFactory keyManagerFactory = mock(KeyManagerFactory.class); - private TrustManagerFactory trustManagerFactory = mock(TrustManagerFactory.class); + private final TrustManagerFactory trustManagerFactory = mock(TrustManagerFactory.class); @Test void getKeyManagersDelegatesToFactory() { @@ -85,4 +87,20 @@ class SslManagerBundleTests { assertThat(bundle).isInstanceOf(DefaultSslManagerBundle.class); } + @Test + void shouldReturnTrustManagerFactory() { + SslManagerBundle bundle = SslManagerBundle.from(this.trustManagerFactory); + assertThat(bundle.getKeyManagerFactory()).isNotNull(); + assertThat(bundle.getTrustManagerFactory()).isSameAs(this.trustManagerFactory); + } + + @Test + void shouldReturnTrustManagers() { + TrustManager trustManager1 = mock(TrustManager.class); + TrustManager trustManager2 = mock(TrustManager.class); + SslManagerBundle bundle = SslManagerBundle.from(trustManager1, trustManager2); + assertThat(bundle.getKeyManagerFactory()).isNotNull(); + assertThat(bundle.getTrustManagerFactory().getTrustManagers()).containsExactly(trustManager1, trustManager2); + } + }