Skip to content

Commit c238049

Browse files
committed
Fix loading of PKCS#8 PEM encoded EC and DSA keys
Closes gh-35322
1 parent 13da659 commit c238049

File tree

8 files changed

+114
-11
lines changed

8 files changed

+114
-11
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,8 +24,11 @@
2424
import java.security.GeneralSecurityException;
2525
import java.security.KeyFactory;
2626
import java.security.PrivateKey;
27+
import java.security.spec.InvalidKeySpecException;
2728
import java.security.spec.PKCS8EncodedKeySpec;
2829
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.Collection;
2932
import java.util.Collections;
3033
import java.util.List;
3134
import java.util.function.Function;
@@ -41,6 +44,7 @@
4144
*
4245
* @author Scott Frederick
4346
* @author Phillip Webb
47+
* @author Moritz Halbritter
4448
*/
4549
final class PrivateKeyParser {
4650

@@ -61,9 +65,12 @@ final class PrivateKeyParser {
6165
private static final List<PemParser> PEM_PARSERS;
6266
static {
6367
List<PemParser> parsers = new ArrayList<>();
64-
parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1));
65-
parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc));
66-
parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new));
68+
parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, Collections.singleton("RSA"),
69+
PrivateKeyParser::createKeySpecForPkcs1));
70+
parsers.add(
71+
new PemParser(EC_HEADER, EC_FOOTER, Collections.singleton("EC"), PrivateKeyParser::createKeySpecForEc));
72+
parsers.add(
73+
new PemParser(PKCS8_HEADER, PKCS8_FOOTER, Arrays.asList("RSA", "EC", "DSA"), PKCS8EncodedKeySpec::new));
6774
PEM_PARSERS = Collections.unmodifiableList(parsers);
6875
}
6976

