Bouncy Castle version: 1.84 (bcprov-jdk18on)
JDK version: 25.0.2
Summary
SignatureSpi.verifyInit() in org.bouncycastle.jcajce.provider.asymmetric.mldsa throws a ClassCastException when the provided PublicKey is not a BCMLDSAPublicKey (e.g. a JDK 25 native ML-DSA key). This worked correctly in
BC 1.83.
Root Cause
In BC 1.84, the ML-DSA SignatureSpi was updated to use the new org.bouncycastle.crypto.params package types:
import org.bouncycastle.crypto.params.MLDSAPublicKeyParameters; // NEW type
However, the verifyInit() else branch still delegates to the old pqc PublicKeyFactory:
// Returns org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters (OLD type)
this.keyParams = org.bouncycastle.pqc.crypto.util.PublicKeyFactory.createKey(pubKeyInfo);
// Casts to org.bouncycastle.crypto.params.MLDSAPublicKeyParameters (NEW type) — ClassCastException!
publicKey = new BCMLDSAPublicKey((MLDSAPublicKeyParameters)this.keyParams);
The old pqc.crypto.mldsa.MLDSAPublicKeyParameters and the new crypto.params.MLDSAPublicKeyParameters are separate class hierarchies (both extend AsymmetricKeyParameter through their own MLDSAKeyParameters), so the cast
always fails.
Suggested fix
In SignatureSpi.verifyInit(), replace the old pqc factory with the new one:
// before
this.keyParams = org.bouncycastle.pqc.crypto.util.PublicKeyFactory.createKey(pubKeyInfo);
// after
this.keyParams = org.bouncycastle.crypto.util.PublicKeyFactory.createKey(pubKeyInfo);
The new crypto.util.PublicKeyFactory returns crypto.params.MLDSAPublicKeyParameters, which matches the imported type and makes the cast succeed.
Impact
Any code that passes a non-BC PublicKey (or a certificate parsed by a non-BC CertificateFactory) to Signature.initVerify() for ML-DSA will silently fail verification. This is particularly likely on JDK 25 where the default
SUN provider natively handles ML-DSA certificates.
This is a regression from BC 1.83 where both the PublicKeyFactory and SignatureSpi consistently used the pqc.crypto.mldsa package types.
Bouncy Castle version: 1.84 (bcprov-jdk18on)
JDK version: 25.0.2
Summary
SignatureSpi.verifyInit()inorg.bouncycastle.jcajce.provider.asymmetric.mldsathrows aClassCastExceptionwhen the providedPublicKeyis not aBCMLDSAPublicKey(e.g. a JDK 25 native ML-DSA key). This worked correctly inBC 1.83.
Root Cause
In BC 1.84, the ML-DSA
SignatureSpiwas updated to use the neworg.bouncycastle.crypto.paramspackage types:However, the
verifyInit()else branch still delegates to the oldpqcPublicKeyFactory:The old
pqc.crypto.mldsa.MLDSAPublicKeyParametersand the newcrypto.params.MLDSAPublicKeyParametersare separate class hierarchies (both extendAsymmetricKeyParameterthrough their ownMLDSAKeyParameters), so the castalways fails.
Suggested fix
In
SignatureSpi.verifyInit(), replace the oldpqcfactory with the new one:The new
crypto.util.PublicKeyFactoryreturnscrypto.params.MLDSAPublicKeyParameters, which matches the imported type and makes the cast succeed.Impact
Any code that passes a non-BC
PublicKey(or a certificate parsed by a non-BCCertificateFactory) toSignature.initVerify()for ML-DSA will silently fail verification. This is particularly likely on JDK 25 where the defaultSUN provider natively handles ML-DSA certificates.
This is a regression from BC 1.83 where both the
PublicKeyFactoryandSignatureSpiconsistently used thepqc.crypto.mldsapackage types.