@blamejs/pki 0.4.15 → 0.5.1

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 +51 -1
  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 +64 -6
  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 +27 -4
  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-smime.js +4 -4
  47. package/lib/schema-tsp.js +32 -1
  48. package/lib/schema-x509.js +14 -1
  49. package/lib/shbs.js +12 -4
  50. package/lib/sigstore.js +62 -6
  51. package/lib/smime.js +28 -7
  52. package/lib/tls-cert-compress.js +15 -3
  53. package/lib/trust.js +27 -4
  54. package/lib/tsp-sign.js +41 -6
  55. package/lib/vendor/README.md +19 -19
  56. package/lib/webauthn.js +895 -26
  57. package/lib/webcrypto.js +35 -2
  58. package/lib/x509-sign.js +3 -0
  59. package/package.json +1 -1
  60. package/sbom.cdx.json +6 -6
package/lib/sigstore.js CHANGED
@@ -117,6 +117,8 @@ function _b64(s, label) {
117
117
  * bundle object (structure only -- no cryptographic verification).
118
118
  *
119
119
  * @example
120
+ * // requires: `bundle` -- a Sigstore bundle as cosign or npm provenance emits it
121
+ * // (the JSON object, a JSON string, or its raw bytes)
120
122
  * var b = pki.sigstore.parseBundle(bundle);
121
123
  * b.mediaType; // "application/vnd.dev.sigstore.bundle.v0.3+json"
122
124
  */
@@ -514,14 +516,54 @@ function _fulcioExtValue(ext, leafArc) {
514
516
  return asn1.read.string(asn1.decode(ext.value));
515
517
  }
516
518
 
519
+ // The fields an identity policy may constrain. Named once so the check, the refusal of an
520
+ // unknown key, and the report of what ran cannot drift apart.
521
+ var IDENTITY_FIELDS = ["san", "issuer", "sourceRepositoryURI"];
522
+ // The guard tests membership with hasOwnProperty, so the permitted set is a lookup object --
523
+ // an array would treat "0"/"1" as the known keys and reject every real field name.
524
+ var IDENTITY_KEYS = { san: 1, issuer: 1, sourceRepositoryURI: 1 };
525
+
526
+ // Returns WHICH fields were actually compared. A bundle verifies its own signature and log
527
+ // inclusion whoever signed it -- Fulcio issues to anyone who completes an OIDC flow -- so
528
+ // `verified: true` without an identity policy says the artifact was signed and logged, not that
529
+ // a trusted party signed it. The caller cannot tell those apart from a bare boolean.
517
530
  function _checkIdentity(id, policy) {
518
- if (!policy) return;
531
+ var ran = { san: false, issuer: false, sourceRepositoryURI: false };
532
+ if (policy === undefined || policy === null) return ran;
533
+ if (typeof policy !== "object" || Array.isArray(policy)) {
534
+ throw _err("sigstore/bad-input", "opts.identity must be an object naming the identity fields to pin (" + IDENTITY_FIELDS.join(", ") + ")");
535
+ }
536
+ // An unknown key is refused, not ignored: cosign spells this `certificateIdentity`, and a
537
+ // swallowed spelling checks nothing under a name the operator believes pins the signer.
538
+ // The guard rejects through a (code, message) FACTORY. Handing it the error CLASS raises
539
+ // "class constructor cannot be invoked without new" -- a raw, untyped throw escaping a public
540
+ // verb, on the branch a valid-input test never takes.
541
+ guard.identifier.assertKnownKeys(policy, IDENTITY_KEYS, _err, "sigstore/bad-input", "opts.identity has an unknown key ");
542
+ // A policy that constrains NOTHING is a configuration mistake, and the most dangerous input on
543
+ // this surface: every guard below is falsy, so it accepts every signer while reading as though
544
+ // an identity policy is in force. Refused at the boundary rather than answered.
545
+ var asked = IDENTITY_FIELDS.filter(function (f) { return policy[f] !== undefined; });
546
+ if (!asked.length) {
547
+ throw _err("sigstore/bad-input", "opts.identity constrains nothing -- name at least one of " + IDENTITY_FIELDS.join(", ") + ", or omit it to state that the signer is not being checked");
548
+ }
549
+ // A named field must carry a value that can actually be compared. The comparisons below are
550
+ // truthiness-guarded, so an empty string or a null would be skipped while the field had been
551
+ // named -- reporting a signer check that never ran, which is the exact confusion this report
552
+ // exists to remove. Deciding "asked" and deciding "compared" must be the SAME test, so a value
553
+ // that cannot be compared is refused here rather than quietly becoming "not asked".
554
+ asked.forEach(function (f) {
555
+ if (typeof policy[f] !== "string" || policy[f] === "") {
556
+ throw _err("sigstore/bad-input", "opts.identity." + f + " must be a non-empty string -- a value that cannot be compared would leave the signer unchecked under a policy that names it");
557
+ }
558
+ ran[f] = true;
559
+ });
519
560
  var sanValue = id.san && id.san.value;
520
561
  if (policy.san && sanValue !== policy.san) throw _err("sigstore/identity-mismatch", "the certificate SAN " + JSON.stringify(sanValue) + " does not match the expected identity");
521
562
  // The OIDC issuer is carried by the current Issuer V2 (.1.8) or, on older certs,
522
563
  // only by the deprecated raw-string issuer (.1.1); match against either.
523
564
  if (policy.issuer && policy.issuer !== id.extensions.issuer && policy.issuer !== id.extensions.issuerLegacy) throw _err("sigstore/identity-mismatch", "the certificate OIDC issuer does not match the expected issuer");
524
565
  if (policy.sourceRepositoryURI && id.extensions.sourceRepositoryURI !== policy.sourceRepositoryURI) throw _err("sigstore/identity-mismatch", "the certificate source-repository URI does not match");
566
+ return ran;
525
567
  }
526
568
 
527
569
  // ---- in-toto Statement leg ---------------------------------------------------
@@ -559,18 +601,29 @@ function _statement(payload, payloadType, expectedPredicate) {
559
601
  * the log entry bound to this exact signature; and the in-toto SLSA statement.
560
602
  * Any leg failing throws a typed `sigstore/*` error. On success returns
561
603
  * `{ verified: true, payload, statement, subjects, predicateType, predicate,
562
- * identity, integratedTime }` -- `payload` is the RAW verified envelope bytes
563
- * (never a re-serialization), and the caller confirms a `subjects[].digest`
564
- * matches the published artifact.
604
+ * identity, identityChecked, integratedTime }` -- `payload` is the RAW verified
605
+ * envelope bytes (never a re-serialization), and the caller confirms a
606
+ * `subjects[].digest` matches the published artifact.
607
+ *
608
+ * `verified: true` says the artifact was signed and logged -- not that a party you
609
+ * trust signed it. Fulcio issues a certificate to anyone who completes an OIDC
610
+ * flow, so WHO signed is decided only by `opts.identity`, and `identityChecked`
611
+ * reports which of its fields were compared (`{ san, issuer, sourceRepositoryURI }`,
612
+ * each a boolean). An `identity` naming none of them is refused rather than
613
+ * satisfied, since it would accept every signer while reading as a policy; so is an
614
+ * unrecognized field name, which would otherwise pin nothing under a spelling the
615
+ * operator believes constrains the signer.
565
616
  *
566
617
  * @opts
567
618
  * fulcioRoots: Array, // the Fulcio CA anchors: a DER Buffer or { der, validFor } each
568
619
  * rekorKeys: Array, // [{ keyId, spki, validFor? }] the Rekor log public keys
569
- * identity: object, // optional policy: { san, issuer, sourceRepositoryURI }
620
+ * identity: object, // optional policy: { san, issuer, sourceRepositoryURI }; at least one required when present
570
621
  * predicateType: string, // optional: require this in-toto predicateType (e.g. the SLSA URI)
571
622
  * time: Date, // optional check-date override (default: the Rekor integratedTime)
572
623
  *
573
624
  * @example
625
+ * // requires: `bundle` from cosign / npm provenance, and `sigstoreTrust` built from
626
+ * // the public-good trusted_root.json (the Fulcio + Rekor material it pins)
574
627
  * var out = await pki.sigstore.verifyBundle(bundle, sigstoreTrust);
575
628
  * out.verified; // true
576
629
  * out.subjects[0].digest; // { sha512: "..." } -- confirm against your tarball
@@ -612,7 +665,7 @@ async function verifyBundle(bundle, opts) {
612
665
  var checkTime = (opts.time instanceof Date) ? opts.time.getTime() : C.TIME.seconds(integratedTime);
613
666
  await _verifyChain(leaf, _chainDers(vm), fulcioRoots, checkTime);
614
667
  var identity = _identity(leaf);
615
- _checkIdentity(identity, opts.identity);
668
+ var identityChecked = _checkIdentity(identity, opts.identity);
616
669
 
617
670
  // Leg 4 -- the in-toto SLSA statement + subject binding (with an optional
618
671
  // caller-pinned predicateType).
@@ -626,6 +679,9 @@ async function verifyBundle(bundle, opts) {
626
679
  predicateType: st.predicateType,
627
680
  predicate: st.predicate,
628
681
  identity: identity,
682
+ // Which identity fields were actually compared. `verified` says the artifact was signed and
683
+ // logged; only these say a signer the caller named was the one who signed it.
684
+ identityChecked: identityChecked,
629
685
  integratedTime: integratedTime,
630
686
  };
631
687
  }
package/lib/smime.js CHANGED
@@ -495,7 +495,7 @@ function _base64Body(der) {
495
495
  * @primitive pki.smime.sign
496
496
  * @signature pki.smime.sign(content, signers, opts?) -> Promise<Buffer>
497
497
  * @since 0.2.25
498
- * @status experimental
498
+ * @status stable
499
499
  * @spec RFC 8551, RFC 5652
500
500
  * @related pki.smime.verify, pki.cms.sign
501
501
  *
@@ -521,6 +521,10 @@ function _base64Body(der) {
521
521
  * @opts protectHeaders enable RFC 9788 header protection (`hp="clear"`) -- inline `opts.headers` on the signed payload + the outer display headers.
522
522
  * @opts headers the Non-Structural fields to protect + display: an object `{ Name: value }` or an array `[{ name, value }]` (Subject / From / To / Date / ...); used with `protectHeaders`.
523
523
  * @example
524
+ * var pair = await pki.key.generate("Ed25519");
525
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
526
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
527
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
524
528
  * var msg = await pki.smime.sign(Buffer.from("hello"), [{ cert: signerCertDer, key: signerKeyPkcs8 }]);
525
529
  */
526
530
  async function sign(content, signers, opts) {
@@ -565,7 +569,7 @@ function _capped(msg) {
565
569
  * @primitive pki.smime.verify
566
570
  * @signature pki.smime.verify(message, opts?) -> Promise<{ valid, signers, form, content, micalg, protectedHeaders, headerProtection }>
567
571
  * @since 0.2.25
568
- * @status experimental
572
+ * @status stable
569
573
  * @spec RFC 8551, RFC 5652, RFC 9788
570
574
  * @related pki.smime.sign, pki.cms.verify, pki.path.validate
571
575
  *
@@ -595,6 +599,11 @@ function _capped(msg) {
595
599
  * @opts strictMicalg reject a `multipart/signed` whose `micalg` disagrees with the SignerInfo digest.
596
600
  * @opts 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 }` -- `headers` an ordered `[{ name, value }]` array (retaining legally-repeated fields such as `Received`), the mode inferred from the envelope (`clear` here) -- NOT 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) -- the non-recursive layered API exposes no single seam holding both the inner signature verdict and the outer header section.
597
601
  * @example
602
+ * var pair = await pki.key.generate("Ed25519");
603
+ * var key = await pki.key.export(pair.privateKey);
604
+ * var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
605
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
606
+ * var smimeMessageBytes = await pki.smime.sign(Buffer.from("hello"), [{ cert: cert, key: key }]);
598
607
  * var res = await pki.smime.verify(smimeMessageBytes);
599
608
  * if (res.valid) { res.content; res.signers[0].sid; }
600
609
  */
@@ -680,7 +689,7 @@ function _cmsEncryptOpts(opts) {
680
689
  * @primitive pki.smime.encrypt
681
690
  * @signature pki.smime.encrypt(content, recipients, opts?) -> Promise<Buffer>
682
691
  * @since 0.2.26
683
- * @status experimental
692
+ * @status stable
684
693
  * @spec RFC 8551, RFC 5652, RFC 5083
685
694
  * @related pki.smime.decrypt, pki.cms.encrypt
686
695
  *
@@ -709,6 +718,11 @@ function _cmsEncryptOpts(opts) {
709
718
  * @opts keyIdentifier forwarded: `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
710
719
  * @opts ukm forwarded: user keying material for kari / kemri recipients.
711
720
  * @example
721
+ * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
722
+ * var pair = await pki.key.generate(rsa);
723
+ * var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
724
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
725
+ * { key: await pki.key.export(pair.privateKey) });
712
726
  * var enc = await pki.smime.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
713
727
  */
714
728
  async function encrypt(content, recipients, opts) {
@@ -735,7 +749,7 @@ async function encrypt(content, recipients, opts) {
735
749
  * @primitive pki.smime.decrypt
736
750
  * @signature pki.smime.decrypt(message, keyMaterial, opts?) -> Promise<{ content, smimeType, authenticated, recipientType, recipientIndex, contentEncryptionAlgorithm, protectedHeaders, headerProtection }>
737
751
  * @since 0.2.26
738
- * @status experimental
752
+ * @status stable
739
753
  * @spec RFC 8551, RFC 5652, RFC 5083, RFC 9788
740
754
  * @related pki.smime.encrypt, pki.cms.decrypt, pki.smime.verify
741
755
  *
@@ -763,9 +777,15 @@ async function encrypt(content, recipients, opts) {
763
777
  * @opts strictSmimeType reject a header `smime-type` that disagrees with the CMS body (`smime/smime-type-mismatch`).
764
778
  * @opts 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 }` -- `headers` an ordered `[{ name, value }]` array (retaining repeated fields), the `confidential` set derived from the actual visible outer Header Section -- NOT 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.
765
779
  * @example
780
+ * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
781
+ * var pair = await pki.key.generate(rsa);
782
+ * var recipientKeyPkcs8 = await pki.key.export(pair.privateKey);
783
+ * var recipientCertDer = await pki.x509.sign({ subject: "Recipient", subjectPublicKey: await pki.key.export(pair.publicKey),
784
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: recipientKeyPkcs8 });
785
+ * var smimeMessageBytes = await pki.smime.encrypt(Buffer.from("secret"), [{ cert: recipientCertDer }]);
766
786
  * var res = await pki.smime.decrypt(smimeMessageBytes, { key: recipientKeyPkcs8, cert: recipientCertDer });
767
787
  * res.content; // the recovered inner MIME entity
768
- * res.authenticated; // false for an enveloped-only (CBC) message -- no integrity
788
+ * res.authenticated; // true here (an AEAD content cipher); false for an enveloped-only CBC message, which carries no integrity
769
789
  */
770
790
  async function decrypt(message, keyMaterial, opts) {
771
791
  opts = opts || {};
@@ -795,7 +815,7 @@ async function decrypt(message, keyMaterial, opts) {
795
815
  * @primitive pki.smime.compress
796
816
  * @signature pki.smime.compress(content, opts?) -> Promise<Buffer>
797
817
  * @since 0.2.27
798
- * @status experimental
818
+ * @status stable
799
819
  * @spec RFC 8551, RFC 3274
800
820
  * @related pki.smime.decompress, pki.cms.compress
801
821
  *
@@ -827,7 +847,7 @@ async function compress(content, opts) {
827
847
  * @primitive pki.smime.decompress
828
848
  * @signature pki.smime.decompress(message, opts?) -> Promise<{ content, contentType, contentTypeName, compressionAlgorithm }>
829
849
  * @since 0.2.27
830
- * @status experimental
850
+ * @status stable
831
851
  * @spec RFC 8551, RFC 3274
832
852
  * @related pki.smime.compress, pki.cms.decompress, pki.smime.verify, pki.smime.decrypt
833
853
  *
@@ -842,6 +862,7 @@ async function compress(content, opts) {
842
862
  *
843
863
  * @opts maxOutputBytes forwarded to cms.decompress: lower the decompressed-output cap (a DoS bound; downward only).
844
864
  * @example
865
+ * var compressedSmimeBytes = await pki.smime.compress(Buffer.from("compress me"));
845
866
  * var res = await pki.smime.decompress(compressedSmimeBytes);
846
867
  * res.content; // the recovered inner MIME entity
847
868
  */
@@ -141,7 +141,7 @@ function _resolveAllowed(opts) {
141
141
  * @primitive pki.tls.decompressCertificate
142
142
  * @signature pki.tls.decompressCertificate(bytes, opts?) -> { algorithm, algorithmName, uncompressedLength, certificateMessage, certificate }
143
143
  * @since 0.4.3
144
- * @status experimental
144
+ * @status stable
145
145
  * @spec RFC 8879, RFC 8446
146
146
  * @related pki.tls.compressCertificate, pki.tls.parseCertificateMessage, pki.schema.x509.parse
147
147
  *
@@ -179,6 +179,10 @@ function _resolveAllowed(opts) {
179
179
  * @example
180
180
  * // The Certificate message this codec carries: an empty request context, then one
181
181
  * // entry -- the certificate DER followed by its (here empty) extensions vector.
182
+ * var pair = await pki.key.generate("Ed25519");
183
+ * var certDer = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
184
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
185
+ * { key: await pki.key.export(pair.privateKey) });
182
186
  * var u24 = function (n) { var b = Buffer.alloc(3); b.writeUIntBE(n, 0, 3); return b; };
183
187
  * var entry = Buffer.concat([u24(certDer.length), certDer, Buffer.from([0, 0])]);
184
188
  * var message = Buffer.concat([Buffer.from([0]), u24(entry.length), entry]);
@@ -252,7 +256,7 @@ function decompressCertificate(bytes, opts) {
252
256
  * @primitive pki.tls.parseCertificateMessage
253
257
  * @signature pki.tls.parseCertificateMessage(bytes, opts?) -> { certificateRequestContext, entries }
254
258
  * @since 0.4.3
255
- * @status experimental
259
+ * @status stable
256
260
  * @spec RFC 8446, RFC 7250
257
261
  * @related pki.tls.decompressCertificate, pki.schema.x509.parse
258
262
  *
@@ -277,6 +281,10 @@ function decompressCertificate(bytes, opts) {
277
281
  * certificateType - "X509" (default) or "RawPublicKey" (RFC 7250).
278
282
  *
279
283
  * @example
284
+ * var pair = await pki.key.generate("Ed25519");
285
+ * var certDer = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
286
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
287
+ * { key: await pki.key.export(pair.privateKey) });
280
288
  * var u24 = function (n) { var b = Buffer.alloc(3); b.writeUIntBE(n, 0, 3); return b; };
281
289
  * var entry = Buffer.concat([u24(certDer.length), certDer, Buffer.from([0, 0])]);
282
290
  * var message = Buffer.concat([Buffer.from([0]), u24(entry.length), entry]);
@@ -307,7 +315,7 @@ function parseCertificateMessage(bytes, opts) {
307
315
  * @primitive pki.tls.compressCertificate
308
316
  * @signature pki.tls.compressCertificate(certificateMessage, opts?) -> Buffer
309
317
  * @since 0.4.3
310
- * @status experimental
318
+ * @status stable
311
319
  * @spec RFC 8879
312
320
  * @related pki.tls.decompressCertificate
313
321
  *
@@ -330,6 +338,10 @@ function parseCertificateMessage(bytes, opts) {
330
338
  * level - the codec's compression level, passed through unchanged where it applies.
331
339
  *
332
340
  * @example
341
+ * var pair = await pki.key.generate("Ed25519");
342
+ * var certDer = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
343
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
344
+ * { key: await pki.key.export(pair.privateKey) });
333
345
  * var u24 = function (n) { var b = Buffer.alloc(3); b.writeUIntBE(n, 0, 3); return b; };
334
346
  * var entry = Buffer.concat([u24(certDer.length), certDer, Buffer.from([0, 0])]);
335
347
  * var message = Buffer.concat([Buffer.from([0]), u24(entry.length), entry]);
package/lib/trust.js CHANGED
@@ -353,7 +353,7 @@ function _dedupAnchors(anchors) {
353
353
  * @primitive pki.trust.parseCertdata
354
354
  * @signature pki.trust.parseCertdata(text) -> { anchors }
355
355
  * @since 0.2.0
356
- * @status experimental
356
+ * @status stable
357
357
  * @spec RFC 5280 (NSS certdata.txt object stream)
358
358
  * @defends trust-metadata-misattribution (CWE-345), trust-store-parser-DoS (CWE-770)
359
359
  * @related pki.trust.parseCcadbCsv, pki.trust.anchor, pki.path.validate
@@ -378,6 +378,11 @@ function _dedupAnchors(anchors) {
378
378
  * @example
379
379
  * // Real input is the NSS certdata.txt read from disk; a one-root stream is
380
380
  * // synthesized here from a DER certificate to show the object shape.
381
+ * var pair = await pki.key.generate("Ed25519");
382
+ * var der = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(pair.publicKey),
383
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
384
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"] } },
385
+ * { key: await pki.key.export(pair.privateKey) });
381
386
  * var cert = pki.schema.x509.parse(der);
382
387
  * var oct = function (buf) { return Array.prototype.map.call(buf, function (b) { return "\\" + ("000" + b.toString(8)).slice(-3); }).join(""); };
383
388
  * var blk = function (n, v) { return n + " MULTILINE_OCTAL\n" + oct(v) + "\nEND\n"; };
@@ -573,7 +578,7 @@ function _pemCell(cell) {
573
578
  * @primitive pki.trust.parseCcadbCsv
574
579
  * @signature pki.trust.parseCcadbCsv(text) -> { anchors }
575
580
  * @since 0.2.0
576
- * @status experimental
581
+ * @status stable
577
582
  * @spec RFC 4180, RFC 5280 (CCADB certificate-records CSV)
578
583
  * @defends trust-metadata-misattribution (CWE-345), trust-store-parser-DoS (CWE-770)
579
584
  * @related pki.trust.parseCertdata, pki.trust.anchor, pki.path.validate
@@ -592,6 +597,11 @@ function _pemCell(cell) {
592
597
  * encoding, through the same strict time reader.
593
598
  *
594
599
  * @example
600
+ * var pair = await pki.key.generate("Ed25519");
601
+ * var pemText = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(pair.publicKey),
602
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
603
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } },
604
+ * { key: await pki.key.export(pair.privateKey) }, { pem: true });
595
605
  * var csv = "Common Name or Certificate Name,Trust Bits," +
596
606
  * "Distrust for TLS After Date,Distrust for S/MIME After Date,PEM Info\n" +
597
607
  * 'Example Root,"Websites; Email",2027.06.01,,"' + pemText + '"';
@@ -651,7 +661,7 @@ function parseCcadbCsv(text) {
651
661
  * @primitive pki.trust.anchor
652
662
  * @signature pki.trust.anchor(entry, opts?) -> trustAnchor
653
663
  * @since 0.2.0
654
- * @status experimental
664
+ * @status stable
655
665
  * @spec RFC 5280 sec. 6.1.1 (NSS trust-bit semantics)
656
666
  * @related pki.trust.parseCertdata, pki.trust.parseCcadbCsv, pki.path.validate
657
667
  *
@@ -669,12 +679,25 @@ function parseCcadbCsv(text) {
669
679
  * purpose: string // "serverAuth" | "emailProtection" | "codeSigning" -- fail-fast purpose check
670
680
  *
671
681
  * @example
682
+ * var ca = await pki.key.generate("Ed25519");
683
+ * var caKey = await pki.key.export(ca.privateKey);
684
+ * var caDer = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(ca.publicKey),
685
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
686
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"], subjectKeyIdentifier: true } },
687
+ * { key: caKey });
688
+ * var pemText = pki.schema.x509.pemEncode(caDer, "CERTIFICATE");
689
+ * var leaf = await pki.key.generate("Ed25519");
690
+ * var der = await pki.x509.sign({ subject: "leaf.example", subjectPublicKey: await pki.key.export(leaf.publicKey),
691
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
692
+ * extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["serverAuth"], authorityKeyIdentifier: true } },
693
+ * { cert: caDer, key: caKey });
672
694
  * var csv = "Common Name or Certificate Name,Trust Bits," +
673
695
  * "Distrust for TLS After Date,Distrust for S/MIME After Date,PEM Info\n" +
674
696
  * 'Example Root,Websites,,,"' + pemText + '"';
675
697
  * var entry = pki.trust.parseCcadbCsv(csv).anchors[0];
676
698
  * var anchor = pki.trust.anchor(entry, { purpose: "serverAuth" });
677
- * await pki.path.validate([pki.schema.x509.parse(der)], { time: new Date(), trustAnchor: anchor, checkPurpose: "serverAuth" });
699
+ * await pki.path.validate([pki.schema.x509.parse(der)],
700
+ * { time: new Date("2026-06-01T00:00:00Z"), trustAnchor: anchor, checkPurpose: "serverAuth" });
678
701
  */
679
702
  function anchor(entry, opts) {
680
703
  if (!entry || typeof entry !== "object" || !Buffer.isBuffer(entry.publicKey) ||
package/lib/tsp-sign.js CHANGED
@@ -76,7 +76,7 @@ function _signingCertV2(certDer, hashName) {
76
76
  * @primitive pki.tsp.sign
77
77
  * @signature pki.tsp.sign(messageImprint, tsa, opts) -> Promise<Buffer|string>
78
78
  * @since 0.2.15
79
- * @status experimental
79
+ * @status stable
80
80
  * @spec RFC 3161
81
81
  * @related pki.schema.tsp.parseToken, pki.cms.sign
82
82
  *
@@ -96,6 +96,14 @@ function _signingCertV2(certDer, hashName) {
96
96
  * @opts certHashAlgorithm The ESSCertIDv2 hash algorithm name. Default `sha256`.
97
97
  * @opts sid / pem Passed through to `pki.cms.sign` (signer identifier, PEM output).
98
98
  * @example
99
+ * var pair = await pki.key.generate("Ed25519");
100
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
101
+ * var signerCertDer = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
102
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
103
+ * // RFC 3161 sec. 2.3: a TSA certificate's extendedKeyUsage MUST be critical
104
+ * extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
105
+ * { key: signerKeyPkcs8 });
106
+ * var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
99
107
  * var imprint = { hashAlgorithm: "sha256", hashedMessage: sha256Digest };
100
108
  * var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
101
109
  * (await pki.cms.verify(token)).valid; // true
@@ -200,7 +208,7 @@ function _assertImprint(mi) {
200
208
  * @primitive pki.tsp.request
201
209
  * @signature pki.tsp.request(messageImprint, opts) -> Buffer|string
202
210
  * @since 0.2.19
203
- * @status experimental
211
+ * @status stable
204
212
  * @spec RFC 3161
205
213
  * @related pki.tsp.parseRequest, pki.tsp.sign
206
214
  *
@@ -215,6 +223,7 @@ function _assertImprint(mi) {
215
223
  * @opts extensions An array of encoded Extension DER buffers ([0] IMPLICIT Extensions).
216
224
  * @opts pem Return a PEM "TIMESTAMP REQUEST" string instead of DER (boolean).
217
225
  * @example
226
+ * var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
218
227
  * var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: sha256Digest }, { nonce: 0x0102030405060708n, certReq: true });
219
228
  */
220
229
  function request(messageImprint, opts) {
@@ -242,7 +251,7 @@ function request(messageImprint, opts) {
242
251
  * @primitive pki.tsp.parseRequest
243
252
  * @signature pki.tsp.parseRequest(input) -> timeStampReq
244
253
  * @since 0.2.19
245
- * @status experimental
254
+ * @status stable
246
255
  * @spec RFC 3161
247
256
  * @related pki.tsp.request, pki.schema.tsp.parseRequest
248
257
  *
@@ -251,6 +260,8 @@ function request(messageImprint, opts) {
251
260
  * nonceHex, certReq, extensions }`; a malformed structure throws a typed `TspError`.
252
261
  *
253
262
  * @example
263
+ * var der = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
264
+ * { certReq: true });
254
265
  * var req = pki.tsp.parseRequest(der);
255
266
  * req.certReq; // -> boolean
256
267
  */
@@ -260,7 +271,7 @@ function parseRequest(input) { return schemaTsp.parseRequest(input); }
260
271
  * @primitive pki.tsp.response
261
272
  * @signature pki.tsp.response(token, opts) -> Buffer|string
262
273
  * @since 0.2.19
263
- * @status experimental
274
+ * @status stable
264
275
  * @spec RFC 3161
265
276
  * @related pki.tsp.parseResponse, pki.tsp.sign
266
277
  *
@@ -275,6 +286,14 @@ function parseRequest(input) { return schemaTsp.parseRequest(input); }
275
286
  * @opts statusString Human-readable PKIFreeText (string or array of strings).
276
287
  * @opts pem Return a PEM "TIMESTAMP RESPONSE" string instead of DER (boolean).
277
288
  * @example
289
+ * var pair = await pki.key.generate("Ed25519");
290
+ * var key = await pki.key.export(pair.privateKey);
291
+ * var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
292
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
293
+ * extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
294
+ * { key: key });
295
+ * var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
296
+ * { cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
278
297
  * var resp = pki.tsp.response(token, {}); // granted
279
298
  * var rej = pki.tsp.response(null, { status: 2, failInfo: ["badAlg"] }); // rejection
280
299
  */
@@ -308,7 +327,7 @@ function response(token, opts) {
308
327
  * @primitive pki.tsp.parseResponse
309
328
  * @signature pki.tsp.parseResponse(input) -> timeStampResp
310
329
  * @since 0.2.19
311
- * @status experimental
330
+ * @status stable
312
331
  * @spec RFC 3161
313
332
  * @related pki.tsp.response, pki.schema.tsp.parse
314
333
  *
@@ -317,6 +336,15 @@ function response(token, opts) {
317
336
  * status-to-token coupling enforced; a granted response's token is decoded via `parseToken`.
318
337
  *
319
338
  * @example
339
+ * var pair = await pki.key.generate("Ed25519");
340
+ * var key = await pki.key.export(pair.privateKey);
341
+ * var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
342
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
343
+ * extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
344
+ * { key: key });
345
+ * var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
346
+ * { cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
347
+ * var der = pki.tsp.response(token, {});
320
348
  * var resp = pki.tsp.parseResponse(der);
321
349
  * resp.timeStampToken.tstInfo.genTime; // -> Date (on a granted response)
322
350
  */
@@ -534,7 +562,7 @@ function _buildTsaChains(leaf, pool) {
534
562
  * @primitive pki.tsp.verify
535
563
  * @signature pki.tsp.verify(token, data, opts) -> Promise<result>
536
564
  * @since 0.2.19
537
- * @status experimental
565
+ * @status stable
538
566
  * @spec RFC 3161, RFC 5816
539
567
  * @related pki.tsp.sign, pki.cms.verify, pki.path.validate
540
568
  *
@@ -563,6 +591,13 @@ function _buildTsaChains(leaf, pool) {
563
591
  * token can still verify and chain.
564
592
  * @opts revocationChecker Passed through to `pki.path.validate`.
565
593
  * @example
594
+ * var pair = await pki.key.generate("Ed25519");
595
+ * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
596
+ * var signerCertDer = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
597
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
598
+ * extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
599
+ * { key: signerKeyPkcs8 });
600
+ * var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
566
601
  * var imprint = { hashAlgorithm: "sha256", hashedMessage: sha256Digest };
567
602
  * var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
568
603
  * var res = await pki.tsp.verify(token, Buffer.from("hello"), {});
@@ -1,41 +1,41 @@
1
1
  # Vendored dependencies
2
2
 
3
- `@blamejs/pki` ships with **zero npm runtime dependencies**, and it currently
4
- vendors **nothing** this directory holds only the manifest.
3
+ `@blamejs/pki` ships with zero npm runtime dependencies and currently vendors
4
+ nothing. This directory holds only the manifest.
5
5
 
6
6
  ## Native-first crypto
7
7
 
8
8
  The toolkit's cryptography runs entirely on Node's built-in `node:crypto`. The
9
9
  engine floor (Node `>=24.19`) links OpenSSL 3.5, which provides:
10
10
 
11
- - the full classical set RSA (PKCS#1 v1.5, PSS, OAEP), ECDSA, EdDSA
12
- (Ed25519/Ed448), ECDH (incl. X25519/X448), AES (GCM/CBC/CTR/KW), HMAC, HKDF,
13
- PBKDF2, and the SHA-1/2/3 family; and
14
- - the FIPS post-quantum set **ML-KEM** (FIPS 203), **ML-DSA** (FIPS 204), and
11
+ - the full classical set: RSA (PKCS#1 v1.5, PSS, OAEP), ECDSA, EdDSA
12
+ (Ed25519/Ed448), ECDH including X25519 and X448, AES (GCM/CBC/CTR/KW), HMAC,
13
+ HKDF, PBKDF2, and the SHA-1/2/3 family; and
14
+ - the FIPS post-quantum set: **ML-KEM** (FIPS 203), **ML-DSA** (FIPS 204), and
15
15
  **SLH-DSA** (FIPS 205).
16
16
 
17
- A platform built-in ships **zero bytes**, has **nothing to hash-pin or keep
18
- current**, and is **OpenSSL/NSS-interoperable by construction** which is what
19
- the toolkit's interoperability gate needs. So there is no reason to vendor a
20
- crypto library when the runtime already provides the primitive.
17
+ A platform built-in ships zero bytes, has nothing to hash-pin or keep current,
18
+ and is OpenSSL- and NSS-interoperable by construction, which is what the
19
+ toolkit's interoperability gate needs. There is no reason to vendor a crypto
20
+ library when the runtime already provides the primitive.
21
21
 
22
- ## When something IS vendored
22
+ ## When something is vendored
23
23
 
24
- A package is added here **only** when a specific operation is confirmed missing
25
- from the engine floor (for example, a pure-JS fallback for a post-quantum
26
- operation whose JS binding is absent on a supported Node version, or a
27
- cross-check reference used only in tests). When that happens:
24
+ A package is added here only when a specific operation is confirmed missing from
25
+ the engine floor: for example a pure-JS fallback for a post-quantum operation
26
+ whose JS binding is absent on a supported Node version, or a cross-check
27
+ reference used only in tests. When that happens:
28
28
 
29
29
  - `MANIFEST.json` records the package's version, SPDX license, upstream author,
30
30
  source URL, exported surface, bundler invocation, CPE, and pinned SHA-256, so
31
31
  any tampering is detectable and `scripts/check-vendor-currency.js` can gate
32
32
  version drift.
33
33
  - The top-level `NOTICE` gains the component's attribution.
34
- - The reason and the re-open condition are recorded alongside the entry — a
35
- vendored dependency is a deliberate, justified exception to native-first, not
36
- a default.
34
+ - The reason and the re-open condition are recorded alongside the entry. A
35
+ vendored dependency is a deliberate, justified exception to native-first
36
+ rather than a default.
37
37
 
38
38
  Bundles, when present, are produced with
39
39
  `esbuild --format=cjs --minify --platform=node` against the pinned upstream
40
- version; refreshing one recomputes its SHA-256 and updates the bytes and the
40
+ version. Refreshing one recomputes its SHA-256 and updates the bytes and the
41
41
  matching `hashes.server` entry in the same change.