Support EC in PEM utils

This change adds

- Support for decoding EC private keys.
- Support for decoding certificate bundles.

Closes #38490

Signed-off-by: Tero Saarni <tero.saarni@est.tech>
This commit is contained in:
Tero Saarni 2025-03-27 16:44:05 +02:00 committed by GitHub
parent 6aa3f9d5a7
commit c7f0fc7ac3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 7 deletions

View File

@ -52,10 +52,7 @@ public final class DerUtils {
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec =
new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf =CryptoIntegration.getProvider().getKeyFactory("RSA");
return kf.generatePrivate(spec);
return decodePrivateKey(keyBytes);
}
public static PublicKey decodePublicKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
@ -79,7 +76,14 @@ public final class DerUtils {
public static PrivateKey decodePrivateKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
PKCS8EncodedKeySpec spec =
new PKCS8EncodedKeySpec(der);
KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("RSA");
return kf.generatePrivate(spec);
}
String[] algorithms = { "RSA", "EC" };
for (String algorithm : algorithms) {
try {
return CryptoIntegration.getProvider().getKeyFactory(algorithm).generatePrivate(spec);
} catch (InvalidKeySpecException e) {
// Ignore and try the next algorithm.
}
}
throw new InvalidKeySpecException("Unable to decode the private key with supported algorithms: " + String.join(", ", algorithms));
}
}

View File

@ -23,6 +23,9 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.keycloak.common.crypto.CryptoIntegration;
@ -53,6 +56,20 @@ public class PemUtils {
return CryptoIntegration.getProvider().getPemUtils().decodeCertificate(cert);
}
/**
* Decode one or more X509 Certificates from a PEM string (certificate bundle)
*
* @param certs
* @return
* @throws Exception
*/
public static X509Certificate[] decodeCertificates(String certs) {
return Arrays.stream(certs.split(END_CERT))
.map(String::trim)
.filter(pemBlock -> !pemBlock.isEmpty())
.map(pemBlock -> PemUtils.decodeCertificate(pemBlock + END_CERT))
.toArray(X509Certificate[]::new);
}
/**
* Decode a Public Key from a PEM string

View File

@ -12,6 +12,7 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.rule.CryptoInitRule;
@ -118,6 +119,50 @@ public abstract class PemUtilsTest {
String pk = PemUtils.removeBeginEnd(privateKeyPkcs8).replace("\n", "");
PrivateKey decodedPrivateKey2 = PemUtils.decodePrivateKey(pk);
Assert.assertEquals(decodedPrivateKey1, decodedPrivateKey2);
String ecPrivateKeyPkcs8 = "-----BEGIN PRIVATE KEY-----\n" +
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgO1oavi4kqVFc/rxj\n" +
"24SJivHXq7buWX58U0tswYikPwyhRANCAASCIp6nVvOk9flbUrMW7JPDmyaXCnDc\n" +
"Q2uMfvxVWIJzBuhG6VDoeFPk3yf2EN5t7Q8FU5jPSp6gJz9xbaFYYLL6\n" +
"-----END PRIVATE KEY-----";
PrivateKey decodedEcPrivateKey = PemUtils.decodePrivateKey(ecPrivateKeyPkcs8);
Assert.assertEquals("EC", decodedEcPrivateKey.getAlgorithm());
}
@Test
public void testDecodeCertificateBundle() {
String certBundleEC = "-----BEGIN CERTIFICATE-----\n" +
"MIIBUTCB96ADAgECAggYMJVpV/BvyTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDEwZz\n" +
"dWItY2EwIBcNMDAwMTAxMDkwMDAwWhgPMjEwMDAxMDEwOTAwMDBaMBUxEzARBgNV\n" +
"BAMTCmVuZC1lbnRpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASCIp6nVvOk\n" +
"9flbUrMW7JPDmyaXCnDcQ2uMfvxVWIJzBuhG6VDoeFPk3yf2EN5t7Q8FU5jPSp6g\n" +
"Jz9xbaFYYLL6ozMwMTAOBgNVHQ8BAf8EBAMCBaAwHwYDVR0jBBgwFoAU3etTPCDC\n" +
"f31HxBuYWWjF9ImW4ccwCgYIKoZIzj0EAwIDSQAwRgIhAKpP+HBEvUWEfjdr2qD2\n" +
"sw/bVLtW1HnpqVnQm2i/kDp2AiEA6F+kKyMNu+jGKmzj0Pf6v0cj0c+f00bqoJdk\n" +
"h+GXGnM=\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"MIIBejCCAR+gAwIBAgIIGDCVaVflNG8wCgYIKoZIzj0EAwIwDTELMAkGA1UEAxMC\n" +
"Y2EwIBcNMDAwMTAxMDkwMDAwWhgPMjEwMDAxMDEwOTAwMDBaMBExDzANBgNVBAMT\n" +
"BnN1Yi1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI4bNe/0VXXojhjdh76p\n" +
"89esSheOT5WEBVQnJUvDBDSRoxRiFx2BEdPaVn8L4cCbaZIxLsoJusOJadm7Eltc\n" +
"h3qjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\n" +
"BBTd61M8IMJ/fUfEG5hZaMX0iZbhxzAfBgNVHSMEGDAWgBQ9q0KnjYuFWTSXf4YM\n" +
"Taz6vbNVRTAKBggqhkjOPQQDAgNJADBGAiEA3y9pa2JMhtM898f6NOZhezoHzj1a\n" +
"2JQIZRLQbOTjk0wCIQCg9A8414teP9whzRGSxM4eJNExdfHeJBYjDD345EW0vg==\n" +
"-----END CERTIFICATE-----";
X509Certificate[] certs = PemUtils.decodeCertificates(certBundleEC);
Assert.assertEquals(2, certs.length);
Assert.assertEquals("CN=end-entity", certs[0].getSubjectX500Principal().getName());
Assert.assertEquals("CN=sub-ca", certs[1].getSubjectX500Principal().getName());
String invalidCertBundle = "foo\n";
Assert.assertThrows(PemException.class, () -> {
PemUtils.decodeCertificates(invalidCertBundle);
});
}
private void testPrivateKeyEncodeDecode(String origPrivateKeyPem) {