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
This commit is contained in:
Scott Frederick
2023-06-06 13:47:17 -05:00
parent 7fcfcadfc3
commit 767ec4e22e
12 changed files with 355 additions and 17 deletions

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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");
}
}