@blamejs/pki 0.4.14 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/MIGRATING.md +2 -2
  3. package/README.md +142 -137
  4. package/index.js +4 -0
  5. package/lib/acme.js +73 -1
  6. package/lib/asn1-der.js +2 -0
  7. package/lib/attrcert-sign.js +4 -0
  8. package/lib/cbor-det.js +32 -16
  9. package/lib/cmc-build.js +880 -0
  10. package/lib/cmc-verify.js +657 -0
  11. package/lib/cmp-build.js +8 -7
  12. package/lib/cmp-verify.js +11 -1
  13. package/lib/cms-sign.js +170 -8
  14. package/lib/cms-verify.js +80 -14
  15. package/lib/crl-sign.js +22 -0
  16. package/lib/crmf-sign.js +5 -2
  17. package/lib/csr-sign.js +3 -0
  18. package/lib/ct.js +72 -0
  19. package/lib/est.js +828 -32
  20. package/lib/framework-error.js +13 -0
  21. package/lib/guard-bytes.js +37 -1
  22. package/lib/guard-range.js +23 -1
  23. package/lib/http-transport.js +9 -3
  24. package/lib/inspect.js +28 -5
  25. package/lib/jose.js +15 -0
  26. package/lib/lint.js +4 -0
  27. package/lib/merkle.js +5 -5
  28. package/lib/ocsp.js +139 -11
  29. package/lib/oid.js +69 -1
  30. package/lib/path-validate.js +438 -100
  31. package/lib/pkcs12-build.js +12 -0
  32. package/lib/schema-all.js +19 -1
  33. package/lib/schema-attrcert.js +27 -0
  34. package/lib/schema-c509.js +6 -0
  35. package/lib/schema-cmc.js +791 -0
  36. package/lib/schema-cmp.js +25 -0
  37. package/lib/schema-cms.js +17 -1
  38. package/lib/schema-crl.js +23 -1
  39. package/lib/schema-crmf.js +13 -0
  40. package/lib/schema-csr.js +11 -0
  41. package/lib/schema-csrattrs.js +6 -0
  42. package/lib/schema-engine.js +6 -2
  43. package/lib/schema-ocsp.js +41 -0
  44. package/lib/schema-pkcs12.js +16 -0
  45. package/lib/schema-pkcs8.js +8 -0
  46. package/lib/schema-pkix.js +6 -0
  47. package/lib/schema-smime.js +4 -4
  48. package/lib/schema-tsp.js +32 -1
  49. package/lib/schema-x509.js +14 -1
  50. package/lib/shbs.js +12 -4
  51. package/lib/sigstore.js +4 -0
  52. package/lib/smime.js +28 -7
  53. package/lib/tls-cert-compress.js +15 -3
  54. package/lib/trust.js +27 -4
  55. package/lib/tsp-sign.js +41 -6
  56. package/lib/vendor/README.md +19 -19
  57. package/lib/webauthn.js +895 -26
  58. package/lib/x509-sign.js +3 -0
  59. package/package.json +1 -1
  60. package/sbom.cdx.json +6 -6
package/lib/cmp-build.js CHANGED
@@ -191,12 +191,7 @@ function _encodeHeader(headerSpec, protectionAlgDer, pvno) {
191
191
 
192
192
  // A certReqId is an UNBOUNDED INTEGER (RFC 9483 -1 sentinel, no upper bound): accept a safe-integer number
193
193
  // or a bigint (a large value beyond 2^53), reject a non-integer / other type. Returns a BigInt for b.integer.
194
- function _reqIdInt(v, code, what) {
195
- if (typeof v === "bigint") return v;
196
- // Number.isSafeInteger (not isInteger): a value above 2^53 is imprecise as a Number, so it MUST be a bigint.
197
- if (typeof v === "number" && Number.isSafeInteger(v)) return BigInt(v);
198
- throw _err(code, what + " must be an integer (a safe-integer number, or a bigint for a large value)");
199
- }
194
+ function _reqIdInt(v, code, what) { return guard.range.authoredInteger(v, _err, code, what); }
200
195
 
201
196
  // PKIFailureInfo ::= BIT STRING (named bits, RFC 9810 sec. 5.2.3) -- a minimal NamedBitList from bit names.
202
197
  function _encodeFailInfo(names, code) {
@@ -616,7 +611,7 @@ function _collectExtraCerts(opts, protCertDer) {
616
611
  * @primitive pki.cmp.build
617
612
  * @signature pki.cmp.build(message, opts?) -> Promise<Buffer|string>
618
613
  * @since 0.3.5
619
- * @status experimental
614
+ * @status stable
620
615
  * @spec RFC 9810, RFC 9481, RFC 9579
621
616
  * @related pki.schema.cmp.parse
622
617
  *
@@ -645,6 +640,12 @@ function _collectExtraCerts(opts, protCertDer) {
645
640
  * - `pss` (boolean) / `digestAlgorithm` (string) -- signature-protection algorithm options.
646
641
  *
647
642
  * @example
643
+ * var pair = await pki.key.generate("Ed25519");
644
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
645
+ * var signerCertDer = await pki.x509.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey),
646
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
647
+ * var csrDer = await pki.csr.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey) },
648
+ * { key: signerKeyPkcs8 });
648
649
  * var der = await pki.cmp.build(
649
650
  * { header: { sender: { directoryName: "CN=client" }, recipient: { directoryName: "CN=CA" } },
650
651
  * body: { p10cr: csrDer } },
package/lib/cmp-verify.js CHANGED
@@ -665,7 +665,7 @@ async function _verify(message, opts) {
665
665
  * @primitive pki.cmp.verify
666
666
  * @signature pki.cmp.verify(message, opts?) -> Promise<verdict>
667
667
  * @since 0.3.26
668
- * @status experimental
668
+ * @status stable
669
669
  * @spec RFC 9810, RFC 9481, RFC 9579, RFC 9483
670
670
  * @related pki.cmp.build, pki.schema.cmp.parse
671
671
  * @defends cmp-unverified-protection (CWE-347), cmp-mac-timing (CWE-208)
@@ -709,6 +709,16 @@ async function _verify(message, opts) {
709
709
  * - `maxIterations` (number) -- downward-only override of the PBKDF2 iteration cap.
710
710
  *
711
711
  * @example
712
+ * var pair = await pki.key.generate("Ed25519");
713
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
714
+ * var signerCertDer = await pki.x509.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey),
715
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
716
+ * var certDer = signerCertDer; // self-signed here, so it is also its own anchor
717
+ * var csrDer = await pki.csr.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey) },
718
+ * { key: signerKeyPkcs8 });
719
+ * var cmpDer = await pki.cmp.build(
720
+ * { header: { sender: { directoryName: "CN=client" }, recipient: { directoryName: "CN=CA" } },
721
+ * body: { p10cr: csrDer } }, { key: signerKeyPkcs8, cert: signerCertDer });
712
722
  * var v = await pki.cmp.verify(cmpDer, { signerCert: signerCertDer, trustAnchors: [certDer] });