@@ -145,14 +152,14 @@ private static class PemParser {
145152

146153
private final Pattern pattern;
147154

148-
private final String algorithm;
155+
private final Collection<String> algorithms;
149156

150157
private final Function<byte[], PKCS8EncodedKeySpec> keySpecFactory;
151158

152-
PemParser(String header, String footer, String algorithm,
159+
PemParser(String header, String footer, Collection<String> algorithms,
153160
Function<byte[], PKCS8EncodedKeySpec> keySpecFactory) {
154161
this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE);
155-
this.algorithm = algorithm;
162+
this.algorithms = algorithms;
156163
this.keySpecFactory = keySpecFactory;
157164
}
158165

@@ -169,8 +176,15 @@ private static byte[] decodeBase64(String content) {
169176
private PrivateKey parse(byte[] bytes) {
170177
try {
171178
PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes);
172-
KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm);
173-
return keyFactory.generatePrivate(keySpec);
179+
for (String algorithm : this.algorithms) {
180+
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
181+
try {
182+
return keyFactory.generatePrivate(keySpec);
183+
}
184+
catch (InvalidKeySpecException ignored) {
185+
}
186+
}
187+
return null;
174188
}
175189
catch (GeneralSecurityException ex) {
176190
throw new IllegalArgumentException("Unexpected key format", ex);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.security.PrivateKey;
2020

2121
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
2224

2325
import static org.assertj.core.api.Assertions.assertThat;
2426
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -27,17 +29,36 @@
2729
* Tests for {@link PrivateKeyParser}.
2830
*
2931
* @author Scott Frederick
32+
* @author Moritz Halbritter
3033
*/
3134
class PrivateKeyParserTests {
3235

3336
@Test
34-
void parsePkcs8KeyFile() {
35-
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-key.pem");
37+
void parsePkcs8RsaKeyFile() {
38+
PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-rsa.pem");
3639
assertThat(privateKey).isNotNull();
3740
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
3841
assertThat(privateKey.getAlgorithm()).isEqualTo("RSA");
3942
}
4043

44+
@ParameterizedTest
45+
@ValueSource(strings = { "key-ec-nist-p256.pem", "key-ec-nist-p384.pem", "key-ec-prime256v1.pem",
46+
"key-ec-secp256r1.pem" })
47+
void parsePkcs8EcKeyFile(String fileName) {
48+
PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/" + fileName);
49+
assertThat(privateKey).isNotNull();
50+
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
51+
assertThat(privateKey.getAlgorithm()).isEqualTo("EC");
52+
}
53+
54+
@Test
55+
void parsePkcs8DsaKeyFile() {
56+
PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-dsa.pem");
57+
assertThat(privateKey).isNotNull();
58+
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
59+
assertThat(privateKey.getAlgorithm()).isEqualTo("DSA");
60+
}
61+
4162
@Test
4263
void parsePkcs8KeyFileWithEcdsa() {
4364
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-ec-key.pem");
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7F
3+
njuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwO
4+
jxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYp
5+
H51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkv
6+
aAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD
7+
3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0A
8+
uvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQN
9+
NNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJ
10+
syB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjG
11+
jQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmk
12+
ltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7l
13+
U92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9ve
14+
BB4CHHBQgJ3ST6U8rIxoTqGe42TiVckPf1PoSiJy8GY=
15+
-----END PRIVATE KEY-----
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgd6SePFfpaTKFd1Gm
3+
+WeHZNkORkot5hx6X9elPdICL9ygCgYIKoZIzj0DAQehRANCAASnMAMgeFBv9ks0
4+
d0jP+utQ3mohwmxY93xljfaBofdg1IeHgDd4I4pBzPxEnvXrU3kcz+SgPZyH1ybl
5+
P6mSXDXu
6+
-----END PRIVATE KEY-----
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCexXiWKrtrqV1+d1Tv
3+
t1n5huuw2A+204mQHRuPL9UC8l0XniJjx/PVELCciyJM/7+gBwYFK4EEACKhZANi
4+
AASHEELZSdrHiSXqU1B+/jrOCr6yjxCMqQsetTb0q5WZdCXOhggGXfbzlRynqphQ
5+
i4G7azBUklgLaXfxN5eFk6C+E38SYOR7iippcQsSR2ZsCiTk7rnur4b40gQ7IgLA
6+
/sU=
7+
-----END PRIVATE KEY-----
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4dVuddgQ6enDvPPw
3+
Dd1mmS6FMm/kzTJjDVsltrNmRuSgCgYIKoZIzj0DAQehRANCAAR1WMrRADEaVj9m
4+
uoUfPhUefJK+lS89NHikQ0ZdkHkybyVKLFMLe1hCynhzpKQmnpgud3E10F0P2PZQ
5+
L9RCEpGf
6+
-----END PRIVATE KEY-----
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU9+v5hUNnTKix8fe
3+
Pfz+NfXFlGxQZMReSCT2Id9PfKagCgYIKoZIzj0DAQehRANCAATeJg+YS4BrJ35A
4+
KgRlZ59yKLDpmENCMoaYUuWbQ9hqHzdybQGzQsrNJqgH0nzWghPwP4nFaLPN+pgB
5+
bqiRgbjG
6+
-----END PRIVATE KEY-----
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDR0KfxUw7MF/8R
3+
B5/YXOM7yLnoHYb/M/6dyoulMbtEdKKhQhU28o5FiDkHcEG9PJQLgqrRgAjl3VmC
4+
C9omtfZJQ2EpfkTttkJjnKOOroXhYE51/CYSckapBYCVh8GkjUEJuEfnp07cTfYZ
5+
FqViIgIWPZyjkzl3w4girS7kCuzNdDntVJVx5F/EsFwMA8n3C0QazHQoM5s00Fer
6+
6aTwd6AW0JD5QkADavpfzZ554e4HrVGwHlM28WKQQkFzzGu44FFXyVuEF3HeyVPu
7+
g8GRHAc8UU7ijVgJB5TmbvRGYowIErD5i4VvGLuOv9mgR3aVyN0SdJ1N7aJnXpeS
8+
QjAgf03jAgMBAAECggEBAIhQyzwj3WJGWOZkkLqOpufJotcmj/Wwf0VfOdkq9WMl
9+
cB/bAlN/xWVxerPVgDCFch4EWBzi1WUaqbOvJZ2u7QNubmr56aiTmJCFTVI/GyZx
10+
XqiTGN01N6lKtN7xo6LYTyAUhUsBTWAemrx0FSErvTVb9C/mUBj6hbEZ2XQ5kN5t
11+
7qYX4Lu0zyn7s1kX5SLtm5I+YRq7HSwB6wLy+DSroO71izZ/VPwME3SwT5SN+c87
12+
3dkklR7fumNd9dOpSWKrLPnq4aMko00rvIGc63xD1HrEpXUkB5v24YEn7HwCLEH7
13+
b8jrp79j2nCvvR47inpf+BR8FIWAHEOUUqCEzjQkdiECgYEA6ifjMM0f02KPeIs7
14+
zXd1lI7CUmJmzkcklCIpEbKWf/t/PHv3QgqIkJzERzRaJ8b+GhQ4zrSwAhrGUmI8
15+
kDkXIqe2/2ONgIOX2UOHYHyTDQZHnlXyDecvHUTqs2JQZCGBZkXyZ9i0j3BnTymC
16+
iZ8DvEa0nxsbP+U3rgzPQmXiQVMCgYEA5WN2Y/RndbriNsNrsHYRldbPO5nfV9rp
17+
cDzcQU66HRdK5VIdbXT9tlMYCJIZsSqE0tkOwTgEB/sFvF/tIHSCY5iO6hpIyk6g
18+
kkUzPcld4eM0dEPAge7SYUbakB9CMvA7MkDQSXQNFyZ0mH83+UikwT6uYHFh7+ox
19+
N1P+psDhXzECgYEA1gXLVQnIcy/9LxMkgDMWV8j8uMyUZysDthpbK3/uq+A2dhRg
20+
9g4msPd5OBQT65OpIjElk1n4HpRWfWqpLLHiAZ0GWPynk7W0D7P3gyuaRSdeQs0P
21+
x8FtgPVDCN9t13gAjHiWjnC26Py2kNbCKAQeJ/MAmQTvrUFX2VCACJKTcV0CgYAj
22+
xJWSUmrLfb+GQISLOG3Xim434e9keJsLyEGj4U29+YLRLTOvfJ2PD3fg5j8hU/rw
23+
Ea5uTHi8cdTcIa0M8X3fX8txD3YoLYh2JlouGTcNYOst8d6TpBSj3HN6I5Wj8beZ
24+
R2fy/CiKYpGtsbCdq0kdZNO18BgQW9kewncjs1GxEQKBgQCf8q34h6KuHpHSDh9h
25+
YkDTypk0FReWBAVJCzDNDUMhVLFivjcwtaMd2LiC3FMKZYodr52iKg60cj43vbYI
26+
frmFFxoL37rTmUocCTBKc0LhWj6MicI+rcvQYe1uwTrpWdFf1aZJMYRLRczeKtev
27+
OWaE/9hVZ5+9pild1NukGpOydw==
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)