Refine `PemContent` and PEM parsers
This commit is contained in:
parent
41724fc5f1
commit
25ce0ef3fc
|
|
@ -48,17 +48,17 @@ final class PemCertificateParser {
|
|||
|
||||
/**
|
||||
* Parse certificates from the specified string.
|
||||
* @param certificates the certificates to parse
|
||||
* @param text the text to parse
|
||||
* @return the parsed certificates
|
||||
*/
|
||||
static X509Certificate[] parse(String certificates) {
|
||||
if (certificates == null) {
|
||||
static List<X509Certificate> parse(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
CertificateFactory factory = getCertificateFactory();
|
||||
List<X509Certificate> certs = new ArrayList<>();
|
||||
readCertificates(certificates, factory, certs::add);
|
||||
return (!certs.isEmpty()) ? certs.toArray(X509Certificate[]::new) : null;
|
||||
readCertificates(text, factory, certs::add);
|
||||
return List.copyOf(certs);
|
||||
}
|
||||
|
||||
private static CertificateFactory getCertificateFactory() {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ import java.io.InputStreamReader;
|
|||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
|
@ -38,17 +42,56 @@ final class PemContent {
|
|||
|
||||
private static final Pattern PEM_FOOTER = Pattern.compile("-+END\\s+[^-]*-+", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private PemContent() {
|
||||
private String text;
|
||||
|
||||
private PemContent(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static String load(String content) {
|
||||
if (content == null || isPemContent(content)) {
|
||||
return content;
|
||||
List<X509Certificate> getCertificates() {
|
||||
return PemCertificateParser.parse(this.text);
|
||||
}
|
||||
|
||||
List<PrivateKey> getPrivateKeys() {
|
||||
return PemPrivateKeyParser.parse(this.text);
|
||||
}
|
||||
|
||||
List<PrivateKey> getPrivateKeys(String password) {
|
||||
return PemPrivateKeyParser.parse(this.text, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(this.text, ((PemContent) obj).text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
static PemContent load(String content) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
if (isPemContent(content)) {
|
||||
return new PemContent(content);
|
||||
}
|
||||
try {
|
||||
URL url = ResourceUtils.getURL(content);
|
||||
try (Reader reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) {
|
||||
return FileCopyUtils.copyToString(reader);
|
||||
return new PemContent(FileCopyUtils.copyToString(reader));
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
|
|
|
|||
|
|
@ -176,28 +176,28 @@ final class PemPrivateKeyParser {
|
|||
|
||||
/**
|
||||
* Parse a private key from the specified string.
|
||||
* @param key the private key to parse
|
||||
* @param text the text to parse
|
||||
* @return the parsed private key
|
||||
*/
|
||||
static PrivateKey[] parse(String key) {
|
||||
return parse(key, null);
|
||||
static List<PrivateKey> parse(String text) {
|
||||
return parse(text, 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 text the text 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) {
|
||||
static List<PrivateKey> parse(String text, String password) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
List<PrivateKey> keys = new ArrayList<>();
|
||||
try {
|
||||
for (PemParser pemParser : PEM_PARSERS) {
|
||||
PrivateKey privateKey = pemParser.parse(key, password);
|
||||
PrivateKey privateKey = pemParser.parse(text, password);
|
||||
if (privateKey != null) {
|
||||
keys.add(privateKey);
|
||||
}
|
||||
|
|
@ -206,7 +206,7 @@ final class PemPrivateKeyParser {
|
|||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Error loading private key file: " + ex.getMessage(), ex);
|
||||
}
|
||||
return keys.toArray(PrivateKey[]::new);
|
||||
return List.copyOf(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -150,20 +151,20 @@ public class PemSslStoreBundle implements SslStoreBundle {
|
|||
}
|
||||
|
||||
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) {
|
||||
String privateKeyContent = PemContent.load(details.privateKey());
|
||||
if (privateKeyContent == null) {
|
||||
PemContent pemContent = PemContent.load(details.privateKey());
|
||||
if (pemContent == null) {
|
||||
return null;
|
||||
}
|
||||
PrivateKey[] privateKeys = PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword());
|
||||
Assert.state(!ObjectUtils.isEmpty(privateKeys), "Loaded private keys are empty");
|
||||
return privateKeys[0];
|
||||
List<PrivateKey> privateKeys = pemContent.getPrivateKeys(details.privateKeyPassword());
|
||||
Assert.state(!CollectionUtils.isEmpty(privateKeys), "Loaded private keys are empty");
|
||||
return privateKeys.get(0);
|
||||
}
|
||||
|
||||
private static X509Certificate[] loadCertificates(PemSslStoreDetails details) {
|
||||
String certificateContent = PemContent.load(details.certificate());
|
||||
X509Certificate[] certificates = PemCertificateParser.parse(certificateContent);
|
||||
Assert.state(!ObjectUtils.isEmpty(certificates), "Loaded certificates are empty");
|
||||
return certificates;
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.ssl.pem;
|
|||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -35,19 +36,19 @@ class PemCertificateParserTests {
|
|||
|
||||
@Test
|
||||
void parseCertificate() throws Exception {
|
||||
X509Certificate[] certificates = PemCertificateParser.parse(read("test-cert.pem"));
|
||||
List<X509Certificate> certificates = PemCertificateParser.parse(read("test-cert.pem"));
|
||||
assertThat(certificates).isNotNull();
|
||||
assertThat(certificates).hasSize(1);
|
||||
assertThat(certificates[0].getType()).isEqualTo("X.509");
|
||||
assertThat(certificates.get(0).getType()).isEqualTo("X.509");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCertificateChain() throws Exception {
|
||||
X509Certificate[] certificates = PemCertificateParser.parse(read("test-cert-chain.pem"));
|
||||
List<X509Certificate> certificates = PemCertificateParser.parse(read("test-cert-chain.pem"));
|
||||
assertThat(certificates).isNotNull();
|
||||
assertThat(certificates).hasSize(2);
|
||||
assertThat(certificates[0].getType()).isEqualTo("X.509");
|
||||
assertThat(certificates[1].getType()).isEqualTo("X.509");
|
||||
assertThat(certificates.get(0).getType()).isEqualTo("X.509");
|
||||
assertThat(certificates.get(1).getType()).isEqualTo("X.509");
|
||||
}
|
||||
|
||||
private String read(String path) throws IOException {
|
||||
|
|
|
|||
|
|
@ -57,19 +57,19 @@ class PemContentTests {
|
|||
+lGuHKdhNOVW9CmqPD1y76o6c8PQKuF7KZEoY2jvy3GeIfddBvqXgZ4PbWvFz1jO
|
||||
32C9XWHwRA4=
|
||||
-----END CERTIFICATE-----""";
|
||||
assertThat(PemContent.load(content)).isEqualTo(content);
|
||||
assertThat(PemContent.load(content)).hasToString(content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenClasspathLocationReturnsContent() throws IOException {
|
||||
String actual = PemContent.load("classpath:test-cert.pem");
|
||||
String actual = PemContent.load("classpath:test-cert.pem").toString();
|
||||
String expected = new ClassPathResource("test-cert.pem").getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenFileLocationReturnsContent() throws IOException {
|
||||
String actual = PemContent.load("src/test/resources/test-cert.pem");
|
||||
String actual = PemContent.load("src/test/resources/test-cert.pem").toString();
|
||||
String expected = new ClassPathResource("test-cert.pem").getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
|
@ -219,9 +220,9 @@ class PemPrivateKeyParserTests {
|
|||
// -passout pass:test
|
||||
// where <algorithm> is aes128 or aes256
|
||||
String content = read("org/springframework/boot/web/server/pkcs8/" + file);
|
||||
PrivateKey[] privateKeys = PemPrivateKeyParser.parse(content, "test");
|
||||
List<PrivateKey> privateKeys = PemPrivateKeyParser.parse(content, "test");
|
||||
assertThat(privateKeys).isNotEmpty();
|
||||
PrivateKey privateKey = privateKeys[0];
|
||||
PrivateKey privateKey = privateKeys.get(0);
|
||||
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
|
||||
assertThat(privateKey.getAlgorithm()).isEqualTo(algorithm);
|
||||
}
|
||||
|
|
@ -268,8 +269,8 @@ class PemPrivateKeyParserTests {
|
|||
}
|
||||
|
||||
private PrivateKey parse(String key) {
|
||||
PrivateKey[] keys = PemPrivateKeyParser.parse(key);
|
||||
return (!ObjectUtils.isEmpty(keys)) ? keys[0] : null;
|
||||
List<PrivateKey> keys = PemPrivateKeyParser.parse(key);
|
||||
return (!ObjectUtils.isEmpty(keys)) ? keys.get(0) : null;
|
||||
}
|
||||
|
||||
private String read(String path) throws IOException {
|
||||
|
|
|
|||
Loading…
Reference in New Issue