Skip to content

Commit 1b61bc1

Browse files
committed
Move PEM verification to spring-boot-autoconfigure
Move `KeyVerifier` to spring-boot-autoconfigure to reduce the public API required in `PemSslStoreBundle`. This commit also moves the verify property so that is can be set per store. Closes gh-38173
1 parent 5e5d226 commit 1b61bc1

File tree

12 files changed

+113
-98
lines changed

12 files changed

+113
-98
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/KeyVerifier.java renamed to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/KeyVerifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.ssl.pem;
17+
package org.springframework.boot.autoconfigure.ssl;
1818

1919
import java.nio.charset.StandardCharsets;
2020
import java.security.InvalidKeyException;

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ public class PemSslBundleProperties extends SslBundleProperties {
3939
*/
4040
private final Store truststore = new Store();
4141

42-
/**
43-
* Whether to verify that the private key matches the public key.
44-
*/
45-
private boolean verifyKeys;
46-
4742
public Store getKeystore() {
4843
return this.keystore;
4944
}
@@ -52,14 +47,6 @@ public Store getTruststore() {
5247
return this.truststore;
5348
}
5449

55-
public boolean isVerifyKeys() {
56-
return this.verifyKeys;
57-
}
58-
59-
public void setVerifyKeys(boolean verifyKeys) {
60-
this.verifyKeys = verifyKeys;
61-
}
62-
6350
/**
6451
* Store properties.
6552
*/
@@ -85,6 +72,11 @@ public static class Store {
8572
*/
8673
private String privateKeyPassword;
8774

75+
/**
76+
* Whether to verify that the private key matches the public key.
77+
*/
78+
private boolean verifyKeys;
79+
8880
public String getType() {
8981
return this.type;
9082
}
@@ -117,6 +109,14 @@ public void setPrivateKeyPassword(String privateKeyPassword) {
117109
this.privateKeyPassword = privateKeyPassword;
118110
}
119111

112+
public boolean isVerifyKeys() {
113+
return this.verifyKeys;
114+
}
115+
116+
public void setVerifyKeys(boolean verifyKeys) {
117+
this.verifyKeys = verifyKeys;
118+
}
119+
120120
}
121121

122122
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot.autoconfigure.ssl;
1818

19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
21+
import java.security.cert.X509Certificate;
22+
1923
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
2024
import org.springframework.boot.ssl.SslBundle;
2125
import org.springframework.boot.ssl.SslBundleKey;
@@ -24,6 +28,7 @@
2428
import org.springframework.boot.ssl.SslStoreBundle;
2529
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
2630
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
31+
import org.springframework.boot.ssl.pem.PemSslStore;
2732
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
2833
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
2934

@@ -107,16 +112,39 @@ public static SslBundle get(JksSslBundleProperties properties) {
107112
}
108113

109114
private static SslStoreBundle asSslStoreBundle(PemSslBundleProperties properties) {
110-
PemSslStoreDetails keyStoreDetails = asStoreDetails(properties.getKeystore())
111-
.withAlias(properties.getKey().getAlias());
112-
PemSslStoreDetails trustStoreDetails = asStoreDetails(properties.getTruststore())
113-
.withAlias(properties.getKey().getAlias());
114-
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, properties.isVerifyKeys());
115+
PemSslStore keyStore = asPemSslStore(properties.getKeystore(), properties.getKey().getAlias());
116+
PemSslStore trustStore = asPemSslStore(properties.getTruststore(), properties.getKey().getAlias());
117+
return new PemSslStoreBundle(keyStore, trustStore);
118+
}
119+
120+
private static PemSslStore asPemSslStore(PemSslBundleProperties.Store properties, String alias) {
121+
try {
122+
PemSslStoreDetails details = asStoreDetails(properties, alias);
123+
PemSslStore pemSslStore = PemSslStore.load(details);
124+
if (properties.isVerifyKeys()) {
125+
verifyPemSslStoreKeys(pemSslStore);
126+
}
127+
return pemSslStore;
128+
}
129+
catch (IOException ex) {
130+
throw new UncheckedIOException(ex);
131+
}
132+
}
133+
134+
private static void verifyPemSslStoreKeys(PemSslStore pemSslStore) {
135+
KeyVerifier keyVerifier = new KeyVerifier();
136+
for (X509Certificate certificate : pemSslStore.certificates()) {
137+
KeyVerifier.Result result = keyVerifier.matches(pemSslStore.privateKey(), certificate.getPublicKey());
138+
if (result == KeyVerifier.Result.YES) {
139+
return;
140+
}
141+
}
142+
throw new IllegalStateException("Private key matches none of the certificates in the chain");
115143
}
116144

