Skip to content

Commit

Permalink
Merge branch '3.2.x' into 3.3.x
Browse files Browse the repository at this point in the history
Closes gh-42169
  • Loading branch information
wilkinsona committed Sep 6, 2024
2 parents 4d2aa2d + a89ae3f commit 21b4eb5
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
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;
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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -70,7 +72,7 @@ public String getKeyStorePassword() {

@Override
public KeyStore getTrustStore() {
return this.trustStore;
return this.trustStore.get();
}

private KeyStore createKeyStore(String name, JksSslStoreDetails details) {
Expand Down Expand Up @@ -127,10 +129,12 @@ private void loadKeyStore(KeyStore store, String location, char[] password) {
@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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -97,4 +97,14 @@ public PrivateKey privateKey() {
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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
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.
Expand All @@ -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.
Expand All @@ -66,8 +68,9 @@ public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails
*/
@Deprecated(since = "3.2.0", forRemoval = true)
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias) {
this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias);
this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias);
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", PemSslStore.load(keyStoreDetails), alias));
this.trustStore = SingletonSupplier
.of(() -> createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias));
}

/**
Expand All @@ -81,13 +84,13 @@ public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore) {
}

private PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore, String alias) {
this.keyStore = createKeyStore("key", pemKeyStore, alias);
this.trustStore = createKeyStore("trust", pemTrustStore, alias);
this.keyStore = SingletonSupplier.of(() -> createKeyStore("key", pemKeyStore, alias));
this.trustStore = SingletonSupplier.of(() -> createKeyStore("trust", pemTrustStore, alias));
}

@Override
public KeyStore getKeyStore() {
return this.keyStore;
return this.keyStore.get();
}

@Override
Expand All @@ -97,19 +100,19 @@ public String getKeyStorePassword() {

@Override
public KeyStore getTrustStore() {
return this.trustStore;
return this.trustStore.get();
}

private static KeyStore createKeyStore(String name, PemSslStore pemSslStore, String alias) {
if (pemSslStore == null) {
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");
alias = (pemSslStore.alias() != null) ? pemSslStore.alias() : alias;
alias = (alias != null) ? 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);
Expand Down Expand Up @@ -149,9 +152,11 @@ private static void addCertificates(KeyStore keyStore, List<X509Certificate> cer
@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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,10 @@ void whenStoresHaveNoValues() {
}

@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");
}
Expand Down Expand Up @@ -109,22 +107,28 @@ void whenHasTrustStoreType() {

@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
Expand All @@ -141,7 +145,8 @@ void whenLocationsAreBase64Encoded() throws IOException {
@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()
Expand All @@ -152,7 +157,8 @@ void invalidBase64EncodedLocationThrowsException() {
@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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -45,4 +45,20 @@ void privateKeyIsLoadedLazily() {
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);
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,6 +27,10 @@
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}.
Expand Down Expand Up @@ -215,6 +219,18 @@ void createWithPemSslStoreCreatesInstance() {
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);
}
Expand Down

0 comments on commit 21b4eb5

Please sign in to comment.