From c238049729e5dca60160a811bcfc0e9f857f38c0 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 9 May 2023 13:53:18 +0200 Subject: [PATCH] Fix loading of PKCS#8 PEM encoded EC and DSA keys Closes gh-35322 --- .../boot/web/server/PrivateKeyParser.java | 32 +++++++++++++------ .../web/server/PrivateKeyParserTests.java | 25 +++++++++++++-- .../src/test/resources/ssl/pkcs8/key-dsa.pem | 15 +++++++++ .../resources/ssl/pkcs8/key-ec-nist-p256.pem | 6 ++++ .../resources/ssl/pkcs8/key-ec-nist-p384.pem | 7 ++++ .../resources/ssl/pkcs8/key-ec-prime256v1.pem | 6 ++++ .../resources/ssl/pkcs8/key-ec-secp256r1.pem | 6 ++++ .../src/test/resources/ssl/pkcs8/key-rsa.pem | 28 ++++++++++++++++ 8 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem create mode 100644 spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java index 93df27cb1c..474138e5af 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PrivateKeyParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -24,8 +24,11 @@ import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -41,6 +44,7 @@ import org.springframework.util.ResourceUtils; * * @author Scott Frederick * @author Phillip Webb + * @author Moritz Halbritter */ final class PrivateKeyParser { @@ -61,9 +65,12 @@ final class PrivateKeyParser { private static final List PEM_PARSERS; static { List parsers = new ArrayList<>(); - parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1)); - parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc)); - parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new)); + parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, Collections.singleton("RSA"), + PrivateKeyParser::createKeySpecForPkcs1)); + parsers.add( + new PemParser(EC_HEADER, EC_FOOTER, Collections.singleton("EC"), PrivateKeyParser::createKeySpecForEc)); + parsers.add( + new PemParser(PKCS8_HEADER, PKCS8_FOOTER, Arrays.asList("RSA", "EC", "DSA"), PKCS8EncodedKeySpec::new)); PEM_PARSERS = Collections.unmodifiableList(parsers); } @@ -145,14 +152,14 @@ final class PrivateKeyParser { private final Pattern pattern; - private final String algorithm; + private final Collection algorithms; private final Function keySpecFactory; - PemParser(String header, String footer, String algorithm, + PemParser(String header, String footer, Collection algorithms, Function keySpecFactory) { this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE); - this.algorithm = algorithm; + this.algorithms = algorithms; this.keySpecFactory = keySpecFactory; } @@ -169,8 +176,15 @@ final class PrivateKeyParser { private PrivateKey parse(byte[] bytes) { try { PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes); - KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm); - return keyFactory.generatePrivate(keySpec); + for (String algorithm : this.algorithms) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + try { + return keyFactory.generatePrivate(keySpec); + } + catch (InvalidKeySpecException ignored) { + } + } + return null; } catch (GeneralSecurityException ex) { throw new IllegalArgumentException("Unexpected key format", ex); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java index 390fb1b2b6..926e5ac12b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/PrivateKeyParserTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.web.server; import java.security.PrivateKey; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -27,17 +29,36 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * Tests for {@link PrivateKeyParser}. * * @author Scott Frederick + * @author Moritz Halbritter */ class PrivateKeyParserTests { @Test - void parsePkcs8KeyFile() { - PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-key.pem"); + void parsePkcs8RsaKeyFile() { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-rsa.pem"); assertThat(privateKey).isNotNull(); assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); assertThat(privateKey.getAlgorithm()).isEqualTo("RSA"); } + @ParameterizedTest + @ValueSource(strings = { "key-ec-nist-p256.pem", "key-ec-nist-p384.pem", "key-ec-prime256v1.pem", + "key-ec-secp256r1.pem" }) + void parsePkcs8EcKeyFile(String fileName) { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/" + fileName); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("EC"); + } + + @Test + void parsePkcs8DsaKeyFile() { + PrivateKey privateKey = PrivateKeyParser.parse("classpath:ssl/pkcs8/key-dsa.pem"); + assertThat(privateKey).isNotNull(); + assertThat(privateKey.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKey.getAlgorithm()).isEqualTo("DSA"); + } + @Test void parsePkcs8KeyFileWithEcdsa() { PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-ec-key.pem"); diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem new file mode 100644 index 0000000000..1aa27e11c2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-dsa.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7F +njuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwO +jxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYp +H51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkv +aAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD +3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0A +uvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQN +NNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJ +syB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjG +jQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmk +ltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7l +U92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9ve +BB4CHHBQgJ3ST6U8rIxoTqGe42TiVckPf1PoSiJy8GY= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem new file mode 100644 index 0000000000..8cd5d39294 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p256.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgd6SePFfpaTKFd1Gm ++WeHZNkORkot5hx6X9elPdICL9ygCgYIKoZIzj0DAQehRANCAASnMAMgeFBv9ks0 +d0jP+utQ3mohwmxY93xljfaBofdg1IeHgDd4I4pBzPxEnvXrU3kcz+SgPZyH1ybl +P6mSXDXu +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem new file mode 100644 index 0000000000..563b519588 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-nist-p384.pem @@ -0,0 +1,7 @@ +-----BEGIN PRIVATE KEY----- +MIG/AgEAMBAGByqGSM49AgEGBSuBBAAiBIGnMIGkAgEBBDCexXiWKrtrqV1+d1Tv +t1n5huuw2A+204mQHRuPL9UC8l0XniJjx/PVELCciyJM/7+gBwYFK4EEACKhZANi +AASHEELZSdrHiSXqU1B+/jrOCr6yjxCMqQsetTb0q5WZdCXOhggGXfbzlRynqphQ +i4G7azBUklgLaXfxN5eFk6C+E38SYOR7iippcQsSR2ZsCiTk7rnur4b40gQ7IgLA +/sU= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem new file mode 100644 index 0000000000..66c626d622 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-prime256v1.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4dVuddgQ6enDvPPw +Dd1mmS6FMm/kzTJjDVsltrNmRuSgCgYIKoZIzj0DAQehRANCAAR1WMrRADEaVj9m +uoUfPhUefJK+lS89NHikQ0ZdkHkybyVKLFMLe1hCynhzpKQmnpgud3E10F0P2PZQ +L9RCEpGf +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem new file mode 100644 index 0000000000..adffc64637 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-ec-secp256r1.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU9+v5hUNnTKix8fe +Pfz+NfXFlGxQZMReSCT2Id9PfKagCgYIKoZIzj0DAQehRANCAATeJg+YS4BrJ35A +KgRlZ59yKLDpmENCMoaYUuWbQ9hqHzdybQGzQsrNJqgH0nzWghPwP4nFaLPN+pgB +bqiRgbjG +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem new file mode 100644 index 0000000000..00d439edc6 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/ssl/pkcs8/key-rsa.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDR0KfxUw7MF/8R +B5/YXOM7yLnoHYb/M/6dyoulMbtEdKKhQhU28o5FiDkHcEG9PJQLgqrRgAjl3VmC +C9omtfZJQ2EpfkTttkJjnKOOroXhYE51/CYSckapBYCVh8GkjUEJuEfnp07cTfYZ +FqViIgIWPZyjkzl3w4girS7kCuzNdDntVJVx5F/EsFwMA8n3C0QazHQoM5s00Fer +6aTwd6AW0JD5QkADavpfzZ554e4HrVGwHlM28WKQQkFzzGu44FFXyVuEF3HeyVPu +g8GRHAc8UU7ijVgJB5TmbvRGYowIErD5i4VvGLuOv9mgR3aVyN0SdJ1N7aJnXpeS +QjAgf03jAgMBAAECggEBAIhQyzwj3WJGWOZkkLqOpufJotcmj/Wwf0VfOdkq9WMl +cB/bAlN/xWVxerPVgDCFch4EWBzi1WUaqbOvJZ2u7QNubmr56aiTmJCFTVI/GyZx +XqiTGN01N6lKtN7xo6LYTyAUhUsBTWAemrx0FSErvTVb9C/mUBj6hbEZ2XQ5kN5t +7qYX4Lu0zyn7s1kX5SLtm5I+YRq7HSwB6wLy+DSroO71izZ/VPwME3SwT5SN+c87 +3dkklR7fumNd9dOpSWKrLPnq4aMko00rvIGc63xD1HrEpXUkB5v24YEn7HwCLEH7 +b8jrp79j2nCvvR47inpf+BR8FIWAHEOUUqCEzjQkdiECgYEA6ifjMM0f02KPeIs7 +zXd1lI7CUmJmzkcklCIpEbKWf/t/PHv3QgqIkJzERzRaJ8b+GhQ4zrSwAhrGUmI8 +kDkXIqe2/2ONgIOX2UOHYHyTDQZHnlXyDecvHUTqs2JQZCGBZkXyZ9i0j3BnTymC +iZ8DvEa0nxsbP+U3rgzPQmXiQVMCgYEA5WN2Y/RndbriNsNrsHYRldbPO5nfV9rp +cDzcQU66HRdK5VIdbXT9tlMYCJIZsSqE0tkOwTgEB/sFvF/tIHSCY5iO6hpIyk6g +kkUzPcld4eM0dEPAge7SYUbakB9CMvA7MkDQSXQNFyZ0mH83+UikwT6uYHFh7+ox +N1P+psDhXzECgYEA1gXLVQnIcy/9LxMkgDMWV8j8uMyUZysDthpbK3/uq+A2dhRg +9g4msPd5OBQT65OpIjElk1n4HpRWfWqpLLHiAZ0GWPynk7W0D7P3gyuaRSdeQs0P +x8FtgPVDCN9t13gAjHiWjnC26Py2kNbCKAQeJ/MAmQTvrUFX2VCACJKTcV0CgYAj +xJWSUmrLfb+GQISLOG3Xim434e9keJsLyEGj4U29+YLRLTOvfJ2PD3fg5j8hU/rw +Ea5uTHi8cdTcIa0M8X3fX8txD3YoLYh2JlouGTcNYOst8d6TpBSj3HN6I5Wj8beZ +R2fy/CiKYpGtsbCdq0kdZNO18BgQW9kewncjs1GxEQKBgQCf8q34h6KuHpHSDh9h +YkDTypk0FReWBAVJCzDNDUMhVLFivjcwtaMd2LiC3FMKZYodr52iKg60cj43vbYI +frmFFxoL37rTmUocCTBKc0LhWj6MicI+rcvQYe1uwTrpWdFf1aZJMYRLRczeKtev +OWaE/9hVZ5+9pild1NukGpOydw== +-----END PRIVATE KEY-----