117-
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) {
118-
return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(),
119-
properties.getPrivateKeyPassword());
145+
private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties, String alias) {
146+
return new PemSslStoreDetails(properties.getType(), alias, null, properties.getCertificate(),
147+
properties.getPrivateKey(), properties.getPrivateKeyPassword());
120148
}
121149

122150
private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/KeyVerifierTests.java renamed to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/KeyVerifierTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.ssl.pem;
17+
package org.springframework.boot.autoconfigure.ssl;
1818

1919
import java.security.InvalidAlgorithmParameterException;
2020
import java.security.KeyPair;
@@ -33,7 +33,7 @@
3333
import org.junit.jupiter.params.provider.Arguments;
3434
import org.junit.jupiter.params.provider.MethodSource;
3535

36-
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
36+
import org.springframework.boot.autoconfigure.ssl.KeyVerifier.Result;
3737

3838
import static org.assertj.core.api.Assertions.assertThat;
3939

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
import java.security.KeyStore;
2121
import java.security.cert.Certificate;
2222
import java.util.Set;
23+
import java.util.function.Consumer;
2324

2425
import org.junit.jupiter.api.Test;
2526

2627
import org.springframework.boot.ssl.SslBundle;
28+
import org.springframework.util.function.ThrowingConsumer;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2932

3033
/**
3134
* Tests for {@link PropertiesSslBundle}.
@@ -35,6 +38,8 @@
3538
*/
3639
class PropertiesSslBundleTests {
3740

41+
private static final char[] EMPTY_KEY_PASSWORD = new char[] {};
42+
3843
@Test
3944
void pemPropertiesAreMappedToSslBundle() throws Exception {
4045
PemSslBundleProperties properties = new PemSslBundleProperties();
@@ -99,4 +104,47 @@ void jksPropertiesAreMappedToSslBundle() {
99104
assertThat(trustStore.getProvider().getName()).isEqualTo("SUN");
100105
}
101106

107+
@Test
108+
void getWithPemSslBundlePropertiesWhenVerifyKeyStoreAgainstSingleCertificateWithMatchCreatesBundle() {
109+
PemSslBundleProperties properties = new PemSslBundleProperties();
110+
properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key1.crt");
111+
properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key1.pem");
112+
properties.getKeystore().setVerifyKeys(true);
113+
properties.getKey().setAlias("test-alias");
114+
SslBundle bundle = PropertiesSslBundle.get(properties);
115+
assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias"));
116+
}
117+
118+
@Test
119+
void getWithPemSslBundlePropertiesWhenVerifyKeyStoreAgainstCertificateChainWithMatchCreatesBundle() {
120+
PemSslBundleProperties properties = new PemSslBundleProperties();
121+
properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2-chain.crt");
122+
properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key2.pem");
123+
properties.getKeystore().setVerifyKeys(true);
124+
properties.getKey().setAlias("test-alias");
125+
SslBundle bundle = PropertiesSslBundle.get(properties);
126+
assertThat(bundle.getStores().getKeyStore()).satisfies(storeContainingCertAndKey("test-alias"));
127+
}
128+
129+
@Test
130+
void getWithPemSslBundlePropertiesWhenVerifyKeyStoreWithNoMatchThrowsException() {
131+
PemSslBundleProperties properties = new PemSslBundleProperties();
132+
properties.getKeystore().setCertificate("classpath:org/springframework/boot/autoconfigure/ssl/key2.crt");
133+
properties.getKeystore().setPrivateKey("classpath:org/springframework/boot/autoconfigure/ssl/key1.pem");
134+
properties.getKeystore().setVerifyKeys(true);
135+
properties.getKey().setAlias("test-alias");
136+
assertThatIllegalStateException().isThrownBy(() -> PropertiesSslBundle.get(properties))
137+
.withMessageContaining("Private key matches none of the certificates");
138+
}
139+
140+
private Consumer<KeyStore> storeContainingCertAndKey(String keyAlias) {
141+
return ThrowingConsumer.of((keyStore) -> {
142+
assertThat(keyStore).isNotNull();
143+
assertThat(keyStore.getType()).isEqualTo(KeyStore.getDefaultType());
144+
assertThat(keyStore.containsAlias(keyAlias)).isTrue();
145+
assertThat(keyStore.getCertificate(keyAlias)).isNotNull();
146+
assertThat(keyStore.getKey(keyAlias, EMPTY_KEY_PASSWORD)).isNotNull();
147+
});
148+
}
149+
102150
}

0 commit comments

Comments
 (0)