Skip to content

Add ML-DSA-65 and ML-DSA-87 certificate parsing and verification - #312

Open
justsem wants to merge 2 commits into
apple:mainfrom
wendylabsinc:mldsa-support
Open

Add ML-DSA-65 and ML-DSA-87 certificate parsing and verification#312
justsem wants to merge 2 commits into
apple:mainfrom
wendylabsinc:mldsa-support

Conversation

@justsem

@justsem justsem commented Aug 11, 2026

Copy link
Copy Markdown

swift-certificates can't yet parse a certificate signed with ML-DSA: it throws on the issuer's signature algorithm before verification even starts, even though swift-crypto already supports these algorithms. That turned out to be a blocker in one of our projects involving PQ PKI.

This PR adds parsing and verification for ML-DSA-65 and ML-DSA-87, per RFC 9881.

  • First commit bumps swift-crypto to 4.0.0, where MLDSA65/MLDSA87 was introduced.
  • Certificates signed by an ML-DSA CA now parse and round-trip byte-for-byte. ML-DSA public keys work through the usual Certificate.PublicKey API, and chains validate through Verifier with RFC5280Policy.
  • ML-DSA requires macOS/iOS 26 at runtime (since it comes from CryptoKit there). Below that, decoding an ML-DSA key fails with an error.

Motivation:

The upcoming ML-DSA support uses the MLDSA65/MLDSA87 APIs that first
appeared in swift-crypto 4.0.0. Raising the floor in a minor release
follows this repository's own precedent (apple#39, apple#275, apple#300). A SwiftPM
trait was considered instead, but traits are used in the ecosystem to
gate optional dependencies, not versions of a required one, and
SE-0450 cannot express a per-trait version floor.

Modifications:

- Raise the swift-crypto lower bound from 3.12.3 to 4.0.0 in
  Package.swift and Benchmarks/Package.swift.

Result:

swift-certificates requires swift-crypto 4.0.0 or later, which
dependency resolution already selects for any graph that does not pin
swift-crypto below 4.
Motivation:

RFC 9881 registers algorithm identifiers for ML-DSA (FIPS 204) in
X.509, and swift-crypto ships MLDSA65 and MLDSA87. swift-certificates
could not even parse a certificate whose issuer signed with ML-DSA:
Certificate.init threw on the issuer's signature algorithm before any
verification was attempted, blocking post-quantum CA hierarchies.

Modifications:

- Add the id-ml-dsa-65 and id-ml-dsa-87 OIDs and AlgorithmIdentifier
  statics (parameters absent, per RFC 9881), and
  Certificate.SignatureAlgorithm.mldsa65/.mldsa87.
- Extend Certificate.Signature with a backing case carrying the raw
  signature bytes, mirroring Ed25519.
- Add MLDSA.swift: ML-DSA public keys are stored as validated raw
  bytes and rebuilt per verification, because CryptoKit's ML-DSA types
  are macOS 26+ on Darwin and enum cases cannot carry availability.
  Below macOS 26 an ML-DSA subject public key fails loudly at decode;
  on Linux support is unconditional.
- Extend Certificate.PublicKey with SPKI decode/encode, verification
  dispatch, and the standard wrap/unwrap initializers.
- Add MLDSATests with OpenSSL 3.6.3 interop fixtures covering parse,
  byte-identical round-trips, self-signature known-answer tests,
  tampered/wrong-parameter-set/wrong-root/padding-bits negatives,
  ML-DSA-44 rejection, and chain validation through Verifier with
  RFC5280Policy.

Signing, ML-DSA-44, HashML-DSA, and TLS signature-scheme codepoints
are deliberately not supported and throw unsupportedSignatureAlgorithm.

Result:

Certificates signed with ML-DSA-65 or ML-DSA-87 parse, round-trip,
and verify, including full chains through the normal verifier path.
Comment thread Benchmarks/Package.swift
.package(path: "../"),
.package(url: "https://github.com/ordo-one/benchmark.git", from: "1.11.1"),
.package(url: "https://github.com/apple/swift-crypto.git", "3.12.3"..<"5.0.0"),
.package(url: "https://github.com/apple/swift-crypto.git", "4.0.0"..<"5.0.0"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was discussing using a package trait to avoid needing this, let us know what works.

@Joannis

Joannis commented Aug 11, 2026

Copy link
Copy Markdown

CC @fabianfett if you're back, could we get a CI run on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants