commit
f6505f7a18
|
|
@ -23,6 +23,7 @@ import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.boot.io.ApplicationResourceLoader;
|
import org.springframework.boot.io.ApplicationResourceLoader;
|
||||||
import org.springframework.boot.ssl.SslStoreBundle;
|
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.core.style.ToStringCreator;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link SslStoreBundle} backed by a Java keystore.
|
* {@link SslStoreBundle} backed by a Java keystore.
|
||||||
|
|
@ -43,9 +45,9 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
||||||
|
|
||||||
private final JksSslStoreDetails keyStoreDetails;
|
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.
|
* Create a new {@link JksSslStoreBundle} instance.
|
||||||
|
|
@ -54,13 +56,13 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
||||||
*/
|
*/
|
||||||
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
|
public JksSslStoreBundle(JksSslStoreDetails keyStoreDetails, JksSslStoreDetails trustStoreDetails) {
|
||||||
this.keyStoreDetails = keyStoreDetails;
|
this.keyStoreDetails = keyStoreDetails;
|
||||||
this.keyStore = createKeyStore("key", this.keyStoreDetails);
|
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", this.keyStoreDetails));
|
||||||
this.trustStore = createKeyStore("trust", trustStoreDetails);
|
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", trustStoreDetails));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyStore getKeyStore() {
|
public KeyStore getKeyStore() {
|
||||||
return this.keyStore;
|
return this.keyStore.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -70,7 +72,7 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyStore getTrustStore() {
|
public KeyStore getTrustStore() {
|
||||||
return this.trustStore;
|
return this.trustStore.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStore createKeyStore(String name, JksSslStoreDetails details) {
|
private KeyStore createKeyStore(String name, JksSslStoreDetails details) {
|
||||||
|
|
@ -127,10 +129,12 @@ public class JksSslStoreBundle implements SslStoreBundle {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ToStringCreator creator = new ToStringCreator(this);
|
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();
|
String keyStorePassword = getKeyStorePassword();
|
||||||
creator.append("keyStorePassword", (keyStorePassword != null) ? "******" : null);
|
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();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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();
|
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.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.boot.ssl.SslStoreBundle;
|
import org.springframework.boot.ssl.SslStoreBundle;
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link SslStoreBundle} backed by PEM-encoded certificates and private keys.
|
* {@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 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.
|
* Create a new {@link PemSslStoreBundle} instance.
|
||||||
|
|
@ -62,13 +64,13 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
*/
|
*/
|
||||||
public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore) {
|
public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore) {
|
||||||
this.keyStore = createKeyStore("key", pemKeyStore);
|
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", pemKeyStore));
|
||||||
this.trustStore = createKeyStore("trust", pemTrustStore);
|
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", pemTrustStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyStore getKeyStore() {
|
public KeyStore getKeyStore() {
|
||||||
return this.keyStore;
|
return this.keyStore.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -78,7 +80,7 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyStore getTrustStore() {
|
public KeyStore getTrustStore() {
|
||||||
return this.trustStore;
|
return this.trustStore.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyStore createKeyStore(String name, PemSslStore pemSslStore) {
|
private static KeyStore createKeyStore(String name, PemSslStore pemSslStore) {
|
||||||
|
|
@ -86,10 +88,10 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
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;
|
String alias = (pemSslStore.alias() != null) ? pemSslStore.alias() : DEFAULT_ALIAS;
|
||||||
KeyStore store = createKeyStore(pemSslStore.type());
|
KeyStore store = createKeyStore(pemSslStore.type());
|
||||||
List<X509Certificate> certificates = pemSslStore.certificates();
|
|
||||||
PrivateKey privateKey = pemSslStore.privateKey();
|
PrivateKey privateKey = pemSslStore.privateKey();
|
||||||
if (privateKey != null) {
|
if (privateKey != null) {
|
||||||
addPrivateKey(store, privateKey, alias, pemSslStore.password(), certificates);
|
addPrivateKey(store, privateKey, alias, pemSslStore.password(), certificates);
|
||||||
|
|
@ -129,9 +131,11 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ToStringCreator creator = new ToStringCreator(this);
|
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("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();
|
return creator.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,10 @@ class JksSslStoreBundleTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void whenTypePKCS11AndLocationThrowsException() {
|
void whenTypePKCS11AndLocationGetKeyStoreThrowsException() {
|
||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
|
||||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
|
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails("PKCS11", null, "test.jks", null);
|
||||||
JksSslStoreDetails trustStoreDetails = null;
|
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
|
||||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
|
||||||
})
|
|
||||||
.withMessageContaining(
|
.withMessageContaining(
|
||||||
"Unable to create key store: Location is 'test.jks', but must be empty or null for PKCS11 hardware key stores");
|
"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
|
@Test
|
||||||
void whenHasKeyStoreProvider() {
|
void whenHasKeyStoreProvider() {
|
||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
|
||||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||||
"classpath:test.jks", "secret");
|
"classpath:test.jks", "secret");
|
||||||
JksSslStoreDetails trustStoreDetails = null;
|
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null);
|
||||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
assertThatIllegalStateException().isThrownBy(jksSslStoreBundle::getKeyStore)
|
||||||
}).withMessageContaining("com.example.KeyStoreProvider");
|
.withMessageContaining("com.example.KeyStoreProvider");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void whenHasTrustStoreProvider() {
|
void whenHasTrustStoreProvider() {
|
||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
|
||||||
JksSslStoreDetails keyStoreDetails = null;
|
|
||||||
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(null, "com.example.KeyStoreProvider",
|
||||||
"classpath:test.jks", "secret");
|
"classpath:test.jks", "secret");
|
||||||
new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
JksSslStoreBundle jksSslStoreBundle = new JksSslStoreBundle(null, trustStoreDetails);
|
||||||
}).withMessageContaining("com.example.KeyStoreProvider");
|
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
|
@Test
|
||||||
|
|
@ -141,7 +145,8 @@ class JksSslStoreBundleTests {
|
||||||
@Test
|
@Test
|
||||||
void invalidBase64EncodedLocationThrowsException() {
|
void invalidBase64EncodedLocationThrowsException() {
|
||||||
JksSslStoreDetails keyStoreDetails = JksSslStoreDetails.forLocation("base64:not base 64");
|
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("key store")
|
||||||
.withMessageContaining("base64:not base 64")
|
.withMessageContaining("base64:not base 64")
|
||||||
.havingRootCause()
|
.havingRootCause()
|
||||||
|
|
@ -152,7 +157,8 @@ class JksSslStoreBundleTests {
|
||||||
@Test
|
@Test
|
||||||
void invalidLocationThrowsException() {
|
void invalidLocationThrowsException() {
|
||||||
JksSslStoreDetails trustStoreDetails = JksSslStoreDetails.forLocation("does-not-exist.p12");
|
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("trust store")
|
||||||
.withMessageContaining("does-not-exist.p12");
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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);
|
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 org.springframework.util.function.ThrowingConsumer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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}.
|
* Tests for {@link PemSslStoreBundle}.
|
||||||
|
|
@ -203,6 +207,18 @@ class PemSslStoreBundleTests {
|
||||||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("ssl"));
|
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) {
|
private Consumer<KeyStore> storeContainingCert(String keyAlias) {
|
||||||
return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
|
return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue