Skip to content

Commit 25ce0ef

Browse files
committed
Refine PemContent and PEM parsers
1 parent 41724fc commit 25ce0ef

File tree

7 files changed

+86
-40
lines changed

7 files changed

+86
-40
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemCertificateParser.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@ private PemCertificateParser() {
4848

4949
/**
5050
* Parse certificates from the specified string.
51-
* @param certificates the certificates to parse
51+
* @param text the text to parse
5252
* @return the parsed certificates
5353
*/
54-
static X509Certificate[] parse(String certificates) {
55-
if (certificates == null) {
54+
static List<X509Certificate> parse(String text) {
55+
if (text == null) {
5656
return null;
5757
}
5858
CertificateFactory factory = getCertificateFactory();
5959
List<X509Certificate> certs = new ArrayList<>();
60-
readCertificates(certificates, factory, certs::add);
61-
return (!certs.isEmpty()) ? certs.toArray(X509Certificate[]::new) : null;
60+
readCertificates(text, factory, certs::add);
61+
return List.copyOf(certs);
6262
}
6363

6464
private static CertificateFactory getCertificateFactory() {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemContent.java

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
import java.io.Reader;
2222
import java.net.URL;
2323
import java.nio.charset.StandardCharsets;
24+
import java.security.PrivateKey;
25+
import java.security.cert.X509Certificate;
26+
import java.util.List;
27+
import java.util.Objects;
2428
import java.util.regex.Pattern;
2529

2630
import org.springframework.util.FileCopyUtils;
@@ -38,17 +42,56 @@ final class PemContent {
3842

3943
private static final Pattern PEM_FOOTER = Pattern.compile("-+END\\s+[^-]*-+", Pattern.CASE_INSENSITIVE);
4044

41-
private PemContent() {
45+
private String text;
46+
47+
private PemContent(String text) {
48+
this.text = text;
49+
}
50+
51+
List<X509Certificate> getCertificates() {
52+
return PemCertificateParser.parse(this.text);
53+
}
54+
55+
List<PrivateKey> getPrivateKeys() {
56+
return PemPrivateKeyParser.parse(this.text);
57+
}
58+
59+
List<PrivateKey> getPrivateKeys(String password) {
60+
return PemPrivateKeyParser.parse(this.text, password);
61+
}
62+
63+
@Override
64+
public boolean equals(Object obj) {
65+
if (this == obj) {
66+
return true;
67+
}
68+
if (obj == null || getClass() != obj.getClass()) {
69+
return false;
70+
}
71+
return Objects.equals(this.text, ((PemContent) obj).text);
72+
}
73+
74+
@Override
75+
public int hashCode() {
76+
return Objects.hash(this.text);
77+
}
78+
79+
@Override
80+
public String toString() {
81+
return this.text;
4282
}
4383

44-
static String load(String content) {
45-
if (content == null || isPemContent(content)) {
46-
return content;
84+
static PemContent load(String content) {
85+
if (content == null) {
86+
return null;
87+
}
88+
if (isPemContent(content)) {
89+
return new PemContent(content);
4790
}
4891
try {
4992
URL url = ResourceUtils.getURL(content);
5093
try (Reader reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) {
51-
return FileCopyUtils.copyToString(reader);
94+
return new PemContent(FileCopyUtils.copyToString(reader));
5295
}
5396
}
5497
catch (IOException ex) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,28 +176,28 @@ private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes,
176176

177177
/**
178178
* Parse a private key from the specified string.
179-
* @param key the private key to parse
179+
* @param text the text to parse
180180
* @return the parsed private key
181181
*/
182-
static PrivateKey[] parse(String key) {
183-
return parse(key, null);
182+
static List<PrivateKey> parse(String text) {
183+
return parse(text, null);
184184
}
185185

186186
/**
187187
* Parse a private key from the specified string, using the provided password for
188188
* decryption if necessary.
189-
* @param key the private key to parse
189+
* @param text the text to parse
190190
* @param password the password used to decrypt an encrypted private key
191191
* @return the parsed private key
192192
*/
193-
static PrivateKey[] parse(String key, String password) {
194-
if (key == null) {
193+
static List<PrivateKey> parse(String text, String password) {
194+
if (text == null) {
195195
return null;
196196
}
197197
List<PrivateKey> keys = new ArrayList<>();
198198
try {
199199
for (PemParser pemParser : PEM_PARSERS) {
200-
PrivateKey privateKey = pemParser.parse(key, password);
200+
PrivateKey privateKey = pemParser.parse(text, password);
201201
if (privateKey != null) {
202202
keys.add(privateKey);
203203
}
@@ -206,7 +206,7 @@ static PrivateKey[] parse(String key, String password) {
206206
catch (Exception ex) {
207207
throw new IllegalStateException("Error loading private key file: " + ex.getMessage(), ex);
208208
}
209-
return keys.toArray(PrivateKey[]::new);
209+
return List.copyOf(keys);
210210
}
211211

212212
/**

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemSslStoreBundle.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
import java.security.PrivateKey;
2424
import java.security.cert.CertificateException;
2525
import java.security.cert.X509Certificate;
26+
import java.util.List;
2627

2728
import org.springframework.boot.ssl.SslStoreBundle;
2829
import org.springframework.boot.ssl.pem.KeyVerifier.Result;
2930
import org.springframework.util.Assert;
30-
import org.springframework.util.ObjectUtils;
31+
import org.springframework.util.CollectionUtils;
3132
import org.springframework.util.StringUtils;
3233

3334
/**
@@ -150,20 +151,20 @@ private static void verifyKeys(PrivateKey privateKey, X509Certificate[] certific
150151
}
151152

152153
private static PrivateKey loadPrivateKey(PemSslStoreDetails details) {
153-
String privateKeyContent = PemContent.load(details.privateKey());
154-
if (privateKeyContent == null) {
154+
PemContent pemContent = PemContent.load(details.privateKey());
155+
if (pemContent == null) {
155156
return null;
156157
}
157-
PrivateKey[] privateKeys = PemPrivateKeyParser.parse(privateKeyContent, details.privateKeyPassword());
158-
Assert.state(!ObjectUtils.isEmpty(privateKeys), "Loaded private keys are empty");
159-
return privateKeys[0];
158+
List<PrivateKey> privateKeys = pemContent.getPrivateKeys(details.privateKeyPassword());
159+
Assert.state(!CollectionUtils.isEmpty(privateKeys), "Loaded private keys are empty");
160+
return privateKeys.get(0);
160161
}
161162

162163
private static X509Certificate[] loadCertificates(PemSslStoreDetails details) {
163-
String certificateContent = PemContent.load(details.certificate());
164-
X509Certificate[] certificates = PemCertificateParser.parse(certificateContent);
165-
Assert.state(!ObjectUtils.isEmpty(certificates), "Loaded certificates are empty");
166-
return certificates;
164+
PemContent pemContent = PemContent.load(details.certificate());
165+
List<X509Certificate> certificates = pemContent.getCertificates();
166+
Assert.state(!CollectionUtils.isEmpty(certificates), "Loaded certificates are empty");
167+
return certificates.toArray(X509Certificate[]::new);
167168
}
168169

169170
private static KeyStore createKeyStore(PemSslStoreDetails details)

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemCertificateParserTests.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.nio.charset.StandardCharsets;
2121
import java.security.cert.X509Certificate;
22+
import java.util.List;
2223

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

@@ -35,19 +36,19 @@ class PemCertificateParserTests {
3536

3637
@Test
3738
void parseCertificate() throws Exception {
38-
X509Certificate[] certificates = PemCertificateParser.parse(read("test-cert.pem"));
39+
List<X509Certificate> certificates = PemCertificateParser.parse(read("test-cert.pem"));
3940
assertThat(certificates).isNotNull();
4041
assertThat(certificates).hasSize(1);
41-
assertThat(certificates[0].getType()).isEqualTo("X.509");
42+
assertThat(certificates.get(0).getType()).isEqualTo("X.509");
4243
}
4344

4445
@Test
4546
void parseCertificateChain() throws Exception {
46-
X509Certificate[] certificates = PemCertificateParser.parse(read("test-cert-chain.pem"));
47+
List<X509Certificate> certificates = PemCertificateParser.parse(read("test-cert-chain.pem"));
4748
assertThat(certificates).isNotNull();
4849
assertThat(certificates).hasSize(2);
49-
assertThat(certificates[0].getType()).isEqualTo("X.509");
50-
assertThat(certificates[1].getType()).isEqualTo("X.509");
50+
assertThat(certificates.get(0).getType()).isEqualTo("X.509");
51+
assertThat(certificates.get(1).getType()).isEqualTo("X.509");
5152
}
5253

5354
private String read(String path) throws IOException {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemContentTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,19 @@ void loadWhenContentIsPemContentReturnsContent() {
5757
+lGuHKdhNOVW9CmqPD1y76o6c8PQKuF7KZEoY2jvy3GeIfddBvqXgZ4PbWvFz1jO
5858
32C9XWHwRA4=
5959
-----END CERTIFICATE-----""";
60-
assertThat(PemContent.load(content)).isEqualTo(content);
60+
assertThat(PemContent.load(content)).hasToString(content);
6161
}
6262

6363
@Test
6464
void loadWhenClasspathLocationReturnsContent() throws IOException {
65-
String actual = PemContent.load("classpath:test-cert.pem");
65+
String actual = PemContent.load("classpath:test-cert.pem").toString();
6666
String expected = new ClassPathResource("test-cert.pem").getContentAsString(StandardCharsets.UTF_8);
6767
assertThat(actual).isEqualTo(expected);
6868
}
6969

7070
@Test
7171
void loadWhenFileLocationReturnsContent() throws IOException {
72-
String actual = PemContent.load("src/test/resources/test-cert.pem");
72+
String actual = PemContent.load("src/test/resources/test-cert.pem").toString();
7373
String expected = new ClassPathResource("test-cert.pem").getContentAsString(StandardCharsets.UTF_8);
7474
assertThat(actual).isEqualTo(expected);
7575
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/pem/PemPrivateKeyParserTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.security.PrivateKey;
2222
import java.security.interfaces.ECPrivateKey;
23+
import java.util.List;
2324

2425
import org.junit.jupiter.api.Test;
2526
import org.junit.jupiter.params.ParameterizedTest;
@@ -219,9 +220,9 @@ void shouldParseEncryptedPkcs8(String file, String algorithm) throws IOException
219220
// -passout pass:test
220221
// where <algorithm> is aes128 or aes256
221222
String content = read("org/springframework/boot/web/server/pkcs8/" + file);
222-
PrivateKey[] privateKeys = PemPrivateKeyParser.parse(content, "test");
223+
List<PrivateKey> privateKeys = PemPrivateKeyParser.parse(content, "test");
223224
assertThat(privateKeys).isNotEmpty();
224-
PrivateKey privateKey = privateKeys[0];
225+
PrivateKey privateKey = privateKeys.get(0);
225226
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
226227
assertThat(privateKey.getAlgorithm()).isEqualTo(algorithm);
227228
}
@@ -268,8 +269,8 @@ void shouldNotParseEncryptedPkcs1() throws Exception {
268269
}
269270

270271
private PrivateKey parse(String key) {
271-
PrivateKey[] keys = PemPrivateKeyParser.parse(key);
272-
return (!ObjectUtils.isEmpty(keys)) ? keys[0] : null;
272+
List<PrivateKey> keys = PemPrivateKeyParser.parse(key);
273+
return (!ObjectUtils.isEmpty(keys)) ? keys.get(0) : null;
273274
}
274275

275276
private String read(String path) throws IOException {

0 commit comments

Comments
 (0)