Add RFC 9964 AKP JWK support for ML-DSA keys - #1072
Open
Arpan0995 wants to merge 1 commit into
Open
Conversation
Adds the AKP (Algorithm Key Pair) JWK key type defined by RFC 9964 for the ML-DSA parameter sets (FIPS 204): API interfaces and builders mirroring the Octet (OKP) family, family factories registered in DispatchingJwkFactory, dynamic builder dispatch, and RFC 9964 JWK thumbprint support (alg, kty, pub). ML-DSA keys have no java.security.interfaces type, so parameter sets are identified by JCA algorithm name when the provider reports a specific one, falling back to the algorithm OID in the key's ASN.1 encoding, following the EdwardsCurve.findByKey approach. Per RFC 9964, an AKP private JWK's priv value is the 32-byte seed. PKCS#8 parsing handles all three ML-DSA-PrivateKey CHOICE alternatives (seed, expandedKey, both). Seed import falls back to BouncyCastle on JDK 24 through 26, whose SUN provider accepts only the expandedKey form. See issue 1042.
Arpan0995
force-pushed
the
rfc9964-akp-jwk
branch
from
August 5, 2026 04:06
28ac6cd to
a097731
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This implements the JWK layer for RFC 9964 that I described in #1042: the
AKP(Algorithm Key Pair) key type with itspubandprivparameters, and the factory, builder and parser wiring for the ML-DSA parameter sets.What's included
AkpPublicJwk/AkpPrivateJwkAPI interfaces plus their builder interfaces, mirroring the Octet (OKP) familyMlDsaAlgorithm(impl): parameter set identification for keys that have nojava.security.interfacestype. Lookup is by JCA algorithm name when the provider reports a specific one (BouncyCastle reportsML-DSA-65), falling back to the algorithm OID in the key's ASN.1 encoding, since the JDKSUNprovider reports the genericML-DSAname for all three sets. This follows theEdwardsCurve.findByKeyapproach.AkpPublicJwkFactory/AkpPrivateJwkFactoryregistered inDispatchingJwkFactory, newakpKey/akpKeyPairbuilder methods, and dispatch from the generickey(PublicKey)/key(PrivateKey)methodsalg,kty,pub)Design notes
A few decisions worth calling out for review:
privis the 32-byte seed. RFC 9964 Section 4 requires it ("the priv parameter MUST be the seed and MUST have a length of 32 bytes"). Not every provider retains the seed: JDK 24 through 26 encode ML-DSA private keys as an expanded key only, and the seed cannot be recovered from the expanded form. Creating anAkpPrivateJwkfrom such a key fails with a clearInvalidKeyExceptionexplaining why. Keys parsed from an existing AKP JWK, BouncyCastle keys, and JDK 27+ keys (which switched to the seed encoding) all work. The PKCS#8 parser handles all threeML-DSA-PrivateKeyCHOICE alternatives (seed,expandedKey,both); BouncyCastle emitsboth, JDK 27 emitsseed.Seed import needs an explicit BouncyCastle retry on JDK 24 through 26. Those JDKs have ML-DSA, so the existing
NoSuchAlgorithmException-triggered fallback inJcaTemplatenever fires, yet theirKeyFactoryrejects the RFC 9964 seed encoding (InvalidKeySpecException: Cannot parse input).MlDsaAlgorithmretries with BouncyCastle in that case, only when the caller did not specify a provider. I verified the JDK behavior empirically on 24.0.2 and 27-ea, and the BC path on 1.84 (the version this project pins).Building a private AKP JWK requires the public key. There is no JCA API to derive an ML-DSA public key from a private key, unlike RSA, EC and Edwards keys, so
Jwks.builder().key(privateKey).build()alone cannot work. The factory throws anInvalidKeyExceptiondirecting the caller toJwks.builder().key(privateKey).publicKey(publicKey).build(), and mismatched parameter sets between the two are rejected.Strict length validation. Public key material must be exactly 1312, 1952 or 2592 bytes for ML-DSA-44, -65 and -87 respectively, and seeds must be exactly 32 bytes, so malformed JWKs are rejected at parse time rather than surfacing later as provider errors.
Testing
45 new tests across
AkpJwksTestandMlDsaAlgorithmTest: round trips for all three parameter sets (build, serialize, parse, and sign with a parsed key), RFC 9964 thumbprint member order, rejection paths (missing or mismatchedalg, wrong-lengthpubandpriv, unrecognizedkty), and byte-level tests for every PKCS#8 CHOICE alternative and malformed-encoding branch using handcrafted encodings, so they run identically on every JDK. The full suite passes on JDK 17, 21 and 24 locally, and the new and modified files are at 100% method, statement and conditional coverage under the Clover gate.Scope: this PR is the JWK layer only. Registering
ML-DSA-44/65/87as standardJwts.SIGalgorithms would be the natural follow-up, and I am happy to do that as a separate PR if you want it.