713
723
  * if (v.valid && v.trusted) console.log("the response protection is authentic and the signer is trusted");
714
724
  */
package/lib/cms-sign.js CHANGED
@@ -22,7 +22,7 @@ var frameworkError = require("./framework-error");
22
22
  var webcrypto = require("./webcrypto");
23
23
  var signScheme = require("./sign-scheme");
24
24
  var guard = require("./guard-all");
25
- var pkiBuild = require("./pki-build");
25
+ var pkiBuild = require("./pki-build"); // the shared post-sign self-check, among other producing helpers
26
26
  var cms = require("./schema-cms");
27
27
 
28
28
  var subtle = webcrypto.webcrypto.subtle;
@@ -33,6 +33,15 @@ function _err(code, message, cause) { return new CmsError(code, message, cause);
33
33
  // cms/<kind>), so its faults keep the cms/* codes.
34
34
  function _signE(kind, message, cause) { return new CmsError("cms/" + kind, message, cause); }
35
35
  function O(name) { return oid.byName(name); }
36
+ // The shared producing-side helpers, bound to this domain's error class. Only the
37
+ // post-sign self-check is used here -- the same one every other signer in the
38
+ // toolkit runs -- so a key-only signer's declared public key is bound by evidence
39
+ // rather than by the caller's word.
40
+ var NS = pkix.makeNS("cms", CmsError, oid);
41
+ var _b = pkiBuild.makeBuilder({
42
+ ErrorClass: CmsError, prefix: "cms", O: O, NS: NS,
43
+ NAME_SCHEMA: pkix.name(NS), SPKI_SCHEMA: pkix.spki(NS), EXT_DECODERS: {},
44
+ });
36
45
 
37
46
  // A digest-algorithm name -> the engine's hash name. The FIPS 202 extendable-output
38
47
  // functions are message digests here at the lengths RFC 8702 sec. 4 fixes for that use,
@@ -43,6 +52,7 @@ var DIGEST_HASH = {
43
52
  };
44
53
 
45
54
  var OID_DATA = O("data");
55
+ var OID_PKI_DATA = O("id-cct-PKIData"); // the content type RFC 5272 sec. 3.2's signer rules govern
46
56
  var OID_SIGNED_DATA = O("signedData");
47
57
  var OID_SKI = O("subjectKeyIdentifier");
48
58
 
@@ -134,10 +144,35 @@ function _buildUnsignedAttrs(list) {
134
144
  // SignedData digestAlgorithms + certificates sets).
135
145
  function _buildSignerInfo(signer, content, eContentType, opts) {
136
146
  var so = signer || {};
137
- var certDer = _normCertDer(so.cert);
138
- var cert = x509.parse(certDer);
147
+ // A KEY-ONLY signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
148
+ // 5272 sec. 3.2 requires exactly this when a Full PKI Request is signed with the
149
+ // key of a certification request it carries -- there is no certificate yet, the
150
+ // sid MUST be the subjectKeyIdentifier form, and its value MUST be the SKI the
151
+ // request itself declares. The scheme resolver only ever reads
152
+ // `cert.subjectPublicKeyInfo.algorithm`, so the request's own parsed SPKI stands
153
+ // in for the certificate that does not exist.
154
+ var keyOnly = so.cert == null && so.spki != null;
155
+ // Every field the emitted SignerInfo depends on is read ONCE, HERE, while
156
+ // nothing has deferred yet. The SignerIdentifier is built from the certificate
157
+ // or the key identifier on the lines below; the key signs, and is matched
158
+ // against `spki`, a promise turn later. A descriptor whose `key` and `spki`
159
+ // were swapped in that gap would emit a SignerInfo that names one public key
160
+ // and carries a signature by another -- coherent to the match check, since
161
+ // both halves moved together, and unverifiable to everyone else.
162
+ //
163
+ // The public bytes are COPIED, so an in-place rewrite cannot reach them
164
+ // either. The private key is captured by reference: copying it would put a
165
+ // second copy of key material in this function's hands, a secret it would then
166
+ // own for the rest of the call, and the reference already pins which key
167
+ // signs against the identifier that names it.
168
+ var soKey = so.key;
169
+ var soSpki = keyOnly ? guard.bytes.snapshotSource(so.spki, CmsError, "cms/bad-input", "a key-only signer's spki") : null;
170
+ var certDer = keyOnly ? null : _normCertDer(so.cert);
171
+ var cert = keyOnly ? _keyOnlyCertStandIn(soSpki) : x509.parse(certDer);
139
172
  var scheme = signScheme.resolveSignScheme(cert, so, opts.signedAttributes === false, _signE);
140
- var sidv = _buildSid(cert, opts.sid === "ski");
173
+ var sidv = keyOnly
174
+ ? { sid: b.contextPrimitive(0, _keyOnlyKeyId(so)), version: 3 } // [0] IMPLICIT SubjectKeyIdentifier
175
+ : _buildSid(cert, opts.sid === "ski");
141
176
  var sid = sidv.sid, version = sidv.version;
142
177
 
143
178
  return Promise.resolve().then(function () {
@@ -155,19 +190,60 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
155
190
  });
156
191
  }).then(function (toSign) {
157
192
  var signedBytes = toSign.setOf ? toSign.setOf : toSign; // SET-OF form for signing (sec. 5.4)
158
- return signScheme.signOverTbs(scheme, so.key, signedBytes, _signE).then(function (sig) {
193
+ return signScheme.signOverTbs(scheme, soKey, signedBytes, _signE).then(function (sig) {
194
+ return _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert).then(function () { return sig; });
195
+ }).then(function (sig) {
159
196
  var fields = [b.integer(BigInt(version)), sid, scheme.digestAlgId];
160
197
  if (toSign.wire) fields.push(toSign.wire); // [0] IMPLICIT signedAttrs
161
198
  fields.push(scheme.sigAlgId, b.octetString(sig));
162
199
  var ua = _buildUnsignedAttrs(opts.unsignedAttributes);
163
200
  if (ua) fields.push(ua); // [1] IMPLICIT unsignedAttrs
164
201
  return { si: b.sequence(fields), digestAlgId: scheme.digestAlgId, version: version, certDer: certDer };
202
+ // certDer is null for a key-only signer, and the certificates [0] embedding
203
+ // below filters it out -- there is nothing to embed, which is the point.
165
204
  });
166
205
  });
167
206
  }
168
207
 
169
208
 
170
209
 
210
+ // A SignerInfo names a public key -- as `spki` when the signer is key-only, and as
211
+ // the certificate it embeds otherwise -- while the signature comes from `key`. Two
212
+ // different keys make a well-formed SignerInfo nobody can verify: the recipient
213
+ // resolves the identifier to the declared key and checks a signature made by
214
+ // another. Neither form catches that by itself, so both are checked here.
215
+ //
216
+ // Proven from the SIGNATURE, not from the key. Deriving the public half and
217
+ // comparing it works only for key material this process can export, and the keys
218
+ // this signer serves are often exactly the ones that cannot be: a non-extractable
219
+ // CryptoKey, an HSM handle, or a composite `{ mldsa, trad }` pair that is two keys
220
+ // rather than one. Every one of those can still be checked the direct way -- the
221
+ // signature it just produced either verifies under the declared public key or it
222
+ // does not -- so there is no key kind this has to take on trust.
223
+ //
224
+ // This is the same post-sign self-check every other signer in the toolkit runs,
225
+ // through the same shared helper. It costs one verification per SignerInfo, which
226
+ // is what a certificate, a CRL, a CSR and a CMP message have always paid here.
227
+ function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
228
+ var declared = keyOnly ? soSpki : (cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.bytes);
229
+ // A parsed certificate that does not retain its SPKI bytes cannot be checked this
230
+ // way. That is not a shape x509.parse produces -- and if it ever were, refusing is
231
+ // the answer rather than skipping, since a skip is the fail-open this prevents.
232
+ if (!declared) {
233
+ throw _signE("bad-input",
234
+ "a signer certificate did not surface its subjectPublicKeyInfo, so the signature it produced could " +
235
+ "not be checked against the key the SignerInfo declares");
236
+ }
237
+ return Promise.resolve()
238
+ .then(function () { return _b.assertSignatureVerifies(signedBytes, sig, declared, scheme); })
239
+ .then(null, function (e) {
240
+ if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
241
+ throw _signE("bad-input",
242
+ "a signer's `key` does not match the public key its SignerInfo declares (" +
243
+ (keyOnly ? "`spki`" : "its certificate") + "): the signature it produced does not verify under that key", e);
244
+ });
245
+ }
246
+
171
247
  // A signing-time Time value: UTCTime before 2050, GeneralizedTime from 2050 (RFC 5652 sec. 11.3 /
