CMS Codec
RFC 5652 Cryptographic Message Syntax encoder + decoder built on the framework's existing b.asn1Der substrate and the vendored noble-post-quantum primitives (b.pqcSoftware.ml_dsa_* / ml_kem_1024 / slh_dsa_shake_256f). Re-opens the CMS forward- watch item from the 2026-05-08 audit (deferred-with-condition pending operator-side demand from the live mail-stack listeners). Operator-demand condition is now met by the inbound MX + JMAP listeners (v0.9.45–v0.9.50).
Scope (v0.10.13):
- **ContentInfo** wrapper (RFC 5652 §3) for all top-level emissions. - **SignedData** (§5) encode + decode with PQC signer support (ML-DSA-65 per RFC 9909 §5, ML-DSA-87 per RFC 9909 §6, SLH-DSA-SHAKE-256f per RFC 9881). The signature input is the DER-encoded SET OF signed-attributes with the IMPLICIT [0] tag re-tagged to the universal SET tag per §5.4 third paragraph. - **EnvelopedData** (§6) encode with KEMRecipientInfo (RFC 9629) for ML-KEM-1024 recipients (RFC 9936). The content-encryption key is wrapped under a KEK derived from the KEM shared-secret via HKDF-SHA3-512; content is encrypted with ChaCha20-Poly1305 (RFC 8103 OID). Efail-class CBC-malleability is impossible by construction — every CMS content blob emitted by this module carries an AEAD tag. - Strict DER on emit (canonical: lexicographic SET-OF ordering, minimal-length encoding, no indefinite length).
Deferred from v0.10.13 (each with documented condition):
- **AuthEnvelopedData** (RFC 5083) as a distinct ContentInfo ciphertext shape. Operator demand is not yet surfaced — every v0.10.13 emission uses EnvelopedData with the ChaCha20-Poly1305 content-encryption OID, which is already AEAD by construction. Defer condition: at least one interop case requires a peer that refuses EnvelopedData and accepts only the §5083 ContentInfo OID. Cheap escape hatch: operators on such a peer compose b.asn1Der directly to rewrap an EnvelopedData blob into an AuthEnvelopedData ContentInfo. A built-in encode path is deferred until an interop case requires a peer that refuses EnvelopedData; the b.asn1Der rewrap covers the gap until then. - **b.cms.decode parse-tree of inner SignedData / EnvelopedData** beyond the ContentInfo wrapper. b.cms.decode returns the inner SEQUENCE bytes as content (an asn1-der node); callers that need fielded access walk it via b.asn1Der.readSequence. Built-in fielded decoders are deferred until they're actually consumed by a shipping primitive; the b.asn1Der.readSequence walk is the escape hatch until then.
Refusal posture:
- Top-level must be SEQUENCE { OID, [0] EXPLICIT content }; any other shape throws cms/bad-content-info. - Recipient/signer counts must be non-empty (cms/no-signers / cms/no-recipients). - Only PQC signature algorithms are accepted (cms/bad-sig-alg). - Only ML-KEM-1024 recipients are accepted (cms/bad-recipient-type). - Input past opts.maxBytes (default 64 MiB) throws cms/oversize.
b.cms.encodeSignedData(opts) #
{
encapContent: Buffer, // bytes to sign
digestAlg: "sha3-256" | "sha3-512", // default sha3-512
signers: [{ certificate: Buffer, secretKey: Uint8Array, sigAlg: string }],
certificates: Buffer[], // additional DER certs (optional)
detached: boolean, // default false; true → omit encapContent
}
Encode an RFC 5652 §5 SignedData ContentInfo with PQC signer support. The output is a DER-encoded Buffer ready for embedding in S/MIME application/pkcs7-mime; smime-type=signed-data parts or for standalone CMS-over-network use.
var pq = b.pqcSoftware;
var kp = pq.ml_dsa_65.keygen();
var bytes = b.cms.encodeSignedData({
encapContent: Buffer.from("payload"),
digestAlg: "sha3-512",
signers: [{ certificate: certDer, secretKey: kp.secretKey, sigAlg: "ML-DSA-65" }],
});
b.cms.encodeEnvelopedData(opts) #
{
plaintext: Buffer, // bytes to encrypt
recipients: [{ type: "kem-mlkem-1024", publicKey: Uint8Array, recipientId: Buffer }],
}
Encode an RFC 5652 §6 EnvelopedData ContentInfo with ML-KEM-1024 recipients per RFC 9629 (KEMRecipientInfo) + RFC 9936 (ML-KEM in CMS). The content-encryption key is wrapped under a KEK derived from the per-recipient KEM shared-secret via HKDF-SHA3-512; content is encrypted with ChaCha20-Poly1305 so Efail-class malleability cannot apply.
var pq = b.pqcSoftware;
var kp = pq.ml_kem_1024.keygen();
var bytes = b.cms.encodeEnvelopedData({
plaintext: Buffer.from("secret"),
recipients: [{ type: "kem-mlkem-1024", publicKey: kp.publicKey, recipientId: Buffer.from([1]) }],
});
b.cms.decode(buf, opts?) #
{
maxBytes: number, // default 64 MiB
}
Decode a CMS ContentInfo from buf (DER bytes). Returns { contentType, content } where contentType is the dotted-OID string (e.g. "1.2.840.113549.1.7.2" for SignedData) and content is the inner asn1-der node (SignedData / EnvelopedData / other) — operators walk it via b.asn1Der.readSequence. Fielded decoders for SignedData / EnvelopedData are deferred; the b.asn1Der.readSequence walk is the escape hatch until then.
Refuses input past opts.maxBytes (default 64 MiB), top-level non-SEQUENCE shapes, missing OID + [0] EXPLICIT child pair.
var ci = b.cms.decode(derBytes);
ci.contentType; // → "1.2.840.113549.1.7.2"
b.cms.parseSignedData(buf, opts?) #
{
maxBytes: number, // default 64 MiB
}
Decode a CMS ContentInfo carrying SignedData and walk into the inner structure per RFC 5652 §5.1. Returns a structured object with digestAlgs, encapContent, certificates, and signerInfos arrays so downstream verifiers (b.mail.crypto.smime.verify) can check signatures without re-implementing the SignedData walker.
var sd = b.cms.parseSignedData(derBytes);
sd.signerInfos[0].sigAlgOid; // → "2.16.840.1.101.3.4.3.18" (ML-DSA-65)
Last updated 2026-08-08T16:39:49.652Z by seeder.