Skip to content

Composite ML-KEM public and private keys return null from getAlgorithm() #2404

Description

@Arpan0995

Summary

On 1.86-SNAPSHOT (provider version 1.8599) and current main, the key pairs produced for every composite ML-KEM algorithm (the twelve id-MLKEM768-* / id-MLKEM1024-* OIDs from draft-ietf-lamps-pq-composite-kem, registered as MLKEM768-X25519-SHA3-256, MLKEM1024-ECDH-P384-SHA3-256 and so on) return null from getAlgorithm() on both the public and the private key. java.security.Key.getAlgorithm() is specified to return the algorithm name, so code that keys off it, such as KeyFactory.getInstance(key.getAlgorithm()), fails with a NullPointerException, and an X.509 certificate carrying a composite ML-KEM key reports cert.getPublicKey().getAlgorithm() == null. The composite ML-DSA keys, which use the same key classes, return the expected names.

Reproduction

Security.addProvider(new BouncyCastleProvider());
KeyPair kp = KeyPairGenerator.getInstance("MLKEM768-X25519-SHA3-256", "BC").generateKeyPair();
kp.getPublic().getAlgorithm();    // null
kp.getPrivate().getAlgorithm();   // null
kp.getPublic().getFormat();       // "X.509" (encoding itself is fine)

KeyFactory kf = KeyFactory.getInstance("MLKEM768-X25519-SHA3-256", "BC");
kf.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded())).getAlgorithm();   // null as well

KeyFactory.getInstance(kp.getPublic().getAlgorithm(), "BC");   // NullPointerException: null algorithm name

// contrast
KeyPairGenerator.getInstance("MLDSA65-Ed25519-SHA512", "BC").generateKeyPair()
    .getPublic().getAlgorithm();  // "MLDSA65-Ed25519-SHA512"

Checked for all twelve composite KEM algorithms: 12 of 12 null for the generated public key, 12 of 12 for the private key, and 12 of 12 after a KeyFactory round trip through X509EncodedKeySpec / PKCS8EncodedKeySpec. The encodings round-trip byte for byte, so this is only the name. Storing such a private key in a PKCS12, BCFKS or JKS keystore and reading it back works, and the recovered key still reports null.

Root cause

CompositePublicKey and CompositePrivateKey are shared between the composite signature and composite KEM providers, and both resolve the name through the signature side's index:

  • prov/src/main/java/org/bouncycastle/jcajce/CompositePublicKey.java line 19 imports org.bouncycastle.jcajce.provider.asymmetric.compositesignatures.CompositeIndex, and getAlgorithm() (line 222) is return CompositeIndex.getAlgorithmName(this.algorithmIdentifier.getAlgorithm());
  • CompositePrivateKey.java line 25 and line 246 do the same.

That getAlgorithmName is a plain map lookup (compositesignatures/CompositeIndex.java lines 126 to 129, return algorithmNames.get(algorithm);) over the composite signature OIDs only, so every composite KEM OID yields null. The KEM side has its own index with the right names, compositekem/CompositeIndex.getAlgorithmName (lines 154 to 157, for example "MLKEM768-X25519-SHA3-256" at line 57), and the KEM KeyPairGeneratorSpi constructs the shared key classes with the KEM OID (lines 103 and 104), so the name is available but never consulted.

Impact

Any caller that dispatches on key.getAlgorithm() or passes it back into a JCA factory, which is the usual way to reconstruct or wrap a key whose type is not known up front, gets a NullPointerException for these keys, and anything that displays or logs the algorithm of a composite KEM certificate shows null. No security impact; the keys encode, decode and decapsulate correctly.

Suggested fix

Have CompositePublicKey.getAlgorithm() and CompositePrivateKey.getAlgorithm() fall back to the KEM index when the signature index has no entry (or look the OID up in both), so the method returns the same name the provider registers the algorithm under. Happy to send a PR if that is useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions