From 767ec4e22eb1455c1aee15ba865fb7168d4959aa Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 6 Jun 2023 13:47:17 -0500 Subject: [PATCH] Support encrypted PKCS8 private keys in SSL bundles Properties `ssl.bundle.pem.mybundle.keystore.private-key-password` and `ssl.bundle.pem.mybundle.truststore.private-key-password` have been added for configuring the password required to decrypt an encrypted private key. Only PKCS8 private keys with encryption are supported. PKCS1 and EC private keys with encryption are much more complex to decrypt, and are not supported. Fixes gh-35652 --- .../ssl/PemSslBundleProperties.java | 13 +++ .../ssl/PropertiesSslBundle.java | 3 +- .../ssl/PropertiesSslBundleTests.java | 92 +++++++++++++++++++ .../boot/ssl/pem/PemPrivateKeyParser.java | 91 +++++++++++++++--- .../boot/ssl/pem/PemSslStoreBundle.java | 4 +- .../boot/ssl/pem/PemSslStoreDetails.java | 18 +++- .../ssl/pem/PemPrivateKeyParserTests.java | 41 +++++++++ .../boot/ssl/pem/PemSslStoreBundleTests.java | 11 +++ .../ssl/ec/key-ec-prime256v1-encrypted.pem | 8 ++ .../resources/ssl/pkcs1/key-rsa-encrypted.pem | 30 ++++++ .../resources/ssl/pkcs8/key-ec-encrypted.pem | 7 ++ .../resources/ssl/pkcs8/key-rsa-encrypted.pem | 54 +++++++++++ 12 files changed, 355 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/ec/key-ec-prime256v1-encrypted.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs1/key-rsa-encrypted.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-encrypted.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa-encrypted.pem diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java index d296ffdec3..89f51bf27d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java @@ -66,6 +66,11 @@ public class PemSslBundleProperties extends SslBundleProperties { */ String privateKey; + /** + * Password used to decrypt an encrypted private key. + */ + String privateKeyPassword; + public String getType() { return this.type; } @@ -90,6 +95,14 @@ public class PemSslBundleProperties extends SslBundleProperties { this.privateKey = privateKey; } + public String getPrivateKeyPassword() { + return this.privateKeyPassword; + } + + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java index abb9ab6cc6..b25b88ad14 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java @@ -113,7 +113,8 @@ public final class PropertiesSslBundle implements SslBundle { } private static PemSslStoreDetails asStoreDetails(PemSslBundleProperties.Store properties) { - return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey()); + return new PemSslStoreDetails(properties.getType(), properties.getCertificate(), properties.getPrivateKey(), + properties.getPrivateKeyPassword()); } private static SslStoreBundle asSslStoreBundle(JksSslBundleProperties properties) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java new file mode 100644 index 0000000000..43c00ac56b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundleTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2023 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.autoconfigure.ssl; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.ssl.SslBundle; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertiesSslBundle}. + * + * @author Scott Frederick + */ +class PropertiesSslBundleTests { + + @Test + void pemPropertiesAreMappedToSslBundle() { + PemSslBundleProperties properties = new PemSslBundleProperties(); + properties.getKey().setAlias("alias"); + properties.getKey().setPassword("secret"); + properties.getOptions().setCiphers(Set.of("cipher1", "cipher2", "cipher3")); + properties.getOptions().setEnabledProtocols(Set.of("protocol1", "protocol2")); + properties.getKeystore().setCertificate("cert1.pem"); + properties.getKeystore().setPrivateKey("key1.pem"); + properties.getKeystore().setPrivateKeyPassword("keysecret1"); + properties.getKeystore().setType("PKCS12"); + properties.getTruststore().setCertificate("cert2.pem"); + properties.getTruststore().setPrivateKey("key2.pem"); + properties.getTruststore().setPrivateKeyPassword("keysecret2"); + properties.getTruststore().setType("JKS"); + SslBundle sslBundle = PropertiesSslBundle.get(properties); + assertThat(sslBundle.getKey().getAlias()).isEqualTo("alias"); + assertThat(sslBundle.getKey().getPassword()).isEqualTo("secret"); + assertThat(sslBundle.getOptions().getCiphers()).containsExactlyInAnyOrder("cipher1", "cipher2", "cipher3"); + assertThat(sslBundle.getOptions().getEnabledProtocols()).containsExactlyInAnyOrder("protocol1", "protocol2"); + assertThat(sslBundle.getStores()).isNotNull(); + assertThat(sslBundle.getStores()).extracting("keyStoreDetails") + .extracting("certificate", "privateKey", "privateKeyPassword", "type") + .containsExactly("cert1.pem", "key1.pem", "keysecret1", "PKCS12"); + assertThat(sslBundle.getStores()).extracting("trustStoreDetails") + .extracting("certificate", "privateKey", "privateKeyPassword", "type") + .containsExactly("cert2.pem", "key2.pem", "keysecret2", "JKS"); + } + + @Test + void jksPropertiesAreMappedToSslBundle() { + JksSslBundleProperties properties = new JksSslBundleProperties(); + properties.getKey().setAlias("alias"); + properties.getKey().setPassword("secret"); + properties.getOptions().setCiphers(Set.of("cipher1", "cipher2", "cipher3")); + properties.getOptions().setEnabledProtocols(Set.of("protocol1", "protocol2")); + properties.getKeystore().setLocation("cert1.p12"); + properties.getKeystore().setPassword("secret1"); + properties.getKeystore().setProvider("provider1"); + properties.getKeystore().setType("JKS"); + properties.getTruststore().setLocation("cert2.jks"); + properties.getTruststore().setPassword("secret2"); + properties.getTruststore().setProvider("provider2"); + properties.getTruststore().setType("PKCS12"); + SslBundle sslBundle = PropertiesSslBundle.get(properties); + assertThat(sslBundle.getKey().getAlias()).isEqualTo("alias"); + assertThat(sslBundle.getKey().getPassword()).isEqualTo("secret"); + assertThat(sslBundle.getOptions().getCiphers()).containsExactlyInAnyOrder("cipher1", "cipher2", "cipher3"); + assertThat(sslBundle.getOptions().getEnabledProtocols()).containsExactlyInAnyOrder("protocol1", "protocol2"); + assertThat(sslBundle.getStores()).isNotNull(); + assertThat(sslBundle.getStores()).extracting("keyStoreDetails") + .extracting("location", "password", "provider", "type") + .containsExactly("cert1.p12", "secret1", "provider1", "JKS"); + assertThat(sslBundle.getStores()).extracting("trustStoreDetails") + .extracting("location", "password", "provider", "type") + .containsExactly("cert2.jks", "secret2", "provider2", "PKCS12"); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java index e7216d03c6..7f3e6c3304 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java @@ -18,6 +18,7 @@ package org.springframework.boot.ssl.pem; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; @@ -27,10 +28,18 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.List; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +import org.springframework.util.Assert; + /** * Parser for PKCS private key files in PEM format. * @@ -48,18 +57,27 @@ final class PemPrivateKeyParser { private static final String PKCS8_FOOTER = "-+END\\s+PRIVATE\\s+KEY[^-]*-+"; + private static final String PKCS8_ENCRYPTED_HEADER = "-+BEGIN\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; + + private static final String PKCS8_ENCRYPTED_FOOTER = "-+END\\s+ENCRYPTED\\s+PRIVATE\\s+KEY[^-]*-+"; + private static final String EC_HEADER = "-+BEGIN\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"; private static final String EC_FOOTER = "-+END\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+"; private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)"; + public static final int BASE64_TEXT_GROUP = 1; + private static final List PEM_PARSERS; static { List parsers = new ArrayList<>(); parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs1, "RSA")); parsers.add(new PemParser(EC_HEADER, EC_FOOTER, PemPrivateKeyParser::createKeySpecForEc, "EC")); - parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PKCS8EncodedKeySpec::new, "RSA", "EC", "DSA", "Ed25519")); + parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, PemPrivateKeyParser::createKeySpecForPkcs8, "RSA", "EC", + "DSA", "Ed25519")); + parsers.add(new PemParser(PKCS8_ENCRYPTED_HEADER, PKCS8_ENCRYPTED_FOOTER, + PemPrivateKeyParser::createKeySpecForPkcs8Encrypted, "RSA", "EC", "DSA", "Ed25519")); PEM_PARSERS = Collections.unmodifiableList(parsers); } @@ -81,11 +99,11 @@ final class PemPrivateKeyParser { private PemPrivateKeyParser() { } - private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes) { + private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes, String password) { return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null); } - private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes) { + private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes, String password) { return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, EC_PARAMETERS); } @@ -106,18 +124,37 @@ final class PemPrivateKeyParser { } } + private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, String password) { + return new PKCS8EncodedKeySpec(bytes); + } + + private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, String password) { + return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password); + } + /** * Parse a private key from the specified string. * @param key the private key to parse * @return the parsed private key */ static PrivateKey parse(String key) { + return parse(key, null); + } + + /** + * Parse a private key from the specified string, using the provided password for + * decryption if necessary. + * @param key the private key to parse + * @param password the password used to decrypt an encrypted private key + * @return the parsed private key + */ + static PrivateKey parse(String key, String password) { if (key == null) { return null; } try { for (PemParser pemParser : PEM_PARSERS) { - PrivateKey privateKey = pemParser.parse(key); + PrivateKey privateKey = pemParser.parse(key, password); if (privateKey != null) { return privateKey; } @@ -136,20 +173,20 @@ final class PemPrivateKeyParser { private final Pattern pattern; - private final Function keySpecFactory; + private final BiFunction keySpecFactory; private final String[] algorithms; - PemParser(String header, String footer, Function keySpecFactory, + PemParser(String header, String footer, BiFunction keySpecFactory, String... algorithms) { this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); - this.algorithms = algorithms; this.keySpecFactory = keySpecFactory; + this.algorithms = algorithms; } - PrivateKey parse(String text) { + PrivateKey parse(String text, String password) { Matcher matcher = this.pattern.matcher(text); - return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(1))); + return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(BASE64_TEXT_GROUP)), password); } private static byte[] decodeBase64(String content) { @@ -157,9 +194,9 @@ final class PemPrivateKeyParser { return Base64.getDecoder().decode(contentBytes); } - private PrivateKey parse(byte[] bytes) { + private PrivateKey parse(byte[] bytes, String password) { try { - PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes); + PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes, password); for (String algorithm : this.algorithms) { KeyFactory keyFactory = KeyFactory.getInstance(algorithm); try { @@ -251,4 +288,34 @@ final class PemPrivateKeyParser { } + static class Pkcs8PrivateKeyDecryptor { + + public static final String PBES2_ALGORITHM = "PBES2"; + + static PKCS8EncodedKeySpec decrypt(byte[] bytes, String password) { + Assert.notNull(password, "Password is required for an encrypted private key"); + try { + EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(bytes); + AlgorithmParameters algorithmParameters = keyInfo.getAlgParameters(); + String encryptionAlgorithm = getEncryptionAlgorithm(algorithmParameters, keyInfo.getAlgName()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptionAlgorithm); + SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray())); + Cipher cipher = Cipher.getInstance(encryptionAlgorithm); + cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters); + return keyInfo.getKeySpec(cipher); + } + catch (IOException | GeneralSecurityException ex) { + throw new IllegalArgumentException("Error decrypting private key", ex); + } + } + + private static String getEncryptionAlgorithm(AlgorithmParameters algParameters, String algName) { + if (algParameters != null && PBES2_ALGORITHM.equals(algName)) { + return algParameters.toString(); + } + return algName; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java index dc857bf66f..dee4651852 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java @@ -84,14 +84,14 @@ public class PemSslStoreBundle implements SslStoreBundle { return null; } try { - Assert.notNull(details.certificate(), "CertificateContent must not be null"); + Assert.notNull(details.certificate(), "Certificate content must not be null"); String type = (!StringUtils.hasText(details.type())) ? KeyStore.getDefaultType() : details.type(); KeyStore store = KeyStore.getInstance(type); store.load(null); String certificateContent = PemContent.load(details.certificate()); String privateKeyContent = PemContent.load(details.privateKey()); X509Certificate[] certificates = PemCertificateParser.parse(certificateContent); - PrivateKey privateKey = PemPrivateKeyParser.parse(privateKeyContent); + PrivateKey privateKey = PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword()); addCertificates(store, certificates, privateKey); return store; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java index 978d14a1b4..81d68eb695 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreDetails.java @@ -30,11 +30,16 @@ import org.springframework.util.StringUtils; * that can be loaded by {@link ResourceUtils#getURL}) * @param privateKey the private key content (either the PEM content itself or something * that can be loaded by {@link ResourceUtils#getURL}) + * @param privateKeyPassword a password used to decrypt an encrypted private key * @author Scott Frederick * @author Phillip Webb * @since 3.1.0 */ -public record PemSslStoreDetails(String type, String certificate, String privateKey) { +public record PemSslStoreDetails(String type, String certificate, String privateKey, String privateKeyPassword) { + + public PemSslStoreDetails(String type, String certificate, String privateKey) { + this(type, certificate, privateKey, null); + } /** * Return a new {@link PemSslStoreDetails} instance with a new private key. @@ -42,7 +47,16 @@ public record PemSslStoreDetails(String type, String certificate, String private * @return a new {@link PemSslStoreDetails} instance */ public PemSslStoreDetails withPrivateKey(String privateKey) { - return new PemSslStoreDetails(this.type, this.certificate, privateKey); + return new PemSslStoreDetails(this.type, this.certificate, privateKey, this.privateKeyPassword); + } + + /** + * Return a new {@link PemSslStoreDetails} instance with a new private key password. + * @param password the new private key password + * @return a new {@link PemSslStoreDetails} instance + */ + public PemSslStoreDetails withPrivateKeyPassword(String password) { + return new PemSslStoreDetails(this.type, this.certificate, this.privateKey, password); } boolean isEmpty() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemPrivateKeyParserTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemPrivateKeyParserTests.java index 79bd3a11ee..82203b75dc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemPrivateKeyParserTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemPrivateKeyParserTests.java @@ -84,6 +84,47 @@ class PemPrivateKeyParserTests { assertThatIllegalStateException().isThrownBy(() -> PemPrivateKeyParser.parse(read("test-banner.txt"))); } + @Test + void parsePkcs8EncryptedRsaKeyFile() throws Exception { + // created with: + // openssl genpkey -aes-256-cbc -algorithm RSA \ + // -pkeyopt rsa_keygen_bits:4096 -out key-rsa-encrypted.key + PrivateKey privateKey = PemPrivateKeyParser.parse(read("ssl/pkcs8/key-rsa-encrypted.pem"), "test"); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("RSA"); + } + + @Test + void parsePkcs8EncryptedEcKeyFile() throws Exception { + // created with: + // openssl genpkey -aes-256-cbc -algorithm EC \ + // -pkeyopt ec_paramgen_curve:prime256v1 -out key-ec-encrypted.key + PrivateKey privateKey = PemPrivateKeyParser.parse(read("ssl/pkcs8/key-ec-encrypted.pem"), "test"); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("EC"); + } + + @Test + void failParsingPkcs1EncryptedKeyFile() throws Exception { + // created with: + // openssl genrsa -aes-256-cbc -out key-rsa-encrypted.pem + assertThatIllegalStateException() + .isThrownBy(() -> PemPrivateKeyParser.parse(read("ssl/pkcs1/key-rsa-encrypted.pem"), "test")) + .withMessageContaining("Unrecognized private key format"); + } + + @Test + void failParsingEcEncryptedKeyFile() throws Exception { + // created with: + // openssl ecparam -genkey -name prime256v1 | openssl ec -aes-128-cbc -out + // key-ec-prime256v1-encrypted.pem + assertThatIllegalStateException() + .isThrownBy(() -> PemPrivateKeyParser.parse(read("ssl/ec/key-ec-prime256v1-encrypted.pem"), "test")) + .withMessageContaining("Unrecognized private key format"); + } + private String read(String path) throws IOException { return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java index fd092261b1..61f5c4983b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemSslStoreBundleTests.java @@ -63,6 +63,17 @@ class PemSslStoreBundleTests { assertThat(bundle.getTrustStore()).isNull(); } + @Test + void whenHasKeyStoreDetailsCertAndEncryptedKey() { + PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem") + .withPrivateKey("classpath:ssl/pkcs8/key-rsa-encrypted.pem") + .withPrivateKeyPassword("test"); + PemSslStoreDetails trustStoreDetails = null; + PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails); + assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ssl")); + assertThat(bundle.getTrustStore()).isNull(); + } + @Test void whenHasKeyStoreDetailsAndTrustStoreDetailsWithoutKey() { PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem") diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/ec/key-ec-prime256v1-encrypted.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/ec/key-ec-prime256v1-encrypted.pem new file mode 100644 index 0000000000..ccecc24707 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/ec/key-ec-prime256v1-encrypted.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,3F26BBC4C7A6F3B5B3A5C03C7CC59B33 + +rp4qC+n+qIG+WKJp4BHQHk5z0oraaaLZvoVK5glESGx5IcR3mCsN7tdg2aZ7yEk+ +HnT4nQuM3R5pv248cmK0xDUje8N7FLe8lixVnEyQx3JdZfGdauowt9yaxL3AJypX +idWxNrxz1xff5RSMI6+PFv2SQpG0l794EpOjZxOUABM= +-----END EC PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs1/key-rsa-encrypted.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs1/key-rsa-encrypted.pem new file mode 100644 index 0000000000..4053ed5f6e --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs1/key-rsa-encrypted.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,2036BA8802B364C6BE415A48E1AC9966 + +0euP/daGIbl798XGc2Tc6u6NmnCl8C6hE0a2grO+wbvFEa13IT4hNVNTzqahfLb8 +STF9LXdvPbz9HybyWnG8KNm5sqsSY1omsHQI38khhwuhpHuWfjb62TqWobwrAmAU +qh2+kjc7upzW48axJLRQ69vfE4zKczZ4Sqv8qImPz9SiXPeaXinHf/wKfgXZZOvr +6A8nCykLglVrFbrZWydqKrTM9w/LTXsn0gNpDmrfZERDgWnabKnjYidHkXdgAoFy +ulsBjKvj2hJFtnJHTkCPXj6XAj18cwUWsN7inFJR5wqjIWXuLqfv/fNbK9lPzjAk +vMTEVWwynekvXFsSvnHAyEJfmRCNF6DOE+5VwjEROCFq8xceK4fz4T3BHC7XSgis +2ofBZwjCAJ0pP6SdRpbbZ0wlXH09wqDDTZ2vWD6bEG7hO9VuP/aO76MIWydLa78z +bDb2R6MnYKmYEInPzwfU4H66Z3/cz8orfYLlnF55DRd4rO+fJm6Y/XY14ac3x0S+ +P67s5e0WbdImagUsOxGNBjgOr84lwmtk/LoVd5nKho8JyQw3V7tiyrsKsM1lAXdS +TaJXKRnN6LbgC8d+Tgfjc/qMUdVqLlN8zATIa6E4sc861DDiJsneXuFm43pHc/ja +0sxSifBKgLGenj8xY5ANEfrYnXKrzaQemUnMzd+zan6OS02I+e8WTQevESt1j0D8 +7mgLJ9W2SfNNfiEd5OKar21lLNOc3POaFn7M73zL/gyhTROipPR3fpV+08k27UKl +nzGhFtRwQieXl34QjM0JrHokKKfv8FsAJsbrGnz2/wcm6jJGmrj5VVsogMKc90v4 +ZTM44NAKymM0UTuIp/rKb/UUVYDpl9VWDegWh2+XKX8io31ENrDMcvQTJ/mzNCuZ +SINrAeMbVD6W5L0i7THEt32YsDmbFsBaEJBlNXlUNBa/NCK0pjwAn5AYOFUvEqBC +oZUEleUMU6Q3TKI287o37euw3No6jo5VdPULlrwHsZAjmq9EUvRCgMoIEPdxv5XG +a4PljE7DQLlk6G9d+gjRzClLFfkadSTbtH9o2011RtWUKP4dcRuSOcBm64xVIO6J +rhe38sE3yzHvLUM/mvLsEM8B0AHl85nrEstlbBzftXMo2CAJ2Gs9c61PYfft+xiT +pRdHx4HB1P+kfHa96ayvAytOexHih2iVKVG5CchOr5tbWmkhXVE5cZAKzvczDYFd +YvniHNiqt6LO8EbJOzz+Yxessmd0zBXj/rjxTMdGwaRjiFI0BIguYpvmGoMS8+bp +spRj2DMtqjNZz68BEEfgKQwDHPCTblYSR+3Uw4sja608sflqQ8rTmOwbfta3SNS8 +Kq/zzWfYzarQ8hAk5n/H1Jm6AQdvcptyMjuF8FAiMnvt8xDCBRMD3xY2BHb/tcs9 +dBWBxhb76CKIrV3pzm9gGhhZ32Ndq9KmmE+bWCYyvrLxvPJxODfM7X5XamPmG5SH +wEKSbp0wPF0b6vyxt5M72OYnU8UnxYWu6PlbVvczfWEu9fIw/oIN/kDT/QUojTyt +wBgzVSTSNFaDtVXJw10IWQgsgWdNY5XueHKH060P5g+14woxU8i3TboNd+tC3hRl +-----END RSA PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-encrypted.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-encrypted.pem new file mode 100644 index 0000000000..ec36422309 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-encrypted.pem @@ -0,0 +1,7 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjZ2eXtLFXLdgICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIlOTfeoLwq9Bs9TFtw7VSMEgZAU +RO9W+K3vvzRG4sACrcFaB7PMhv3+HUOcPFf09QHa7aSJMWsb8EXGFBK4xlFhkK0/ +N0C8sRtH2N1JdYj9hiwS7I8WyybaR2W0ZALNR57iLz3WsOGQ7nVECFprElboqnSW +BtjkKTD9pz3xX/6cMkDV/2WqbS6Y/dzWJTH8yTTqjzTjbs4vguEK1Io6dlIRVqA= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa-encrypted.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa-encrypted.pem new file mode 100644 index 0000000000..638f6fd3fd --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa-encrypted.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY-----