172
248
  // RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above).
173
249
  function _timeValue(when) {
@@ -178,6 +254,75 @@ function _timeValue(when) {
178
254
  // Normalize a signer certificate input to its raw DER (DER Buffer / PEM string / Uint8Array).
179
255
  // The same bytes drive scheme resolution and the certificates [0] embedding, so a parsed
180
256
  // certificate (which does not retain its full DER) is rejected -- pass DER or PEM.
257
+ // The stand-in a key-only signer resolves its signature scheme from: the parsed
258
+ // SubjectPublicKeyInfo of the key that will sign. Only `.subjectPublicKeyInfo` is
259
+ // read downstream, so this deliberately carries nothing else -- a fuller fake
260
+ // would invite code to start trusting fields no certificate actually backs.
261
+ function _keyOnlyCertStandIn(spkiDer) {
262
+ var alg;
263
+ try {
264
+ // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
265
+ // The WHOLE structure, not just the field this function goes on to read. For an
266
+ // OPAQUE key handle the derivation check downstream is deliberately skipped, so
267
+ // this is the ONLY thing standing between a caller's bytes and a SignerInfo that
268
+ // declares them: a SEQUENCE carrying an algorithm and nothing else, or a third
269
+ // field, would be emitted as the signer's public key and resolve to nothing for
270
+ // anyone trying to verify. The key VALUE is not interpreted -- only that there
271
+ // is one, in the field the structure reserves for it.
272
+ var node = asn1.decode(spkiDer);
273
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children ||
274
+ node.children.length !== 2) {
275
+ throw _err("cms/bad-input",
276
+ "a key-only signer's spki is SEQUENCE { algorithm, subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7)");
277
+ }
278
+ var keyNode = node.children[1];
279
+ if (keyNode.tagClass !== "universal" || keyNode.tagNumber !== asn1.TAGS.BIT_STRING) {
280
+ throw _err("cms/bad-input",
281
+ "a key-only signer's spki subjectPublicKey must be a BIT STRING (RFC 5280 sec. 4.1.2.7)");
282
+ }
283
+ asn1.read.bitString(keyNode); // read, not tag-inspected: a malformed unused-bit count is refused here
284
+ var algNode = node.children[0];
285
+ if (algNode.tagClass !== "universal" || algNode.tagNumber !== asn1.TAGS.SEQUENCE ||
286
+ !algNode.children || !algNode.children.length || algNode.children.length > 2) {
287
+ throw _err("cms/bad-input",
288
+ "a key-only signer's spki algorithm is SEQUENCE { algorithm OID, parameters OPTIONAL } (RFC 5280 sec. 4.1.1.2)");
289
+ }
290
+ // The same shape schema-x509 surfaces: the algorithm OID plus its parameters
291
+ // as the RAW TLV (or null when absent) -- the resolver re-decodes those bytes
292
+ // for an EC named curve and for the RSASSA-PSS hash pinning, so handing it a
293
+ // different representation here would break exactly those two algorithms.
294
+ alg = {
295
+ oid: asn1.read.oid(algNode.children[0]),
296
+ parameters: algNode.children[1] ? algNode.children[1].bytes : null,
297
+ };
298
+ } catch (e) {
299
+ // The shape refusals above already name what is wrong; only a decode failure
300
+ // needs the generic message, so a precise verdict is not overwritten by it.
301
+ if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
302
+ throw _err("cms/bad-input", "a key-only signer's spki is not a SubjectPublicKeyInfo", e);
303
+ }
304
+ return { subjectPublicKeyInfo: { algorithm: alg } };
305
+ }
306
+
307
+ // The key identifier a key-only signer's sid carries. Required: without it there
308
+ // is no way for a verifier to find the key, and RFC 5272 sec. 3.2 makes the value
309
+ // the one the certification request declares.
310
+ function _keyOnlyKeyId(so) {
311
+ if (so.keyIdentifier == null) {
312
+ throw _err("cms/bad-input",
313
+ "a key-only signer requires keyIdentifier -- the subjectKeyIdentifier the certification request declares (RFC 5272 sec. 3.2)");
314
+ }
315
+ // A key identifier is BYTES, so the type is checked rather than coerced. Buffer.from
316
+ // ACCEPTS far more than it should mean here: Buffer.from(20) allocates twenty zero
317
+ // octets and Buffer.from("a1b2") takes the ASCII of the text rather than the two
318
+ // octets a reader means by it. Either would emit a structurally valid
319
+ // SignerIdentifier carrying an identifier the caller never asked for, which no
320
+ // verifier can match back to the certification request (RFC 5272 sec. 3.2).
321
+ var id = guard.bytes.view(so.keyIdentifier, CmsError, "cms/bad-input", "a key-only signer's keyIdentifier");
322
+ if (!id.length) throw _err("cms/bad-input", "a key-only signer's keyIdentifier must not be empty");
323
+ return id;
324
+ }
325
+
181
326
  function _normCertDer(c) {
182
327
  if (c == null) throw _err("cms/bad-input", "each signer requires a certificate (cert)");
183
328
  if (c instanceof Uint8Array && !Buffer.isBuffer(c)) c = Buffer.from(c); // a Uint8Array -> Buffer (below)
@@ -199,6 +344,18 @@ function sign(content, signers, opts) {
199
344
  var list = Array.isArray(signers) ? signers : [signers];
200
345
  if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
201
346
  var eContentType = opts.eContentType ? O(opts.eContentType) : OID_DATA;
347
+ // RFC 5272 sec. 3.2: "If the request key is used for signing, there MUST be only
348
+ // one SignerInfo in the SignedData." That clause governs a Full PKI Request, so
349
+ // it binds HERE only for that content type. CMS itself is happy with several
350
+ // SignerInfos, and a key-only signer's certificate can reach a verifier by other
351
+ // means -- applying the CMC rule to every content type would refuse ordinary
352
+ // SignedData that nothing objects to. pki.cmc.build enforces the same rule on
353
+ // the message it assembles, which is where the clause is really about.
354
+ if (eContentType === OID_PKI_DATA && list.length > 1 &&
355
+ list.some(function (s) { return s && s.cert == null && s.spki != null; })) {
356
+ throw _err("cms/bad-input",
357
+ "a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
358
+ }
202
359
  // RFC 5652 sec. 5.3: signed attributes MUST be present (carrying a content-type attribute)
203
360
  // whenever the encapsulated content type is not id-data -- so signedAttributes:false is only
204
361
  // valid for id-data content. Refusing it here keeps cms.sign from emitting a non-conformant
@@ -226,8 +383,12 @@ function sign(content, signers, opts) {
226
383
  // certificates [0] IMPLICIT SET OF (the signer certs), deduped + SET-OF-ordered, when embedded.
227
384
  var sdFields = [b.integer(BigInt(version)), b.set(digestAlgs), encap];
228
385
  if (opts.certificates !== false) {
229
- var certDers = _dedupe(built.map(function (x) { return x.certDer; })).sort(Buffer.compare); // X.690 sec. 11.6
230
- sdFields.push(b.contextConstructed(0, Buffer.concat(certDers))); // [0] IMPLICIT SET OF
386
+ // A key-only signer contributes no certificate (certDer is null), so the
387
+ // set can end up EMPTY -- and an empty certificates [0] is not the same as
388
+ // an absent one, so the field is omitted entirely in that case.
389
+ var certDers = _dedupe(built.map(function (x) { return x.certDer; })
390
+ .filter(function (d) { return d != null; })).sort(Buffer.compare); // X.690 sec. 11.6
391
+ if (certDers.length) sdFields.push(b.contextConstructed(0, Buffer.concat(certDers))); // [0] IMPLICIT SET OF
231
392
  }
232
393
  sdFields.push(b.set(built.map(function (x) { return x.si; }))); // signerInfos SET OF
233
394
  var signedData = b.sequence(sdFields);
@@ -276,6 +437,7 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
276
437
  var cert = x509.parse(certDer);
277
438
  var scheme = signScheme.resolveSignScheme(cert, so, opts.signedAttributes === false, _signE);
278
439
  var sidv = _buildSid(cert, opts.sid === "ski");
440
+ var soKey = so.key; // read once, here -- the SignerIdentifier above is already fixed to `cert`
279
441
  return Promise.resolve().then(function () {
280
442
  if (opts.signedAttributes === false) return null; // sign the target signature octets directly
281
443
  return _digest(scheme.digest, targetSigOctets).then(function (md) {
@@ -286,7 +448,7 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
286
448
  return _buildSignedAttrs(pairs.concat(extra));
287
449
  });
288
450
  }).then(function (attrs) {
289
- return signScheme.signOverTbs(scheme, so.key, attrs ? attrs.setOf : targetSigOctets, _signE).then(function (sig) {
451
+ return signScheme.signOverTbs(scheme, soKey, attrs ? attrs.setOf : targetSigOctets, _signE).then(function (sig) {
290
452
  var fields = [b.integer(BigInt(sidv.version)), sidv.sid, scheme.digestAlgId];
291
453
  if (attrs) fields.push(attrs.wire); // [0] IMPLICIT signedAttrs
292
454
  fields.push(scheme.sigAlgId, b.octetString(sig));
package/lib/cms-verify.js CHANGED
@@ -572,7 +572,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
572
572
  * @primitive pki.cms.verify
573
573
  * @signature pki.cms.verify(input, opts?) -> Promise<{ valid, signers }>
574
574
  * @since 0.2.14
575
- * @status experimental
575
+ * @status stable
576
576
  * @spec RFC 5652
577
577
  * @spec RFC 9882
578
578
  * @spec RFC 9814
@@ -594,19 +594,55 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
594
594
  * @opts certs Extra signer certificates (an array of DER `Buffer`s) to match against, in
595
595
  * addition to the certificates embedded in the SignedData.
596
596
  * @example
597
+ * var pair = await pki.key.generate("Ed25519");
598
+ * var key = await pki.key.export(pair.privateKey);
599
+ * var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
600
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
601
+ * var detachedBytes = Buffer.from("hello");
602
+ * var p7sDer = await pki.cms.sign(detachedBytes, { cert: cert, key: key, detached: true });
597
603
  * var res = await pki.cms.verify(p7sDer, { content: detachedBytes });
598
604
  * res.valid; // boolean
599
605
  * res.signers[0].ok; // per-signer verdict
600
606
  */
607
+ // A private copy of the input when -- and only when -- it is memory the caller can
608
+ // still write to. Anything else (a PEM string, and whatever else the parser accepts)
609
+ // is returned untouched so this cannot narrow the input contract of the parse it
610
+ // feeds; the parser stays the one place that decides what an acceptable input is.
611
+ function _snapshotIfBytes(input, label) {
612
+ // Every byte form the parser accepts -- an ArrayBuffer or a DataView reaches the
613
+ // decoder as readily as a Buffer, and leaving those aliased would reopen the
614
+ // window for exactly the inputs that came in by the wider door.
615
+ if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
616
+ return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", label);
617
+ }
618
+ return input;
619
+ }
620
+
601
621
  function verify(input, opts) {
602
622
  opts = opts || {};
603
623
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.verify options must be an object");
604
- var parsed = (input && typeof input === "object" && !Buffer.isBuffer(input) && Array.isArray(input.signerInfos)) ? input : cms.parse(input);
624
+ // Handed MUTABLE BYTES, parse a private copy. This function decodes synchronously
625
+ // and checks signatures in a later promise turn, so without the copy every range
626
+ // the parse surfaced -- the signed content above all -- stays a view into the
627
+ // caller's memory across that await, and a buffer rewritten in the gap would leave
628
+ // the result describing one message while the signature was checked over another.
629
+ // Only a Buffer / Uint8Array can change underneath us: a PEM string is immutable
630
+ // and passes through untouched, and an already-parsed object is a lifetime the
631
+ // caller chose. Narrow rather than blanket, so neither input form is refused here
632
+ // that pki.cms.parse itself accepts.
633
+ var parsed = (input && typeof input === "object" && !Buffer.isBuffer(input) && Array.isArray(input.signerInfos))
634
+ ? input
635
+ : cms.parse(_snapshotIfBytes(input, "pki.cms.verify"));
605
636
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
606
637
  var content = parsed.encapContentInfo.eContent;
607
638
  if (content == null) {
608
639
  if (opts.content == null) throw _err("cms/detached-content-required", "this SignedData is detached; opts.content (the external content) is required");
609
- content = _toBuf(opts.content, "opts.content");
640
+ // Copied, not aliased: the digest over this content is computed in a later promise turn,
641
+ // and a detached signature's content is the whole of what the signature speaks about. A
642
+ // caller who rewrote the buffer in that gap would have the signature checked against the
643
+ // original bytes while holding the replacement, and the verdict would report a valid
644
+ // signature over content that was never signed.
645
+ content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
610
646
  } else if (opts.content != null) {
611
647
  // A caller supplied external content AND the SignedData is attached. A redundant COPY that equals
612
648
  // the encapsulated content is fine; content that DIFFERS is a substitution trap -- silently
@@ -635,10 +671,15 @@ function verify(input, opts) {
635
671
  }
636
672
  // Parse a candidate cert DER and index its SKI; a cert that will not parse is skipped (it
637
673
  // simply cannot be a signer match, and a malformed embedded cert must not fail the verify).
674
+ // The certificate bytes are copied BEFORE they are parsed, not after: the parse surfaces the
675
+ // signed portion and the public key as VIEWS into this buffer, and both are read in a later
676
+ // promise turn to check the signer's signature and walk the chain. Copying afterwards would
677
+ // leave those views pointing at bytes the caller can still rewrite -- a signer certificate
678
+ // swapped for another in that gap, with the verdict still naming the first.
638
679
  function _addCert(out, der) {
639
680
  var buf;
640
681
  try {
641
- buf = _toBuf(der, "a certificate");
682
+ buf = _toBuf(_snapshotIfBytes(der, "a certificate"), "a certificate");
642
683
  } catch (_e) { return; }
643
684
  var cert;
644
685
  try {
@@ -663,7 +704,7 @@ function _addCert(out, der) {
663
704
  * @primitive pki.cms.sign
664
705
  * @signature pki.cms.sign(content, signers, opts?) -> Promise<Buffer|string>
665
706
  * @since 0.2.15
666
- * @status experimental
707
+ * @status stable
667
708
  * @spec RFC 5652
668
709
  * @spec RFC 9882
669
710
  * @spec RFC 9814
@@ -701,6 +742,10 @@ function _addCert(out, der) {
701
742
  * rejected here (RFC 5652 sec. 11). Use `pki.cms.countersign` for a
702
743
  * countersignature specifically.
703
744
  * @example
745
+ * var pair = await pki.key.generate("Ed25519");
746
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
747
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
748
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
704
749
  * var p7 = await pki.cms.sign(Buffer.from("hello"), { cert: signerCertDer, key: signerKeyPkcs8 });
705
750
  * var res = await pki.cms.verify(p7); // res.valid === true
706
751
  */
@@ -710,7 +755,7 @@ var sign = cmsSign.sign;
710
755
  * @primitive pki.cms.countersign
711
756
  * @signature pki.cms.countersign(cms, signers, opts?) -> Promise<Buffer|string>
712
757
  * @since 0.3.13
713
- * @status experimental
758
+ * @status stable
714
759
  * @spec RFC 5652
715
760
  * @related pki.cms.sign, pki.cms.verify, pki.schema.cms.parse
716
761
  *
@@ -740,6 +785,10 @@ var sign = cmsSign.sign;
740
785
  * @opts certificates Embed the countersigner certificate(s) in the SignedData. Default true.
741
786
  * @opts pem Return a PEM string instead of a DER Buffer.
742
787
  * @example
788
+ * var pair = await pki.key.generate("Ed25519");
789
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
790
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
791
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
743
792
  * var p7 = await pki.cms.sign(Buffer.from("hi"), { cert: signerCertDer, key: signerKeyPkcs8 });
744
793
  * var cs = await pki.cms.countersign(p7, { cert: signerCertDer, key: signerKeyPkcs8 });
745
794
  * var res = await pki.cms.verify(cs);
@@ -751,7 +800,7 @@ var countersign = cmsSign.countersign;
751
800
  * @primitive pki.cms.encrypt
752
801
  * @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
753
802
  * @since 0.2.23
754
- * @status experimental
803
+ * @status stable
755
804
  * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
756
805
  * @related pki.cms.decrypt, pki.schema.cms.parse
757
806
  *
@@ -775,6 +824,11 @@ var countersign = cmsSign.countersign;
775
824
  * @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData.
776
825
  * @opts pem Return a PEM string instead of a DER Buffer.
777
826
  * @example
827
+ * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
828
+ * var pair = await pki.key.generate(rsa);
829
+ * var recipientKeyPkcs8 = await pki.key.export(pair.privateKey);
830
+ * var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
831
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: recipientKeyPkcs8 });
778
832
  * var env = await pki.cms.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
779
833
  */
780
834
  var encrypt = cmsEncrypt.encrypt;
@@ -783,7 +837,7 @@ var encrypt = cmsEncrypt.encrypt;
783
837
  * @primitive pki.cms.authenticate
784
838
  * @signature pki.cms.authenticate(content, recipients, opts?) -> Promise<Buffer | string>
785
839
  * @since 0.3.14
786
- * @status experimental
840
+ * @status stable
787
841
  * @spec RFC 5652, RFC 2104, RFC 4231, RFC 3370
788
842
  * @related pki.cms.decrypt, pki.schema.cms.parse
789
843
  *
@@ -809,9 +863,14 @@ var encrypt = cmsEncrypt.encrypt;
809
863
  * @opts keyIdentifier `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
810
864
  * @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
811
865
  * @example
812
- * var r = pki.cms; // authenticate a message to an RSA recipient, then verify via decrypt
813
- * var auth = await r.authenticate(Buffer.from("hi"), [{ cert: recipientCertDer }]);
814
- * var out = await r.decrypt(auth, { key: recipientKeyPkcs8, cert: recipientCertDer });
866
+ * // authenticate a message to an RSA recipient, then verify the MAC via decrypt
867
+ * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
868
+ * var pair = await pki.key.generate(rsa);
869
+ * var recipientKeyPkcs8 = await pki.key.export(pair.privateKey);
870
+ * var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
871
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: recipientKeyPkcs8 });
872
+ * var auth = await pki.cms.authenticate(Buffer.from("hi"), [{ cert: recipientCertDer }]);
873
+ * var out = await pki.cms.decrypt(auth, { key: recipientKeyPkcs8, cert: recipientCertDer });
815
874
  * out.authenticated; // true
816
875
  */
817
876
  var authenticate = cmsEncrypt.authenticate;
@@ -820,7 +879,7 @@ var authenticate = cmsEncrypt.authenticate;
820
879
  * @primitive pki.cms.decrypt
821
880
  * @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated }>
822
881
  * @since 0.2.23
823
- * @status experimental
882
+ * @status stable
824
883
  * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018, RFC 3218, RFC 2104
825
884
  * @related pki.cms.encrypt, pki.cms.authenticate, pki.schema.cms.parse
826
885
  *
@@ -843,6 +902,12 @@ var authenticate = cmsEncrypt.authenticate;
843
902
  * @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
844
903
  * @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
845
904
  * @example
905
+ * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
906
+ * var pair = await pki.key.generate(rsa);
907
+ * var recipientKeyPkcs8 = await pki.key.export(pair.privateKey);
908
+ * var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
909
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: recipientKeyPkcs8 });
910
+ * var envDer = await pki.cms.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
846
911
  * var res = await pki.cms.decrypt(envDer, { key: recipientKeyPkcs8, cert: recipientCertDer });
847
912
  * res.content; // the recovered plaintext Buffer
848
913
  */
@@ -852,7 +917,7 @@ var decrypt = cmsDecrypt.decrypt;
852
917
  * @primitive pki.cms.compress
853
918
  * @signature pki.cms.compress(content, opts?) -> Promise<Buffer | string>
854
919
  * @since 0.2.27
855
- * @status experimental
920
+ * @status stable
856
921
  * @spec RFC 3274, RFC 1950, RFC 1951
857
922
  * @related pki.cms.decompress, pki.schema.cms.parse
858
923
  *
@@ -875,7 +940,7 @@ var compress = cmsCompress.compress;
875
940
  * @primitive pki.cms.decompress
876
941
  * @signature pki.cms.decompress(input, opts?) -> Promise<{ content, contentType, contentTypeName, compressionAlgorithm }>
877
942
  * @since 0.2.27
878
- * @status experimental
943
+ * @status stable
879
944
  * @spec RFC 3274, RFC 1950, RFC 1951
880
945
  * @related pki.cms.compress, pki.schema.cms.parse
881
946
  *
@@ -890,6 +955,7 @@ var compress = cmsCompress.compress;
890
955
  *
891
956
  * @opts maxOutputBytes Lower the decompressed-output cap (a DoS bound; downward only).
892
957
  * @example
958
+ * var compressedDer = await pki.cms.compress(Buffer.from("compress me"));
893
959
  * var res = await pki.cms.decompress(compressedDer);
894
960
  * res.content; // the recovered plaintext Buffer
895
961
  */
package/lib/crl-sign.js CHANGED
@@ -486,6 +486,12 @@ function _sign(spec, issuer, opts) {
486
486
  * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
487
487
  * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
488
488
  * @example
489
+ * var pair = await pki.key.generate("Ed25519");
490
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
491
+ * var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
492
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
493
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"], subjectKeyIdentifier: true } },
494
+ * { key: signerKeyPkcs8 });
489
495
  * var der = await pki.crl.sign({
490
496
  * thisUpdate: new Date("2026-01-01T00:00:00Z"), nextUpdate: new Date("2026-02-01T00:00:00Z"),
491
497
  * crlNumber: 7n,
@@ -529,6 +535,14 @@ function _resolveIssuerSpki(issuer) {
529
535
  * signature only -- issuer authorization, currency, and distribution-point scope are `pki.path.crlChecker`.
530
536
  *
531
537
  * @example
538
+ * var pair = await pki.key.generate("Ed25519");
539
+ * var signerSpki = await pki.key.export(pair.publicKey);
540
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
541
+ * var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: signerSpki,
542
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
543
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: signerKeyPkcs8 });
544
+ * var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
545
+ * { cert: signerCertDer, key: signerKeyPkcs8 });
532
546
  * var ok = await pki.crl.verify(crlDer, { publicKey: signerSpki }); // true / false
533
547
  */
534
548
  function verify(crl, issuer) { return Promise.resolve().then(function () { return _verify(crl, issuer); }); }
@@ -565,6 +579,14 @@ function _serialHexOf(serial) {
565
579
  * it does NOT verify the CRL signature or its currency; call `pki.crl.verify` / `pki.path.crlChecker` for that.
566
580
  *
567
581
  * @example
582
+ * var pair = await pki.key.generate("Ed25519");
583
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
584
+ * var signerCertDer = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
585
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
586
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: signerKeyPkcs8 });
587
+ * var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n,
588
+ * revoked: [{ serialNumber: 0x1234n, revocationDate: new Date("2026-01-15T00:00:00Z") }] },
589
+ * { cert: signerCertDer, key: signerKeyPkcs8 });
568
590
  * pki.crl.isRevoked(crlDer, 0x1234n) ? "revoked" : "not listed";
569
591
  */
570
592
  function isRevoked(crl, serialNumber) {
package/lib/crmf-sign.js CHANGED
@@ -203,7 +203,7 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
203
203
  * @primitive pki.crmf.build
204
204
  * @signature pki.crmf.build(spec, key?, opts?) -> Promise<Buffer|string>
205
205
  * @since 0.3.3
206
- * @status experimental
206
+ * @status stable
207
207
  * @spec RFC 4211
208
208
  * @defends forged-certificate-request (CWE-347)
209
209
  * @related pki.schema.crmf.parse, pki.csr.sign
@@ -229,6 +229,9 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
229
229
  * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
230
230
  * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
231
231
  * @example
232
+ * var pair = await pki.key.generate("Ed25519");
233
+ * var signerSpki = await pki.key.export(pair.publicKey);
234
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
232
235
  * var msg = await pki.crmf.build(
233
236
  * { certReqId: 0, certTemplate: { subject: "device-42", publicKey: signerSpki } },
234
237
  * { key: signerKeyPkcs8 });
@@ -287,7 +290,7 @@ function _build(spec, key, opts) {
287
290
  * @primitive pki.crmf.buildCertTemplate
288
291
  * @signature pki.crmf.buildCertTemplate(template) -> Buffer
289
292
  * @since 0.3.5
290
- * @status experimental
293
+ * @status stable
291
294
  * @spec RFC 4211
292
295
  * @related pki.crmf.build
293
296
  *
package/lib/csr-sign.js CHANGED
@@ -91,6 +91,9 @@ function _challengePassword(pw) {
91
91
  * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
92
92
  * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
93
93
  * @example
94
+ * var pair = await pki.key.generate("Ed25519");
95
+ * var signerSpki = await pki.key.export(pair.publicKey);
96
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
94
97
  * var req = await pki.csr.sign(
95
98
  * { subject: "req.example.com", subjectPublicKey: signerSpki,
96
99
  * extensionRequest: { subjectAltName: [{ dNSName: "req.example.com" }] } },