Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
Expand All @@ -17,10 +17,15 @@
package org.springframework.security.converter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
Expand All @@ -36,6 +41,7 @@
* Used for creating {@link java.security.Key} converter instances
*
* @author Josh Cummings
* @author Shazin Sadakath
* @since 5.2
*/
public final class RsaKeyConverters {
Expand All @@ -50,6 +56,10 @@ public final class RsaKeyConverters {

private static final String X509_PEM_FOOTER = DASHES + "END PUBLIC KEY" + DASHES;

private static final String X509_CERT_HEADER = DASHES + "BEGIN CERTIFICATE" + DASHES;

private static final String X509_CERT_FOOTER = DASHES + "END CERTIFICATE" + DASHES;

private RsaKeyConverters() {
}

Expand Down Expand Up @@ -91,8 +101,8 @@ public static Converter<InputStream, RSAPrivateKey> pkcs8() {
}

/**
* Construct a {@link Converter} for converting a PEM-encoded X.509 RSA Public Key
* into a {@link RSAPublicKey}.
* Construct a {@link Converter} for converting a PEM-encoded X.509 RSA Public Key or
* X.509 Certificate into a {@link RSAPublicKey}.
*
* This converter does not close the {@link InputStream} in order to avoid making
* non-portable assumptions about the streams' origin and further use.
Expand All @@ -101,27 +111,52 @@ public static Converter<InputStream, RSAPrivateKey> pkcs8() {
*/
public static Converter<InputStream, RSAPublicKey> x509() {
KeyFactory keyFactory = rsaFactory();
CertificateFactory certificateFactory = x509CertificateFactory();
return (source) -> {
List<String> lines = readAllLines(source);
Assert.isTrue(!lines.isEmpty() && lines.get(0).startsWith(X509_PEM_HEADER),
"Key is not in PEM-encoded X.509 format, please check that the header begins with -----"
+ X509_PEM_HEADER + "-----");
Assert.isTrue(
!lines.isEmpty()
&& (lines.get(0).startsWith(X509_PEM_HEADER) || lines.get(0).startsWith(X509_CERT_HEADER)),
"Key is not in PEM-encoded X.509 format or a valid X.509 certificate, please check that the header begins with "
+ X509_PEM_HEADER + " or " + X509_CERT_HEADER);
StringBuilder base64Encoded = new StringBuilder();
for (String line : lines) {
if (RsaKeyConverters.isNotX509Wrapper(line)) {
if (RsaKeyConverters.isNotX509PemWrapper(line) && isNotX509CertificateWrapper(line)) {
base64Encoded.append(line);
}
}
byte[] x509 = Base64.getDecoder().decode(base64Encoded.toString());
try {
return (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509));
if (lines.get(0).startsWith(X509_PEM_HEADER)) {
try {
return (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509));
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
if (lines.get(0).startsWith(X509_CERT_HEADER)) {
try (InputStream x509CertStream = new ByteArrayInputStream(x509)) {
X509Certificate certificate = (X509Certificate) certificateFactory
.generateCertificate(x509CertStream);
return (RSAPublicKey) certificate.getPublicKey();
}
catch (CertificateException | IOException ex) {
throw new IllegalArgumentException(ex);
}
}
return null;
};
}

private static CertificateFactory x509CertificateFactory() {
try {
return CertificateFactory.getInstance("X.509");
}
catch (CertificateException ex) {
throw new IllegalArgumentException(ex);
}
}

private static List<String> readAllLines(InputStream source) {
BufferedReader reader = new BufferedReader(new InputStreamReader(source));
return reader.lines().collect(Collectors.toList());
Expand All @@ -140,8 +175,12 @@ private static boolean isNotPkcs8Wrapper(String line) {
return !PKCS8_PEM_HEADER.equals(line) && !PKCS8_PEM_FOOTER.equals(line);
}

private static boolean isNotX509Wrapper(String line) {
private static boolean isNotX509PemWrapper(String line) {
return !X509_PEM_HEADER.equals(line) && !X509_PEM_FOOTER.equals(line);
}

private static boolean isNotX509CertificateWrapper(String line) {
return !X509_CERT_HEADER.equals(line) && !X509_CERT_FOOTER.equals(line);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
Expand Down Expand Up @@ -93,6 +93,20 @@ public class RsaKeyConvertersTests {
+ "-----END PUBLIC KEY-----";
// @formatter:on

// @formatter:off
private static final String X509_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" +
"MIIBqDCCARECBgF5zJA6MjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9TaGF6\n" +
"aW4gU2FkYWthdGgwHhcNMjEwNjAxMTE1MTE0WhcNMjEwNTE3MjAwOTI1WjAaMRgw\n" +
"FgYDVQQDEw9TaGF6aW4gU2FkYWthdGgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ\n" +
"AoGBAKsKpS6sliNSri3koOAgzS7Nz2cpl0tGpNP3GPuUYVMP4MA0LJ2+blxjxUcn\n" +
"oIajtaf9HljFetKVjyARp1zjZ3Oxm//lfmyqqI5KDUjqe5J2rdtbdFCH9FXUEoGD\n" +
"mu2ameR9lAfxtaGI58DGS9uJ5hvGJoIvLiaDUfv1qZ+kIwG7AgMBAAEwDQYJKoZI\n" +
"hvcNAQELBQADgYEAWdIIi4cGPod5O/V7K0QSTXZRLRIKFQ7qhn5XTNlMUnFnwp7c\n" +
"8O8EsOiCKAZeVvgRnurFkxAlVnpxmdktZ9j+mv2mrMGKJxYkZcBkFh++DRixpY8N\n" +
"zBLbxZJ9kcOHWWDA602FMbNIEL1OiHrfggsPk3sckSaSg4d7UoP9T6+uqq8=\n" +
"-----END CERTIFICATE-----";
// @formatter:on

private static final String MALFORMED_X509_KEY = "malformed";

private final Converter<InputStream, RSAPublicKey> x509 = RsaKeyConverters.x509();
Expand All @@ -112,11 +126,17 @@ public void pkcs8WhenConvertingPkcs1PrivateKeyThenIllegalArgumentException() {
}

@Test
public void x509WhenConverteringX509PublicKeyThenOk() {
public void x509WhenConvertingX509PublicKeyThenOk() {
RSAPublicKey key = this.x509.convert(toInputStream(X509_PUBLIC_KEY));
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
}

@Test
public void x509WhenConvertingX509CertificateThenOk() {
RSAPublicKey key = this.x509.convert(toInputStream(X509_CERTIFICATE));
Assertions.assertThat(key.getModulus().bitLength()).isEqualTo(1024);
}

@Test
public void x509WhenConvertingDerEncodedX509PublicKeyThenIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.x509.convert(toInputStream(MALFORMED_X509_KEY)));
Expand Down