commit
f6505f7a18
|
|
@ -23,6 +23,7 @@ import java.security.KeyStoreException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.boot.io.ApplicationResourceLoader;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
|
|
@ -30,6 +31,7 @@ import org.springframework.core.io.Resource;
|
|||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* {@link SslStoreBundle} backed by a Java keystore.
|
||||
|
|
@ -43,9 +45,9 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
|||
|
||||
private final JksSslStoreDetails keyStoreDetails;
|
||||
|
||||
private final KeyStore keyStore;
|
||||
private final Supplier<KeyStore> keyStore;
|
||||
|
||||
private final KeyStore trustStore;
|
||||
private final Supplier<KeyStore> trustStore;
|
||||
|
||||
/**
|
||||
* Create a new {@link JksSslStoreBundle} instance.
|
||||
|
|
@ -54,13 +56,13 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
|||
*/
|
||||
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
|
||||
this.keyStoreDetails = keyStoreDetails;
|
||||
this.keyStore = createKeyStore("key", this.keyStoreDetails);
|
||||
this.trustStore = createKeyStore("trust", trustStoreDetails);
|
||||
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", this.keyStoreDetails));
|
||||
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", trustStoreDetails));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() {
|
||||
return this.keyStore;
|
||||
return this.keyStore.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -70,7 +72,7 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
|||
|
||||
@Override
|
||||
public KeyStore getTrustStore() {
|
||||
return this.trustStore;
|
||||
return this.trustStore.get();
|
||||
}
|
||||
|
||||
private KeyStore createKeyStore(String name, JksSslStoreDetails details) {
|
||||
|
|
@ -127,10 +129,12 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
|||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator creator = new ToStringCreator(this);
|
||||
creator.append("keyStore.type", (this.keyStore != null) ? this.keyStore.getType() : "none");
|
||||
KeyStore keyStore = this.keyStore.get();
|
||||
creator.append("keyStore.type", (keyStore != null) ? keyStore.getType() : "none");
|
||||
String keyStorePassword = getKeyStorePassword();
|
||||
creator.append("keyStorePassword", (keyStorePassword != null) ? "******" : null);
|
||||
creator.append("trustStore.type", (this.trustStore != null) ? this.trustStore.getType() : "none");
|
||||
KeyStore trustStore = this.trustStore.get();
|
||||
creator.append("trustStore.type", (trustStore != null) ? trustStore.getType() : "none");
|
||||
return creator.toString();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -97,4 +97,14 @@ final class LoadedPemSslStore implements PemSslStore {
|
|||
return this.privateKeySupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemSslStore withAlias(String alias) {
|
||||
return new LoadedPemSslStore(this.details.withAlias(alias));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemSslStore withPassword(String password) {
|
||||
return new LoadedPemSslStore(this.details.withPassword(password));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import java.security.PrivateKey;
|
|||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* {@link SslStoreBundle} backed by PEM-encoded certificates and private keys.
|
||||
|
|
@ -42,9 +44,9 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
|
||||
private static final String DEFAULT_ALIAS = "ssl";
|
||||
|
||||
private final KeyStore keyStore;
|
||||
private final Supplier<KeyStore> keyStore;
|
||||
|
||||
private final KeyStore trustStore;
|
||||
private final Supplier<KeyStore> trustStore;
|
||||
|
||||
/**
|
||||
* Create a new {@link PemSslStoreBundle} instance.
|
||||
|
|
@ -62,13 +64,13 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
* @since 3.2.0
|
||||
*/
|
||||
public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore) {
|
||||
this.keyStore = createKeyStore("key", pemKeyStore);
|
||||
this.trustStore = createKeyStore("trust", pemTrustStore);
|
||||
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", pemKeyStore));
|
||||
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", pemTrustStore));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() {
|
||||
return this.keyStore;
|
||||
return this.keyStore.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -78,7 +80,7 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
|
||||
@Override
|
||||
public KeyStore getTrustStore() {
|
||||
return this.trustStore;
|
||||
return this.trustStore.get();
|
||||
}
|
||||
|
||||
private static KeyStore createKeyStore(String name, PemSslStore pemSslStore) {
|
||||
|
|
@ -86,10 +88,10 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
return null;
|
||||
}
|
||||
try {
|
||||
Assert.notEmpty(pemSslStore.certificates(), "Certificates must not be empty");
|
||||
List<X509Certificate> certificates = pemSslStore.certificates();
|
||||
Assert.notEmpty(certificates, "Certificates must not be empty");
|
||||
String alias = (pemSslStore.alias() != null) ? pemSslStore.alias() : DEFAULT_ALIAS;
|
||||
KeyStore store = createKeyStore(pemSslStore.type());
|
||||
List<X509Certificate> certificates = pemSslStore.certificates();
|
||||
PrivateKey privateKey = pemSslStore.privateKey();
|
||||
if (privateKey != null) {
|
||||
addPrivateKey(store, privateKey, alias, pemSslStore.password(), certificates);
|
||||
|
|
@ -129,9 +131,11 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator creator = new ToStringCreator(this);
|
||||
creator.append("keyStore.type", (this.keyStore != null) ? this.keyStore.getType() : "none");
|
||||
KeyStore keyStore = this.keyStore.get();
|
||||
KeyStore trustStore = this.trustStore.get();
|
||||
creator.append("keyStore.type", (keyStore != null) ? keyStore.getType() : "none");
|
||||
creator.append("keyStorePassword", null);
|
||||
creator.append("trustStore.type", (this.trustStore != null) ? this.trustStore.getType() : "none");
|
||||
creator.append("trustStore.type", (trustStore != null) ? trustStore.getType() : "none");
|
||||
return creator.toString();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,12 +63,10 @@ class JksSslStoreBundleTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void whenTypePKCS11AndLocationThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
|
||||
JksSslStoreDetails trustStoreDetails = null;
|
||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
})
|
||||
void whenTypePKCS11AndLocationGetKeyStoreThrowsException() {
|
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
|
||||
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
|
||||
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
|
||||
.withMessageContaining(
|
||||
"Unable to create key store: Location is 'test.jks', but must be empty or null for PKCS11 hardware key stores");
|
||||
}
|
||||
|
|
@ -109,22 +107,28 @@ class JksSslStoreBundleTests {
|
|||
|
||||
@Test
|
||||
void whenHasKeyStoreProvider() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||
"classpath:test.jks", "secret");
|
||||
JksSslStoreDetails trustStoreDetails = null;
|
||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
}).withMessageContaining("com.example.KeyStoreProvider");
|
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||
"classpath:test.jks", "secret");
|
||||
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
|
||||
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
|
||||
.withMessageContaining("com.example.KeyStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasTrustStoreProvider() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
JksSslStoreDetails keyStoreDetails = null;
|
||||
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||
"classpath:test.jks", "secret");
|
||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
}).withMessageContaining("com.example.KeyStoreProvider");
|
||||
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||
"classpath:test.jks", "secret");
|
||||
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(null, trustStoreDetails);
|
||||
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getTrustStore)
|
||||
.withMessageContaining("com.example.KeyStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void storeCreationIsLazy() {
|
||||
JksSslStoreDetails details = new JksSslStoreDetails(null, null, "does-not-exist", null);
|
||||
JksSslStoreBundle bundle = new JksSslStoreBundle(details, details);
|
||||
assertThatIllegalStateException().isThrownBy(bundle::getKeyStore);
|
||||
assertThatIllegalStateException().isThrownBy(bundle::getTrustStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -141,7 +145,8 @@ class JksSslStoreBundleTests {
|
|||
@Test
|
||||
void invalidBase64EncodedLocationThrowsException() {
|
||||
JksSslStoreDetails keyStoreDetails = JksSslStoreDetails.forLocation("base64:not base 64");
|
||||
assertThatIllegalStateException().isThrownBy(() -> new JksSslStoreBundle(keyStoreDetails, null))
|
||||
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
|
||||
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
|
||||
.withMessageContaining("key store")
|
||||
.withMessageContaining("base64:not base 64")
|
||||
.havingRootCause()
|
||||
|
|
@ -152,7 +157,8 @@ class JksSslStoreBundleTests {
|
|||
@Test
|
||||
void invalidLocationThrowsException() {
|
||||
JksSslStoreDetails trustStoreDetails = JksSslStoreDetails.forLocation("does-not-exist.p12");
|
||||
assertThatIllegalStateException().isThrownBy(() -> new JksSslStoreBundle(null, trustStoreDetails))
|
||||
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(null, trustStoreDetails);
|
||||
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getTrustStore)
|
||||
.withMessageContaining("trust store")
|
||||
.withMessageContaining("does-not-exist.p12");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -45,4 +45,20 @@ class LoadedPemSslStoreTests {
|
|||
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::privateKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withAliasIsLazy() {
|
||||
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStore store = new LoadedPemSslStore(details).withAlias("alias");
|
||||
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPasswordIsLazy() {
|
||||
PemSslStoreDetails details = PemSslStoreDetails.forCertificate("classpath:missing-test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStore store = new LoadedPemSslStore(details).withPassword("password");
|
||||
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(store::certificates);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
/**
|
||||
* Tests for {@link PemSslStoreBundle}.
|
||||
|
|
@ -203,6 +207,18 @@ class PemSslStoreBundleTests {
|
|||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("ssl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void storeCreationIsLazy() {
|
||||
PemSslStore pemSslStore = mock(PemSslStore.class);
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(pemSslStore, pemSslStore);
|
||||
given(pemSslStore.certificates()).willReturn(PemContent.of(CERTIFICATE).getCertificates());
|
||||
then(pemSslStore).shouldHaveNoInteractions();
|
||||
bundle.getKeyStore();
|
||||
then(pemSslStore).should().certificates();
|
||||
bundle.getTrustStore();
|
||||
then(pemSslStore).should(times(2)).certificates();
|
||||
}
|
||||
|
||||
private Consumer<KeyStore> storeContainingCert(String keyAlias) {
|
||||
return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue