S/MIME signed and encrypted mail, with ESS security labels

S/MIME Enhanced Security Services signed-attribute values per RFC 5035 (ESS) and RFC 8551 (S/MIME 4.0). These are the DER-decoded VALUES of CMS signed attributes: they ride inside a SignerInfo.signedAttrs, so this is a companion decoder a CMS consumer invokes by attribute OID, not a top-level format the schema orchestrator auto-routes.

parseSigningCertificate / parseSigningCertificateV2 decode the ESS signing-certificate attributes that bind a signature to the exact certificate that made it: each surfaces its list of ESSCertID(v2) (the certificate hash (raw), the hash algorithm (decoded for v2, or the implied SHA-1 for v1), and the optional issuerSerial (issuer GeneralNames validated + surfaced raw, serial as a BigInt + hex)) plus the optional certificate policies. buildSigningCertificateV2 encodes the v2 attribute value a signer attaches: one ESSCertIDv2 per certificate, the SHA-256 form with the DEFAULT algorithm omitted and SHA-384 / SHA-512 with an absent parameters field. parseSmimeCapabilities decodes the ordered SMIMECapabilities list (each a capability OID + raw parameters). decodeAttribute takes a CMS-shaped { type, values } attribute, enforces the single-AttributeValue rule (RFC 8551 sec. 2.5.2), routes on the attribute OID, and recognize-and-defers an unknown attribute type with its raw values intact.

Structure is decoded; verification is the consumer's. The parser surfaces certHash + hashAlgorithm + issuerSerial so a verifier recomputes the certificate hash (compose webcrypto) and matches the issuer/serial against the actual signing certificate; it never recomputes a hash or trusts a cert. Whether an attribute is correctly placed in signedAttrs (vs unsignedAttrs) is the CMS consumer's knowledge. DER-only, fail-closed.

pki.schema.smime.parseSigningCertificate

since 0.1.22 stable
pki.schema.smime.parseSigningCertificate(der, caps?) -> { certs, policies }

Decode an ESS v1 SigningCertificate attribute value (RFC 5035 sec. 5.4.2): the raw AttributeValue a CMS consumer plucks off SignerInfo.signedAttrs. Returns { certs, policies }: each certs entry is { certHash, hashAlgorithm, issuerSerial } in wire order (the first is the signing certificate), where hashAlgorithm is the implied SHA-1 (v1 carries no algorithm field) and issuerSerial (or null) surfaces the issuer GeneralNames + serial. Throws a typed smime/* (or leaf asn1/*) error on malformed input.

Options

- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
  defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
  above it, or an option outside this set, is refused. A parse that exceeds one is
  refused with the `/too-large` code of this format.

Example

var b = pki.asn1.build;
var essCertId = b.sequence([b.octetString(Buffer.alloc(20, 1))]);   // SHA-1 hash, no issuerSerial
var av = b.sequence([b.sequence([essCertId])]);                     // SigningCertificate { certs }
var sc = pki.schema.smime.parseSigningCertificate(av);
sc.certs[0].hashAlgorithm.name;    // "sha1" (implied)

References

pki.schema.smime.parseSigningCertificateV2

since 0.1.22 stable
pki.schema.smime.parseSigningCertificateV2(der) -> { certs, policies }

Decode an ESS v2 SigningCertificateV2 attribute value (RFC 5035 sec. 5.4.1). Identical shape to v1 but each certs entry carries a real hashAlgorithm: decoded when present, or the RFC 5035 sec. 4 default id-sha256 (with defaulted: true) when omitted. An explicit hashAlgorithm byte-equal to that default is a non-canonical DER encoding and is rejected smime/non-canonical-default (X.690 sec. 11.5). Throws a typed smime/* error on malformed input.

Example

var b = pki.asn1.build;
var essCertId = b.sequence([b.octetString(Buffer.alloc(32, 2))]);   // hashAlgorithm defaulted
var av = b.sequence([b.sequence([essCertId])]);
var sc = pki.schema.smime.parseSigningCertificateV2(av);
sc.certs[0].hashAlgorithm.defaulted;   // true (SHA-256 default)

References

pki.schema.smime.parseSmimeCapabilities

since 0.1.22 stable
pki.schema.smime.parseSmimeCapabilities(der, caps?) -> { capabilities }

Decode an SMIMECapabilities attribute value (RFC 8551 sec. 2.5.2) into { capabilities }, an ordered list (preference order, never sorted), each { capabilityID, name, parameters } with parameters the raw ANY DEFINED BY capabilityID bytes (or null). Throws a typed smime/* error on malformed input.

Options

- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
  defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
  above it, or an option outside this set, is refused. A parse that exceeds one is
  refused with the `/too-large` code of this format.

Example

var b = pki.asn1.build;
var cap = b.sequence([b.oid(pki.oid.byName("aes256-CBC"))]);
var caps = pki.schema.smime.parseSmimeCapabilities(b.sequence([cap]));
caps.capabilities[0].name;    // "aes256-CBC"

References

pki.schema.smime.buildSigningCertificateV2

since 0.8.52 stable
pki.schema.smime.buildSigningCertificateV2(certs, opts?) -> Buffer

Encode an ESS SigningCertificateV2 attribute value (RFC 5035 sec. 5.4.1), the signed attribute that binds a signature to the certificate that made it. certs is the signer's certificate DER, or a list of certificate DERs whose first entry is the signer's (RFC 5035 sec. 5.4); each entry becomes an ESSCertIDv2 carrying that certificate's hash. The result is the single AttributeValue of a signingCertificateV2 signed attribute, which is what CAdES requires a signature to carry (ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3).

opts.hashAlgorithm names the binding digest: sha256 (the default), sha384 or sha512. The SHA-256 form omits the hashAlgorithm field, which is the RFC 5035 sec. 4 DEFAULT and so must be absent (X.690 sec. 11.5); the other two emit an AlgorithmIdentifier with the parameters field absent, which RFC 5754 sec. 2 requires an implementation to generate. SHA-1 is refused smime/unsupported-algorithm: a binding is an identity claim, and pki.tsp.verify refuses a SHA-1 one as a weak binding.

The OPTIONAL issuerSerial and policies fields are not emitted; CAdES has the issuer serial omitted (ETSI EN 319 122-1 clause 6.3 requirement g). A caller that needs either field builds the value with pki.asn1.build, and parseSigningCertificateV2 reads both back.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var key = await pki.key.export(pair.privateKey);
  var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
  var av = pki.schema.smime.buildSigningCertificateV2(signerCertDer);
  var msg = await pki.cms.sign(Buffer.from("hello"), { cert: signerCertDer, key: key },
    { additionalSignedAttributes: [{ type: "signingCertificateV2", values: [av] }] });
  (await pki.cms.verify(msg, { certs: [signerCertDer] })).signers[0].cadesBaseline.conformant;   // true
}
example();

References

pki.schema.smime.decodeAttribute

since 0.1.22 stable
pki.schema.smime.decodeAttribute(attr) -> { kind, ... }

OID-dispatch convenience over the three value decoders for a CMS-shaped { type, values } attribute (the shape cms.parse surfaces on signerInfos[i].signedAttrs). Enforces the single-AttributeValue rule (RFC 8551 sec. 2.5.2 / sec. 2.5), so a values length other than 1 is rejected smime/multi-valued-attribute, then routes on attr.type: signingCertificate / signingCertificateV2 / smimeCapabilities decode to { kind, ...result }; any other type is recognize-and-deferred smime/unsupported-attribute (its type, registry name, and raw values carried on the error so a caller keeps the bytes).

Example

var b = pki.asn1.build;
var essCertId = b.sequence([b.octetString(Buffer.alloc(32, 2))]);   // ESSCertIDv2, hashAlgorithm defaulted
var av = b.sequence([b.sequence([essCertId])]);                     // SigningCertificateV2 { certs: [ ESSCertIDv2 ] }
var got = pki.schema.smime.decodeAttribute({ type: pki.oid.byName("signingCertificateV2"), values: [av] });
got.kind;    // "signingCertificateV2"

References

pki.smime.sign

since 0.2.25 stable
pki.smime.sign(content, signers, opts?) -> Promise<Buffer>

Assemble a signed S/MIME message (RFC 8551). content is the payload: a raw body wrapped as a text/plain entity by default, or the caller's own complete MIME entity when opts.entity is set; signers is the pki.cms.sign signer array (any RSA / RSASSA-PSS / ECDSA / EdDSA / ML-DSA / SLH-DSA signer: the S/MIME layer is algorithm-agnostic). Each signer's certificate must be one a conforming receiving agent accepts a mail signature under: a keyUsage extension it carries must assert digitalSignature or nonRepudiation (RFC 8550 sec. 4.4.2) and an extendedKeyUsage extension it carries must include emailProtection or anyExtendedKeyUsage (sec. 4.4.4); a certificate with neither extension signs, and one failing either rule is refused with smime/bad-signer-certificate. The rule is about the certificate the message carries: a key-only signer ({ key, spki, keyIdentifier }, the form RFC 8550 sec. 3 permits where correspondents hold each other's certificates by other means) carries none, and the reader holds the certificate it supplies to the same rule. Two forms via opts.form: - "multipart" (default, clear-signed): a multipart/signed message carrying the canonical entity verbatim in the first part and a DETACHED CMS SignedData (application/pkcs7-signature) over its canonical form in the second, with protocol="application/pkcs7-signature" + a matching micalg. - "pkcs7-mime" (opaque): one application/pkcs7-mime; smime-type=signed-data entity whose base64 body is an ATTACHED CMS SignedData over the canonical entity. The signed bytes are the entity's RFC 8551 sec. 3.1.1 canonical form (CRLF line endings); the same canonicalizer runs on verify. With opts.protectHeaders, the message is header-protected (RFC 9788): the caller's opts.headers are inlined on the Cryptographic Payload root (its Content-Type gains hp="clear"), so the signature covers them, and copied to the outer display headers; verify surfaces the authenticated inner set. Returns the assembled message bytes. Fail-closed with SmimeError.

Options

additionalSignedAttributes forwarded: extra signed attributes to carry, as `[{ type, values }]`.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
  var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
  var msg = await pki.smime.sign(Buffer.from("hello"), [{ cert: signerCertDer, key: signerKeyPkcs8 }]);
}
example();

References

pki.smime.verify

since 0.2.25 stable
pki.smime.verify(message, opts?) -> Promise<{ valid, trusted, signers, form, content, micalg, sender, protectedHeaders, headerProtection }>

Unwrap and verify a signed S/MIME message (RFC 8551), both multipart/signed and application/pkcs7-mime; smime-type=signed-data. For multipart/signed the detached CMS signature is recomputed over the first part's RFC 8551 sec. 3.1.1 canonical form (the same canonicalizer the signer used); for application/pkcs7-mime the base64 body is the attached CMS SignedData. Returns pki.cms.verify's { valid, trusted, signers } verdict PLUS form, the recovered content (the signed MIME entity bytes), and the micalg.

valid and trusted are separate claims, exactly as in cms.verify: a SignedData carries its own certificates, so valid says the signature is sound under one of them and nothing about who signed. Name the roots you accept in opts.trustAnchors and trusted says every signer chained to one, validated for EMAIL, at both ends of the chain. The signer certificate must carry the emailProtection key purpose (RFC 8550 sec. 4.4.4), because a certificate restricted to serverAuth chains to its root perfectly well and is still the wrong key to have signed a message; and the anchor's own trust metadata must permit that purpose, because a root distributed with NSS trust bits can be marked untrusted for email while remaining a good TLS root. Override either with opts.requiredEku and opts.checkPurpose. Supply no anchors and trusted is false, since there was nothing to chain to. A micalg that disagrees with the actual digest is advisory unless opts.strictMicalg (then smime/micalg-mismatch). If the message is header-protected (RFC 9788), protectedHeaders is the AUTHENTICATED inner header set (a tampered outer header cannot alter it) and headerProtection is { present, mode, fromMismatch, confidential, legacy }. present is true only for cryptographically-DECLARED (hp=) protection you may trust; fromMismatch is true when the outer From differs from the protected one, false when they agree, and null when there was no protected From to compare against, which is every message without header protection. Test it against false rather than for falsiness: !fromMismatch treats the unanswered case as a passed check, and that is the common case. For a binding that does not depend on the composer having protected headers, use sender with opts.expectedSender; confidential lists the protected fields the composer kept end-to-end confidential (per the authenticated HP-Outer records, RFC 9788 sec. 4.3; only for an encrypted hp="cipher" payload). A non-protected message reports protectedHeaders: null, present: false. A payload whose declared hp is malformed, invalid, or contradicts the envelope fails closed (smime/bad-header-protection), never a silent downgrade. legacy is null unless opts.legacyHeaderProtection detected a legacy RFC 8551 message/rfc822 wrap (see that option), in which case it is its own { headers, mode, fromMismatch, confidential } object (headers an ordered [{ name, value }] array that retains legally-repeated fields like Received). A legacy inference is never placed in protectedHeaders and never sets present: true, because it is indistinguishable from an ordinary forwarded message/rfc822, so a caller keying trust off present / protectedHeaders cannot mistake the opt-in heuristic for authenticated headers.

sender answers who the message is from, which valid does not: a signature proves a key signed, never that the message came from the mailbox the reader sees. Name the address you expect in opts.expectedSender and sender.match is true only when the signer certificate asserts it, compared under RFC 5280 sec. 7.5: the local-part exactly, the host-part case-insensitively. The address is read from the subjectAltName rfc822Name entries (RFC 8550 sec. 4.4.3) and, where the extension carries none, from the subject DN's PKCS #9 emailAddress attribute, which RFC 8550 sec. 3 requires a receiving agent to recognize. Where both are present the extension wins. Only a signer whose signature verified contributes an identity, so a tampered message cannot report a binding. match is THREE-valued and a caller enforcing sender binding tests match === true; false means every identity was comparable and none matched, and null means the question was not answered (no expectedSender and no single outer From, a signer certificate asserting no readable email identity, or an internationalized address needing an IDNA transform this toolkit does not perform). null is not a pass. sender.identities lists what the certificate actually asserts, sender.checked whether a comparison ran, and sender.source which input it ran against. "from" is ADVISORY, because on a message without header protection the outer From is attacker-controlled.

Options

legacyHeaderProtection  opt in to detecting a LEGACY RFC 8551 header-protected message (RFC 9788 sec. 4.10): a Cryptographic Payload that is a bare `message/rfc822` wrap with no `hp=` parameter. When set, a precisely-identified legacy message surfaces the inner message's headers under `headerProtection.legacy = { headers, mode, fromMismatch, confidential }`, where `headers` is an ordered `[{ name, value }]` array (retaining legally-repeated fields such as `Received`) and the mode is inferred from the envelope (`clear` here). They are not placed under `protectedHeaders`, and `present` stays `false`. Consuming `headerProtection.legacy.headers` is an explicit choice: a legacy message is structurally indistinguishable from an ordinary forwarded `message/rfc822`, so this is a heuristic (RFC 9788 sec. 4.10.2: "not based on any strong end-to-end guarantees"); cross-check `legacy.fromMismatch`. Anything not precisely identified (a nested crypto layer, an `hp=` on the inner message, a non-`message/rfc822` payload, a duplicate of a singleton field, or a duplicate Content-Type) reports `legacy: null`. Off by default. The signed-and-encrypted form (RFC 9788 Appendix C.3.17) is a documented gap (`legacy: null` at `decrypt`; surfaces as `clear` only via the caller's re-`verify` step), because the non-recursive layered API exposes no single seam holding both the inner signature verdict and the outer header section.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var key = await pki.key.export(pair.privateKey);
  var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
  var smimeMessageBytes = await pki.smime.sign(Buffer.from("hello"), [{ cert: cert, key: key }]);
  var res = await pki.smime.verify(smimeMessageBytes);
  if (res.valid) { res.content; res.signers[0].sid; }
}
example();

References

pki.smime.encrypt

since 0.2.26 stable
pki.smime.encrypt(content, recipients, opts?) -> Promise<Buffer>

Envelope a MIME entity as an encrypted S/MIME message (RFC 8551 sec. 3.3 / sec. 3.4). content is the payload: a raw body wrapped as a text/plain entity by default, or the caller's own complete MIME entity when opts.entity is set; recipients is the pki.cms.encrypt recipient array (any RSA-OAEP ktri / EC or X25519/X448 kari / ML-KEM ori-KEM / password pwri / kek kekri: the S/MIME layer is recipient-agnostic; a single descriptor is accepted and normalized to a one-element array). Enveloping has a single form, opaque application/pkcs7-mime with the whole entity base64-encoded. The smime-type is derived from the produced CMS: AES-GCM (the default) yields an AuthEnvelopedData with smime-type=authEnveloped-data (confidentiality AND integrity); a CBC choice yields an EnvelopedData with smime-type=enveloped-data (confidentiality only, no integrity, RFC 8551 sec. 3.3). With opts.protectHeaders, the message is header-protected (RFC 9788): the real opts.headers are inlined inside the ciphertext (the payload Content-Type gains hp="cipher"), and only the Header-Confidentiality- Policy-processed display copies appear outside. The default hcp_baseline obscures Subject to [...] and removes Comments / Keywords, so those values live only in the ciphertext; decrypt recovers the real inner set. Returns the assembled message bytes. Fail-closed with SmimeError.

Options

ukm                        forwarded: user keying material for kari / kemri recipients.

Example

async function example() {
  var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
  var pair = await pki.key.generate(rsa);
  var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(pair.privateKey) });
  var enc = await pki.smime.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
}
example();

References

pki.smime.decrypt

since 0.2.26 stable
pki.smime.decrypt(message, keyMaterial, opts?) -> Promise<{ content, smimeType, authenticated, recipientType, recipientIndex, contentEncryptionAlgorithm, protectedHeaders, headerProtection }>

Open an encrypted S/MIME message (RFC 8551 sec. 3.3 / sec. 3.4), an application/pkcs7-mime entity whose base64 body is a CMS EnvelopedData or AuthEnvelopedData. keyMaterial is the pki.cms.decrypt key material ({ key, cert }, { password }, or { kek, kekId? }). Returns the recovered inner MIME entity as content, the smimeType, authenticated (true only for AuthEnvelopedData; a CBC enveloped-data message reports false, the RFC 8551 sec. 3.3 / EFAIL no-integrity caveat), and the recipientType / recipientIndex / contentEncryptionAlgorithm from the CMS layer. Fail-closed and oracle-free: every secret-dependent failure collapses to the uniform cms/decrypt-failed the CMS layer emits (this layer only propagates it). A recovered content that is itself a signed S/MIME message is returned as-is for the caller to feed back to pki.smime.verify (no auto-recursion). Accepts OpenSSL's legacy application/x-pkcs7-mime and a missing smime-type. If the decrypted payload is header-protected (RFC 9788, hp="cipher"), protectedHeaders is the recovered real inner header set (the values the outer Header Confidentiality Policy hid) and headerProtection is { present, mode, fromMismatch, confidential, legacy }, where present is true only for a declared hp= payload, and confidential names the fields the composer kept end-to-end confidential (via the authenticated HP-Outer records, RFC 9788 sec. 4.3), so a caller can reply/forward without leaking them (sec. 6.1); a payload whose hp is malformed or contradicts the envelope fails closed (smime/bad-header-protection). legacy is null unless opts.legacyHeaderProtection detected a legacy RFC 8551 message/rfc822 wrap (its own { headers, mode, fromMismatch, confidential } object, where headers is an ordered [{ name, value }] array; it is never merged into protectedHeaders / present).

Options

legacyHeaderProtection  opt in to detecting a LEGACY RFC 8551 header-protected message (RFC 9788 sec. 4.10): an encrypted Cryptographic Payload that is a bare `message/rfc822` wrap with no `hp=` parameter. When set, a precisely-identified legacy message surfaces the inner headers under `headerProtection.legacy = { headers, mode: "cipher", fromMismatch, confidential }`, with `headers` an ordered `[{ name, value }]` array (retaining repeated fields) and the `confidential` set derived from the actual visible outer Header Section. They are not placed under `protectedHeaders`, and `present` stays `false`, since a legacy message is structurally indistinguishable from a forwarded `message/rfc822` (a heuristic; cross-check `legacy.fromMismatch`). Anything not precisely identified reports `legacy: null`. Off by default.

Example

async function example() {
  var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
  var pair = await pki.key.generate(rsa);
  var recipientKeyPkcs8 = await pki.key.export(pair.privateKey);
  var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: recipientKeyPkcs8 });
  var smimeMessageBytes = await pki.smime.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
  var res = await pki.smime.decrypt(smimeMessageBytes, { key: recipientKeyPkcs8, cert: recipientCertDer });
  res.content;        // the recovered inner MIME entity
  res.authenticated;  // true here (an AEAD content cipher); false for an enveloped-only CBC message, which carries no integrity
}
example();

References

pki.smime.compress

since 0.2.27 stable
pki.smime.compress(content, opts?) -> Promise<Buffer>

Compress a MIME entity as an opaque compressed S/MIME message (RFC 8551 sec. 3.6). content is the payload: a raw body wrapped as a text/plain entity by default, or the caller's own complete MIME entity when opts.entity is set. The entity is canonicalized (RFC 8551 sec. 3.1) and ZLIB-compressed into a CMS CompressedData (pki.cms.compress), carried opaque in one application/pkcs7-mime; smime-type=compressed-data; name=smime.p7z entity (base64). Compression is a size transform with NO integrity, confidentiality, or authentication (RFC 8551 sec. 2.4.5). Sign or encrypt the result if you need protection. Returns the assembled message bytes; fail-closed with SmimeError.

Options

level       forwarded to cms.compress: the DEFLATE compression level (an integer).

Example

async function example() {
  var z = await pki.smime.compress(Buffer.from("compress this message"));
}
example();

References

pki.smime.decompress

since 0.2.27 stable
pki.smime.decompress(message, opts?) -> Promise<{ content, contentType, contentTypeName, compressionAlgorithm }>

Decompress a compressed S/MIME message (RFC 8551 sec. 3.6), an application/pkcs7-mime entity whose base64 body is a CMS CompressedData. Returns the recovered inner MIME entity as content plus the inner contentType / contentTypeName and the compressionAlgorithm. The inflate is BOUNDED (a decompression-bomb defense, cms/decompress-too-large; opts.maxOutputBytes tightens it downward). The verdict carries NO authenticated / valid field, because CompressedData is not a security assertion (RFC 8551 sec. 2.4.5). A recovered content that is itself a signed or enveloped S/MIME message is returned as-is for the caller to feed back to pki.smime.verify / pki.smime.decrypt (no auto-recursion). Accepts OpenSSL's legacy application/x-pkcs7-mime and a missing smime-type.

Options

maxOutputBytes forwarded to cms.decompress: lower the decompressed-output cap (a DoS bound; downward only).

Example

async function example() {
  var compressedSmimeBytes = await pki.smime.compress(Buffer.from("compress me"));
  var res = await pki.smime.decompress(compressedSmimeBytes);
  res.content;   // the recovered inner MIME entity
}
example();

References

pki.smime.buildCertsOnly

since 0.6.3 stable
pki.smime.buildCertsOnly(certs, opts?) -> Buffer

Wrap a certs-only certificate-management message (RFC 8551 sec. 3.8) in one S/MIME application/pkcs7-mime; smime-type=certs-only; name=smime.p7c entity: the base64 body is the pki.cms.certsOnly DER, with Content-Disposition: attachment; filename=smime.p7c (RFC 8551 sec. 3.2.1). certs and opts.crls are the certificate/CRL inputs pki.cms.certsOnly accepts, and at least one certificate or CRL is required. A certs-only message carries no user content, so the header-protection options of pki.smime.encrypt do not apply here.

Options

crls A CRL or array of CRLs (DER Buffer or PEM string) to convey alongside the certificates.

Example

async function example() {
  var pair = await pki.key.generate("Ed25519");
  var leafDer = await pki.x509.sign({ subject: "Leaf", subjectPublicKey: await pki.key.export(pair.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: await pki.key.export(pair.privateKey) });
  var p7c = pki.smime.buildCertsOnly([leafDer]);
}
example();

References