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) #

stable0.10.13
{
  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) #

stable0.10.13
{
  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?) #

stable0.10.13
{
  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?) #

stable0.10.16
{
  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.