Add pem-api module: PEMDecoder/PEMEncoder (JEP 538 preview on JDK 27, JEP 542 final in JDK 28) for JWT keys, TLS, encrypted keys and BouncyCastle migration
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CifR2BE7PBvRy5hT8TS9oz
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.security.*;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.openssl.*;
|
||||
import org.bouncycastle.openssl.jcajce.*;
|
||||
import org.bouncycastle.operator.InputDecryptorProvider;
|
||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||
import org.springframework.security.converter.RsaKeyConverters;
|
||||
|
||||
/** The same jobs done with BouncyCastle (bcpkix), with Spring Security's RsaKeyConverters, and with the JDK. */
|
||||
public class Migration {
|
||||
static final char[] PW = "changeit".toCharArray();
|
||||
|
||||
static Object bcParse(String pem) throws Exception {
|
||||
try (PEMParser p = new PEMParser(new StringReader(pem))) { return p.readObject(); }
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
PEMDecoder jdk = PEMDecoder.of();
|
||||
JcaPEMKeyConverter conv = new JcaPEMKeyConverter();
|
||||
|
||||
Fx.h("1. load an RSA private key (PKCS#8)");
|
||||
PrivateKey viaBc = conv.getPrivateKey((PrivateKeyInfo) bcParse(Fx.read("rsa-private-pkcs8.pem")));
|
||||
PrivateKey viaJdk = jdk.decode(Fx.read("rsa-private-pkcs8.pem"), PrivateKey.class);
|
||||
Fx.check(viaBc.equals(viaJdk), "BouncyCastle and the JDK produce equal keys");
|
||||
System.out.println("BC parser returned: " + bcParse(Fx.read("rsa-private-pkcs8.pem")).getClass().getSimpleName());
|
||||
|
||||
Fx.h("2. load a certificate");
|
||||
X509CertificateHolder holder = (X509CertificateHolder) bcParse(Fx.read("server.pem"));
|
||||
X509Certificate c1 = new JcaX509CertificateConverter().getCertificate(holder);
|
||||
X509Certificate c2 = jdk.decode(Fx.read("server.pem"), X509Certificate.class);
|
||||
Fx.check(c1.equals(c2), "same certificate either way");
|
||||
|
||||
Fx.h("3. write keys");
|
||||
StringWriter sw = new StringWriter();
|
||||
try (JcaPEMWriter w = new JcaPEMWriter(sw)) { w.writeObject(c2.getPublicKey()); }
|
||||
Fx.check(sw.toString().replace("\r\n", "\n").equals(PEMEncoder.of().encodeToString(c2.getPublicKey()).replace("\r\n", "\n")), "JcaPEMWriter and PEMEncoder write identical public-key text (line terminators aside)");
|
||||
sw = new StringWriter();
|
||||
try (JcaPEMWriter w = new JcaPEMWriter(sw)) { w.writeObject(viaJdk); }
|
||||
System.out.println("JcaPEMWriter on an RSA private key writes: " + sw.toString().lines().findFirst().get());
|
||||
System.out.println("PEMEncoder on an RSA private key writes: " + PEMEncoder.of().encodeToString(viaJdk).lines().findFirst().get());
|
||||
|
||||
Fx.h("4. encrypted PKCS#8 key (BC needs its own provider for AES/CBC/PKCS7Padding; the JDK does not)");
|
||||
PKCS8EncryptedPrivateKeyInfo bcEnc = (PKCS8EncryptedPrivateKeyInfo) bcParse(Fx.read("rsa-private-encrypted.pem"));
|
||||
InputDecryptorProvider dec = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()).build(PW);
|
||||
PrivateKey k1 = conv.getPrivateKey(bcEnc.decryptPrivateKeyInfo(dec));
|
||||
PrivateKey k2 = jdk.withDecryption(PW).decode(Fx.read("rsa-private-encrypted.pem"), PrivateKey.class);
|
||||
Fx.check(k1.equals(k2), "same key either way");
|
||||
|
||||
Fx.h("5. what BouncyCastle reads and the JDK does not");
|
||||
Object sec1 = bcParse(Fx.read("ec-private-sec1.pem"));
|
||||
KeyPair bcPair = conv.getKeyPair((PEMKeyPair) sec1);
|
||||
System.out.println("EC PRIVATE KEY (SEC1): BC -> " + sec1.getClass().getSimpleName() + ", JDK -> " + Fx.describe(jdk.decode(Fx.read("ec-private-sec1.pem"))));
|
||||
PrivateKey ecViaJdk = jdk.decode(Fx.read("ec-private-pkcs8.pem"), PrivateKey.class);
|
||||
Fx.check(((ECPrivateKey) bcPair.getPrivate()).getS().equals(((ECPrivateKey) ecViaJdk).getS()), "after 'openssl pkcs8 -topk8 -nocrypt' the JDK reads the same secret scalar BC read from SEC1");
|
||||
|
||||
Object legacy = bcParse(Fx.read("rsa-private-legacy-encrypted.pem"));
|
||||
PEMDecryptorProvider legacyDec = new JcePEMDecryptorProviderBuilder().setProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()).build(PW);
|
||||
KeyPair legacyKp = conv.getKeyPair(((PEMEncryptedKeyPair) legacy).decryptKeyPair(legacyDec));
|
||||
System.out.println("Proc-Type: 4,ENCRYPTED: BC -> " + legacy.getClass().getSimpleName() + " decrypted to " + legacyKp.getPrivate().getAlgorithm());
|
||||
Fx.check(legacyKp.getPrivate().equals(viaJdk), "...and it is the same RSA key");
|
||||
Fx.tryIt("Proc-Type: 4,ENCRYPTED: JDK", () -> jdk.withDecryption(PW).decode(Fx.read("rsa-private-legacy-encrypted.pem"), PrivateKey.class));
|
||||
|
||||
Fx.h("6. Spring Security RsaKeyConverters (RSA only; PKCS#8 and X.509 only)");
|
||||
Fx.check(RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("rsa-private-pkcs8.pem").getBytes())).equals(viaJdk), "pkcs8() reads the RSA private key");
|
||||
Fx.check(RsaKeyConverters.x509().convert(new ByteArrayInputStream(Fx.read("rsa-public.pem").getBytes())).equals(jdk.decode(Fx.read("rsa-public.pem"), PublicKey.class)), "x509() reads the RSA public key");
|
||||
Fx.tryIt("pkcs8() on an EC key", () -> RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("ec-private-pkcs8.pem").getBytes())));
|
||||
Fx.tryIt("pkcs8() on the PKCS#1 key", () -> RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("rsa-private-pkcs1.pem").getBytes())));
|
||||
Fx.tryIt("JDK on the EC key", () -> jdk.decode(Fx.read("ec-private-pkcs8.pem"), PrivateKey.class));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user