mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Introduce PemSslStore
as an alternative to PemSslStoreDetails
.
Add a `PemSslStore` interface that can be used as an alternative to `PemSslStoreDetails` when PEM content has already been loaded and parsed. Closes gh-38175
This commit is contained in:
parent
2b39ec6f60
commit
5e5d2265f5
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.ssl.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link PemSslStore} loaded from {@link PemSslStoreDetails}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see PemSslStore#load(PemSslStoreDetails)
|
||||
*/
|
||||
final class LoadedPemSslStore implements PemSslStore {
|
||||
|
||||
private final PemSslStoreDetails details;
|
||||
|
||||
private final List<X509Certificate> certificates;
|
||||
|
||||
private final PrivateKey privateKey;
|
||||
|
||||
LoadedPemSslStore(PemSslStoreDetails details) throws IOException {
|
||||
Assert.notNull(details, "Details must not be null");
|
||||
this.details = details;
|
||||
this.certificates = loadCertificates(details);
|
||||
this.privateKey = loadPrivateKey(details);
|
||||
}
|
||||
|
||||
private static List<X509Certificate> loadCertificates(PemSslStoreDetails details) throws IOException {
|
||||
PemContent pemContent = PemContent.load(details.certificates());
|
||||
if (pemContent == null) {
|
||||
return null;
|
||||
}
|
||||
List<X509Certificate> certificates = pemContent.getCertificates();
|
||||
Assert.state(!CollectionUtils.isEmpty(certificates), "Loaded certificates are empty");
|
||||
return certificates;
|
||||
}
|
||||
|
||||
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) throws IOException {
|
||||
PemContent pemContent = PemContent.load(details.privateKey());
|
||||
return (pemContent != null) ? pemContent.getPrivateKey(details.privateKeyPassword()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return this.details.type();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alias() {
|
||||
return this.details.alias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String password() {
|
||||
return this.details.password();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<X509Certificate> certificates() {
|
||||
return this.certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey privateKey() {
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.ssl.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An individual trust or key store that has been loaded from PEM content.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.2.0
|
||||
* @see PemSslStoreDetails
|
||||
* @see PemContent
|
||||
*/
|
||||
public interface PemSslStore {
|
||||
|
||||
/**
|
||||
* The key store type, for example {@code JKS} or {@code PKCS11}. A {@code null} value
|
||||
* will use {@link KeyStore#getDefaultType()}).
|
||||
* @return the key store type
|
||||
*/
|
||||
String type();
|
||||
|
||||
/**
|
||||
* The alias used when setting entries in the {@link KeyStore}.
|
||||
* @return the alias
|
||||
*/
|
||||
String alias();
|
||||
|
||||
/**
|
||||
* the password used
|
||||
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||
* setting key entries} in the {@link KeyStore}.
|
||||
* @return the password
|
||||
*/
|
||||
String password();
|
||||
|
||||
/**
|
||||
* The certificates for this store. When a {@link #privateKey() private key} is
|
||||
* present the returned value is treated as a certificate chain, otherwise it is
|
||||
* treated a list of certificates that should all be registered.
|
||||
* @return the X509 certificates
|
||||
*/
|
||||
List<X509Certificate> certificates();
|
||||
|
||||
/**
|
||||
* The private key for this store or {@code null}.
|
||||
* @return the private key
|
||||
*/
|
||||
PrivateKey privateKey();
|
||||
|
||||
/**
|
||||
* Return a new {@link PemSslStore} instance with a new alias.
|
||||
* @param alias the new alias
|
||||
* @return a new {@link PemSslStore} instance
|
||||
*/
|
||||
default PemSslStore withAlias(String alias) {
|
||||
return of(type(), alias, password(), certificates(), privateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link PemSslStore} instance with a new password.
|
||||
* @param password the new password
|
||||
* @return a new {@link PemSslStore} instance
|
||||
*/
|
||||
default PemSslStore withPassword(String password) {
|
||||
return of(type(), alias(), password, certificates(), privateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PemSslStore} instance loaded using the given
|
||||
* {@link PemSslStoreDetails}.
|
||||
* @param details the PEM store details
|
||||
* @return a loaded {@link PemSslStore} or {@code null}.
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
static PemSslStore load(PemSslStoreDetails details) throws IOException {
|
||||
if (details == null || details.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new LoadedPemSslStore(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that can be used to create a new {@link PemSslStore} with the given
|
||||
* values.
|
||||
* @param type the key store type
|
||||
* @param certificates the certificates for this store
|
||||
* @param privateKey the private key
|
||||
* @return a new {@link PemSslStore} instance
|
||||
*/
|
||||
static PemSslStore of(String type, List<X509Certificate> certificates, PrivateKey privateKey) {
|
||||
return of(type, null, null, certificates, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that can be used to create a new {@link PemSslStore} with the given
|
||||
* values.
|
||||
* @param certificates the certificates for this store
|
||||
* @param privateKey the private key
|
||||
* @return a new {@link PemSslStore} instance
|
||||
*/
|
||||
static PemSslStore of(List<X509Certificate> certificates, PrivateKey privateKey) {
|
||||
return of(null, null, null, certificates, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that can be used to create a new {@link PemSslStore} with the given
|
||||
* values.
|
||||
* @param type the key store type
|
||||
* @param alias the alias used when setting entries in the {@link KeyStore}
|
||||
* @param password the password used
|
||||
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||
* setting key entries} in the {@link KeyStore}
|
||||
* @param certificates the certificates for this store
|
||||
* @param privateKey the private key
|
||||
* @return a new {@link PemSslStore} instance
|
||||
*/
|
||||
static PemSslStore of(String type, String alias, String password, List<X509Certificate> certificates,
|
||||
PrivateKey privateKey) {
|
||||
Assert.notEmpty(certificates, "Certificates must not be empty");
|
||||
return new PemSslStore() {
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String password() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<X509Certificate> certificates() {
|
||||
return certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey privateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.ssl.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -27,7 +28,6 @@ import java.util.List;
|
||||
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@ -52,7 +52,7 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
* @param trustStoreDetails the trust store details
|
||||
*/
|
||||
public PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails) {
|
||||
this(keyStoreDetails, trustStoreDetails, null);
|
||||
this(keyStoreDetails, trustStoreDetails, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,8 +83,26 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
|
||||
private PemSslStoreBundle(PemSslStoreDetails keyStoreDetails, PemSslStoreDetails trustStoreDetails, String alias,
|
||||
boolean verifyKeys) {
|
||||
this.keyStore = createKeyStore("key", keyStoreDetails, alias, verifyKeys);
|
||||
this.trustStore = createKeyStore("trust", trustStoreDetails, alias, verifyKeys);
|
||||
try {
|
||||
this.keyStore = createKeyStore("key", PemSslStore.load(keyStoreDetails), alias, verifyKeys);
|
||||
this.trustStore = createKeyStore("trust", PemSslStore.load(trustStoreDetails), alias, verifyKeys);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link PemSslStoreBundle} instance.
|
||||
* @param pemKeyStore the PEM key store
|
||||
* @param pemTrustStore the PEM trust store
|
||||
* @param alias the alias to use or {@code null} to use a default alias
|
||||
* @param verifyKeys whether to verify that the private key matches the public key
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public PemSslStoreBundle(PemSslStore pemKeyStore, PemSslStore pemTrustStore, String alias, boolean verifyKeys) {
|
||||
this.keyStore = createKeyStore("key", pemKeyStore, alias, verifyKeys);
|
||||
this.trustStore = createKeyStore("trust", pemTrustStore, alias, verifyKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,22 +120,22 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
return this.trustStore;
|
||||
}
|
||||
|
||||
private static KeyStore createKeyStore(String name, PemSslStoreDetails details, String alias, boolean verifyKeys) {
|
||||
if (details == null || details.isEmpty()) {
|
||||
private static KeyStore createKeyStore(String name, PemSslStore pemSslStore, String alias, boolean verifyKeys) {
|
||||
if (pemSslStore == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Assert.notNull(details.certificate(), "Certificate content must not be null");
|
||||
alias = (details.alias() != null) ? details.alias() : alias;
|
||||
Assert.notEmpty(pemSslStore.certificates(), "Certificates must not be empty");
|
||||
alias = (pemSslStore.alias() != null) ? pemSslStore.alias() : alias;
|
||||
alias = (alias != null) ? alias : DEFAULT_ALIAS;
|
||||
KeyStore store = createKeyStore(details);
|
||||
X509Certificate[] certificates = loadCertificates(details);
|
||||
PrivateKey privateKey = loadPrivateKey(details);
|
||||
KeyStore store = createKeyStore(pemSslStore.type());
|
||||
List<X509Certificate> certificates = pemSslStore.certificates();
|
||||
PrivateKey privateKey = pemSslStore.privateKey();
|
||||
if (privateKey != null) {
|
||||
if (verifyKeys) {
|
||||
verifyKeys(privateKey, certificates);
|
||||
}
|
||||
addPrivateKey(store, privateKey, alias, details.password(), certificates);
|
||||
addPrivateKey(store, privateKey, alias, pemSslStore.password(), certificates);
|
||||
}
|
||||
else {
|
||||
addCertificates(store, certificates, alias);
|
||||
@ -129,50 +147,37 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyKeys(PrivateKey privateKey, X509Certificate[] certificates) {
|
||||
KeyVerifier keyVerifier = new KeyVerifier();
|
||||
// Key should match one of the certificates
|
||||
for (X509Certificate certificate : certificates) {
|
||||
Result result = keyVerifier.matches(privateKey, certificate.getPublicKey());
|
||||
if (result == Result.YES) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Private key matches none of the certificates");
|
||||
}
|
||||
|
||||
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) throws IOException {
|
||||
PemContent pemContent = PemContent.load(details.privateKey());
|
||||
if (pemContent == null) {
|
||||
return null;
|
||||
}
|
||||
return pemContent.getPrivateKey(details.privateKeyPassword());
|
||||
}
|
||||
|
||||
private static X509Certificate[] loadCertificates(PemSslStoreDetails details) throws IOException {
|
||||
PemContent pemContent = PemContent.load(details.certificate());
|
||||
List<X509Certificate> certificates = pemContent.getCertificates();
|
||||
Assert.state(!CollectionUtils.isEmpty(certificates), "Loaded certificates are empty");
|
||||
return certificates.toArray(X509Certificate[]::new);
|
||||
}
|
||||
|
||||
private static KeyStore createKeyStore(PemSslStoreDetails details)
|
||||
private static KeyStore createKeyStore(String type)
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
|
||||
String type = StringUtils.hasText(details.type()) ? details.type() : KeyStore.getDefaultType();
|
||||
KeyStore store = KeyStore.getInstance(type);
|
||||
KeyStore store = KeyStore.getInstance(StringUtils.hasText(type) ? type : KeyStore.getDefaultType());
|
||||
store.load(null);
|
||||
return store;
|
||||
}
|
||||
|
||||
private static void addPrivateKey(KeyStore keyStore, PrivateKey privateKey, String alias, String keyPassword,
|
||||
X509Certificate[] certificates) throws KeyStoreException {
|
||||
keyStore.setKeyEntry(alias, privateKey, (keyPassword != null) ? keyPassword.toCharArray() : null, certificates);
|
||||
private static void verifyKeys(PrivateKey privateKey, List<X509Certificate> certificateChain) {
|
||||
KeyVerifier keyVerifier = new KeyVerifier();
|
||||
// Key should match one of the certificates
|
||||
for (X509Certificate certificate : certificateChain) {
|
||||
KeyVerifier.Result result = keyVerifier.matches(privateKey, certificate.getPublicKey());
|
||||
if (result == KeyVerifier.Result.YES) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Private key matches none of the certificates in the chain");
|
||||
}
|
||||
|
||||
private static void addCertificates(KeyStore keyStore, X509Certificate[] certificates, String alias)
|
||||
private static void addPrivateKey(KeyStore keyStore, PrivateKey privateKey, String alias, String keyPassword,
|
||||
List<X509Certificate> certificateChain) throws KeyStoreException {
|
||||
keyStore.setKeyEntry(alias, privateKey, (keyPassword != null) ? keyPassword.toCharArray() : null,
|
||||
certificateChain.toArray(X509Certificate[]::new));
|
||||
}
|
||||
|
||||
private static void addCertificates(KeyStore keyStore, List<X509Certificate> certificates, String alias)
|
||||
throws KeyStoreException {
|
||||
for (int index = 0; index < certificates.length; index++) {
|
||||
keyStore.setCertificateEntry(alias + "-" + index, certificates[index]);
|
||||
for (int index = 0; index < certificates.size(); index++) {
|
||||
String entryAlias = alias + ((certificates.size() == 1) ? "" : "-" + index);
|
||||
X509Certificate certificate = certificates.get(index);
|
||||
keyStore.setCertificateEntry(entryAlias, certificate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,16 +30,19 @@ import org.springframework.util.StringUtils;
|
||||
* @param password the password used
|
||||
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||
* setting key entries} in the {@link KeyStore}
|
||||
* @param certificate the certificate content (either the PEM content itself or something
|
||||
* that can be loaded by {@link ResourceUtils#getURL})
|
||||
* @param certificates the certificates content (either the PEM content itself or
|
||||
* something that can be loaded by {@link ResourceUtils#getURL}). When a
|
||||
* {@link #privateKey() private key} is present this value is treated as a certificate
|
||||
* chain, otherwise it is treated a list of certificates that should all be registered.
|
||||
* @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
|
||||
* @see PemSslStore#load(PemSslStoreDetails)
|
||||
*/
|
||||
public record PemSslStoreDetails(String type, String alias, String password, String certificate, String privateKey,
|
||||
public record PemSslStoreDetails(String type, String alias, String password, String certificates, String privateKey,
|
||||
String privateKeyPassword) {
|
||||
|
||||
/**
|
||||
@ -50,7 +53,7 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
* @param password the password used
|
||||
* {@link KeyStore#setKeyEntry(String, java.security.Key, char[], java.security.cert.Certificate[])
|
||||
* setting key entries} in the {@link KeyStore}
|
||||
* @param certificate the certificate content (either the PEM content itself or
|
||||
* @param certificates the certificate content (either the PEM content itself or
|
||||
* something 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})
|
||||
@ -87,6 +90,16 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
this(type, certificate, privateKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the certificate content.
|
||||
* @return the certificate content
|
||||
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of {@link #certificates()}
|
||||
*/
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public String certificate() {
|
||||
return certificates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link PemSslStoreDetails} instance with a new alias.
|
||||
* @param alias the new alias
|
||||
@ -94,7 +107,7 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public PemSslStoreDetails withAlias(String alias) {
|
||||
return new PemSslStoreDetails(this.type, alias, this.password, this.certificate, this.privateKey,
|
||||
return new PemSslStoreDetails(this.type, alias, this.password, this.certificates, this.privateKey,
|
||||
this.privateKeyPassword);
|
||||
}
|
||||
|
||||
@ -105,7 +118,7 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public PemSslStoreDetails withPassword(String password) {
|
||||
return new PemSslStoreDetails(this.type, this.alias, password, this.certificate, this.privateKey,
|
||||
return new PemSslStoreDetails(this.type, this.alias, password, this.certificates, this.privateKey,
|
||||
this.privateKeyPassword);
|
||||
}
|
||||
|
||||
@ -115,7 +128,7 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
* @return a new {@link PemSslStoreDetails} instance
|
||||
*/
|
||||
public PemSslStoreDetails withPrivateKey(String privateKey) {
|
||||
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, privateKey,
|
||||
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificates, privateKey,
|
||||
this.privateKeyPassword);
|
||||
}
|
||||
|
||||
@ -125,12 +138,12 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
* @return a new {@link PemSslStoreDetails} instance
|
||||
*/
|
||||
public PemSslStoreDetails withPrivateKeyPassword(String privateKeyPassword) {
|
||||
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificate, this.privateKey,
|
||||
return new PemSslStoreDetails(this.type, this.alias, this.password, this.certificates, this.privateKey,
|
||||
privateKeyPassword);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return isEmpty(this.type) && isEmpty(this.certificate) && isEmpty(this.privateKey);
|
||||
return isEmpty(this.type) && isEmpty(this.certificates) && isEmpty(this.privateKey);
|
||||
}
|
||||
|
||||
private boolean isEmpty(String value) {
|
||||
@ -139,12 +152,27 @@ public record PemSslStoreDetails(String type, String alias, String password, Str
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link PemSslStoreDetails} instance for the given
|
||||
* certificate.
|
||||
* @param certificate the certificate
|
||||
* certificate. <b>Note:</b> This method doesn't actually check if the provided value
|
||||
* only contains a single certificate. It is functionally equivalent to
|
||||
* {@link #forCertificates(String)}.
|
||||
* @param certificate the certificate content (either the PEM content itself or
|
||||
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||
* @return a new {@link PemSslStoreDetails} instance.
|
||||
*/
|
||||
public static PemSslStoreDetails forCertificate(String certificate) {
|
||||
return new PemSslStoreDetails(null, certificate, null);
|
||||
return forCertificates(certificate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link PemSslStoreDetails} instance for the given
|
||||
* certificates.
|
||||
* @param certificates the certificates content (either the PEM content itself or
|
||||
* something that can be loaded by {@link ResourceUtils#getURL})
|
||||
* @return a new {@link PemSslStoreDetails} instance.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static PemSslStoreDetails forCertificates(String certificates) {
|
||||
return new PemSslStoreDetails(null, certificates, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,9 @@
|
||||
package org.springframework.boot.ssl.pem;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -94,7 +97,7 @@ class PemSslStoreBundleTests {
|
||||
private static final char[] EMPTY_KEY_PASSWORD = new char[] {};
|
||||
|
||||
@Test
|
||||
void whenNullStores() {
|
||||
void createWithDetailsWhenNullStores() {
|
||||
PemSslStoreDetails keyStoreDetails = null;
|
||||
PemSslStoreDetails trustStoreDetails = null;
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
@ -104,7 +107,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenStoresHaveNoValues() {
|
||||
void createWithDetailsWhenStoresHaveNoValues() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate(null);
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate(null);
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
@ -114,7 +117,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsCertAndKey() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsCertAndKey() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStoreDetails trustStoreDetails = null;
|
||||
@ -124,7 +127,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsCertAndEncryptedKey() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsCertAndEncryptedKey() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:ssl/pkcs8/key-rsa-encrypted.pem")
|
||||
.withPrivateKeyPassword("test");
|
||||
@ -135,17 +138,17 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsWithoutKey() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsWithoutKey() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem");
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ssl"));
|
||||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCert("ssl-0"));
|
||||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCert("ssl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetails() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetails() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
@ -156,7 +159,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasEmbeddedKeyStoreDetailsAndTrustStoreDetails() {
|
||||
void createWithDetailsWhenHasEmbeddedKeyStoreDetailsAndTrustStoreDetails() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate(CERTIFICATE).withPrivateKey(PRIVATE_KEY);
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate(CERTIFICATE)
|
||||
.withPrivateKey(PRIVATE_KEY);
|
||||
@ -167,7 +170,7 @@ class PemSslStoreBundleTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndAlias() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem");
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
@ -178,7 +181,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasStoreType() {
|
||||
void createWithDetailsWhenHasStoreType() {
|
||||
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails("PKCS12", "classpath:test-cert.pem",
|
||||
"classpath:test-key.pem");
|
||||
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails("PKCS12", "classpath:test-cert.pem",
|
||||
@ -189,7 +192,7 @@ class PemSslStoreBundleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
|
||||
void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate("classpath:test-cert.pem")
|
||||
.withPrivateKey("classpath:test-key.pem")
|
||||
.withAlias("ksa")
|
||||
@ -217,7 +220,7 @@ class PemSslStoreBundleTests {
|
||||
@Test
|
||||
void shouldVerifyKeysIfEnabledAndCertificateChainIsUsed() {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails
|
||||
.forCertificate("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
|
||||
.forCertificates("classpath:org/springframework/boot/ssl/pem/key2-chain.crt")
|
||||
.withPrivateKey("classpath:org/springframework/boot/ssl/pem/key2.pem")
|
||||
.withAlias("test-alias")
|
||||
.withPassword("keysecret");
|
||||
@ -234,6 +237,16 @@ class PemSslStoreBundleTests {
|
||||
.withMessageContaining("Private key matches none of the certificates");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithPemSslStoreCreatesInstance() {
|
||||
List<X509Certificate> certificates = PemContent.of(CERTIFICATE).getCertificates();
|
||||
PrivateKey privateKey = PemContent.of(PRIVATE_KEY).getPrivateKey();
|
||||
PemSslStore pemSslStore = PemSslStore.of(certificates, privateKey);
|
||||
PemSslStoreBundle bundle = new PemSslStoreBundle(pemSslStore, pemSslStore, null, false);
|
||||
assertThat(bundle.getKeyStore()).satisfies(storeContainingCertAndKey("ssl"));
|
||||
assertThat(bundle.getTrustStore()).satisfies(storeContainingCertAndKey("ssl"));
|
||||
}
|
||||
|
||||
private Consumer<KeyStore> storeContainingCert(String keyAlias) {
|
||||
return storeContainingCert(KeyStore.getDefaultType(), keyAlias);
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.ssl.pem;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PemSslStore}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PemSslStoreTests {
|
||||
|
||||
@Test
|
||||
void withAliasReturnsStoreWithNewAlias() {
|
||||
List<X509Certificate> certificates = List.of(mock(X509Certificate.class));
|
||||
PrivateKey privateKey = mock(PrivateKey.class);
|
||||
PemSslStore store = PemSslStore.of("type", "alias", "secret", certificates, privateKey);
|
||||
assertThat(store.withAlias("newalias").alias()).isEqualTo("newalias");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPasswordReturnsStoreWithNewPassword() {
|
||||
List<X509Certificate> certificates = List.of(mock(X509Certificate.class));
|
||||
PrivateKey privateKey = mock(PrivateKey.class);
|
||||
PemSslStore store = PemSslStore.of("type", "alias", "secret", certificates, privateKey);
|
||||
assertThat(store.withPassword("newsecret").password()).isEqualTo("newsecret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenNullCertificatesThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> PemSslStore.of(null, null, null, null, null))
|
||||
.withMessage("Certificates must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenEmptyCertificatesThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> PemSslStore.of(null, null, null, Collections.emptyList(), null))
|
||||
.withMessage("Certificates must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofReturnsPemSslStore() {
|
||||
List<X509Certificate> certificates = List.of(mock(X509Certificate.class));
|
||||
PrivateKey privateKey = mock(PrivateKey.class);
|
||||
PemSslStore store = PemSslStore.of("type", "alias", "password", certificates, privateKey);
|
||||
assertThat(store.type()).isEqualTo("type");
|
||||
assertThat(store.alias()).isEqualTo("alias");
|
||||
assertThat(store.password()).isEqualTo("password");
|
||||
assertThat(store.certificates()).isEqualTo(certificates);
|
||||
assertThat(store.privateKey()).isEqualTo(privateKey);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user