From c85c1aa091c31ef51d8ea2c09d90d1ed421d7a0b Mon Sep 17 00:00:00 2001 From: Will Childs-Klein Date: Thu, 24 Sep 2026 21:08:01 +0000 Subject: [PATCH] Convert non-BC ML-KEM keys in the legacy KEM code path KemUtil resolves the key objects and the KeyGenerator from the JCA separately, so a provider ahead of BC can decode a peer's ML-KEM public key while BC still serves the encapsulation. MLKEMKeyGeneratorSpi casts to BC's own key types, so that split fails every handshake with internal_error(80). --- .../tls/crypto/impl/jcajce/KemUtil.java | 45 +++++++++++++++++++ .../tls/crypto/impl/jcajce/KemUtil.java | 45 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java index 935db71c4b..69285b8f54 100644 --- a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java @@ -2,12 +2,15 @@ import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; import java.security.spec.EncodedKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyGenerator; @@ -18,6 +21,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jcajce.SecretKeyWithEncapsulation; +import org.bouncycastle.jcajce.interfaces.MLKEMPrivateKey; import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey; import org.bouncycastle.jcajce.spec.KEMExtractSpec; import org.bouncycastle.jcajce.spec.KEMGenerateSpec; @@ -54,6 +58,7 @@ static JceTlsSecret decapsulate(JcaTlsCrypto crypto, String kemName, PrivateKey try { KeyGenerator keyGenerator = crypto.getHelper().createKeyGenerator(kemName); + privateKey = importPrivateKey(keyGenerator.getProvider(), kemName, privateKey); keyGenerator.init(new KEMExtractSpec.Builder(privateKey, ciphertext, "DEF", 256).withNoKdf().build()); SecretKeyWithEncapsulation secEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey(); return crypto.adoptLocalSecret(secEnc.getEncoded()); @@ -69,6 +74,7 @@ static SecretKeyWithEncapsulation encapsulate(JcaTlsCrypto crypto, String kemNam try { KeyGenerator keyGenerator = crypto.getHelper().createKeyGenerator(kemName); + publicKey = importPublicKey(keyGenerator.getProvider(), kemName, publicKey); keyGenerator.init(new KEMGenerateSpec.Builder(publicKey, "DEF", 256).withNoKdf().build()); return (SecretKeyWithEncapsulation)keyGenerator.generateKey(); } @@ -164,6 +170,45 @@ private static ASN1ObjectIdentifier getAlgorithmOID(String kemName) throw new IllegalArgumentException("unknown kem name " + kemName); } + /* + * The key objects and the KeyGenerator are resolved from the JCA independently, so a provider + * ahead of BC can supply keys that BC's generator will not accept. Re-import through the + * generator's own provider when that happens. + */ + private static PrivateKey importPrivateKey(Provider provider, String kemName, PrivateKey privateKey) + throws GeneralSecurityException + { + if (!(provider instanceof BouncyCastleProvider) || privateKey instanceof MLKEMPrivateKey) + { + return privateKey; + } + + if (!"PKCS#8".equals(privateKey.getFormat())) + { + throw new InvalidKeyException("Private key format unrecognized: " + privateKey.getFormat()); + } + + KeyFactory kf = KeyFactory.getInstance(kemName, provider); + return kf.generatePrivate(new PKCS8EncodedKeySpec(privateKey.getEncoded())); + } + + private static PublicKey importPublicKey(Provider provider, String kemName, PublicKey publicKey) + throws GeneralSecurityException + { + if (!(provider instanceof BouncyCastleProvider) || publicKey instanceof MLKEMPublicKey) + { + return publicKey; + } + + if (!"X.509".equals(publicKey.getFormat())) + { + throw new InvalidKeyException("Public key format unrecognized: " + publicKey.getFormat()); + } + + KeyFactory kf = KeyFactory.getInstance(kemName, provider); + return kf.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); + } + static boolean isKemSupported(JcaTlsCrypto crypto, String kemName) { if (kemName != null) diff --git a/tls/src/main/jdk17/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java b/tls/src/main/jdk17/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java index 34c993dbcd..112e2a8d23 100644 --- a/tls/src/main/jdk17/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java +++ b/tls/src/main/jdk17/org/bouncycastle/tls/crypto/impl/jcajce/KemUtil.java @@ -2,12 +2,15 @@ import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; import java.security.spec.EncodedKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyGenerator; @@ -18,6 +21,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jcajce.SecretKeyWithEncapsulation; +import org.bouncycastle.jcajce.interfaces.MLKEMPrivateKey; import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey; import org.bouncycastle.jcajce.spec.KEMExtractSpec; import org.bouncycastle.jcajce.spec.KEMGenerateSpec; @@ -60,6 +64,7 @@ static JceTlsSecret decapsulate(JcaTlsCrypto crypto, String kemName, PrivateKey try { KeyGenerator keyGenerator = crypto.getHelper().createKeyGenerator(kemName); + privateKey = importPrivateKey(keyGenerator.getProvider(), kemName, privateKey); keyGenerator.init(new KEMExtractSpec.Builder(privateKey, ciphertext, "DEF", 256).withNoKdf().build()); SecretKeyWithEncapsulation secEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey(); return crypto.adoptLocalSecret(secEnc.getEncoded()); @@ -80,6 +85,7 @@ static SecretKeyWithEncapsulation encapsulate(JcaTlsCrypto crypto, String kemNam try { KeyGenerator keyGenerator = crypto.getHelper().createKeyGenerator(kemName); + publicKey = importPublicKey(keyGenerator.getProvider(), kemName, publicKey); keyGenerator.init(new KEMGenerateSpec.Builder(publicKey, "DEF", 256).withNoKdf().build()); return (SecretKeyWithEncapsulation)keyGenerator.generateKey(); } @@ -180,6 +186,45 @@ private static ASN1ObjectIdentifier getAlgorithmOID(String kemName) throw new IllegalArgumentException("unknown kem name " + kemName); } + /* + * The key objects and the KeyGenerator are resolved from the JCA independently, so a provider + * ahead of BC can supply keys that BC's generator will not accept. Re-import through the + * generator's own provider when that happens. + */ + private static PrivateKey importPrivateKey(Provider provider, String kemName, PrivateKey privateKey) + throws GeneralSecurityException + { + if (!(provider instanceof BouncyCastleProvider) || privateKey instanceof MLKEMPrivateKey) + { + return privateKey; + } + + if (!"PKCS#8".equals(privateKey.getFormat())) + { + throw new InvalidKeyException("Private key format unrecognized: " + privateKey.getFormat()); + } + + KeyFactory kf = KeyFactory.getInstance(kemName, provider); + return kf.generatePrivate(new PKCS8EncodedKeySpec(privateKey.getEncoded())); + } + + private static PublicKey importPublicKey(Provider provider, String kemName, PublicKey publicKey) + throws GeneralSecurityException + { + if (!(provider instanceof BouncyCastleProvider) || publicKey instanceof MLKEMPublicKey) + { + return publicKey; + } + + if (!"X.509".equals(publicKey.getFormat())) + { + throw new InvalidKeyException("Public key format unrecognized: " + publicKey.getFormat()); + } + + KeyFactory kf = KeyFactory.getInstance(kemName, provider); + return kf.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); + } + static boolean isKemSupported(JcaTlsCrypto crypto, String kemName) { if (SpiUtil.hasKEM())