@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
@@ -47,6 +47,7 @@ var CODE_SHAPE = /^[a-z0-9-]+\/[a-z0-9-]+$/;
47
47
  * site instead of shipping prose into a code-switching consumer.
48
48
  *
49
49
  * @example
50
+ * var bytes = Buffer.from([0x30, 0x80]); // indefinite length -- not valid DER
50
51
  * try { pki.asn1.decode(bytes); }
51
52
  * catch (e) {
52
53
  * if (e instanceof pki.errors.PkiError) console.error(e.code);
@@ -88,6 +89,7 @@ class PkiError extends Error {
88
89
  * withCause: boolean, // default: false -- constructor becomes (code, message, cause)
89
90
  *
90
91
  * @example
92
+ * // throws: my/bad-input -- raising the new error type IS what this shows
91
93
  * var MyError = pki.errors.defineClass("MyError");
92
94
  * throw new MyError("my/bad-input", "explanation");
93
95
  */
@@ -273,6 +275,16 @@ var CsrattrsError = defineClass("CsrattrsError", { withCause: true });
273
275
  // `.cause`.
274
276
  var EstError = defineClass("EstError", { withCause: true });
275
277
 
278
+ // CmcError -- an RFC 5272 / 6402 Certificate Management over CMS message-layer
279
+ // fault: a PKIData / PKIResponse whose structure, body-part identity, control
280
+ // placement or status encoding is malformed, a control this side must act on
281
+ // that cannot be decoded, or an exchange binding (transactionId / recipientNonce)
282
+ // that does not match the request it answers. Distinct from EstError because the
283
+ // CMC layer is the MESSAGE and EST is the TRANSPORT that carried it: an operator
284
+ // needs to know whether the CA's answer was unreadable or the HTTP hop failed.
285
+ // Carries the underlying cms/* or asn1/* leaf fault as `.cause`.
286
+ var CmcError = defineClass("CmcError", { withCause: true });
287
+
276
288
  // TransportError -- a fault from the shared node:https transport (pki.transport):
277
289
  // a non-https / unparseable request URL, a missing trust anchor, a TLS handshake /
278
290
  // server-authentication failure, a negotiated protocol below the floor, a response
@@ -380,6 +392,7 @@ module.exports = {
380
392
  SmimeError: SmimeError,
381
393
  CsrattrsError: CsrattrsError,
382
394
  EstError: EstError,
395
+ CmcError: CmcError,
383
396
  TransportError: TransportError,
384
397
  JoseError: JoseError,
385
398
  AcmeError: AcmeError,
@@ -57,4 +57,40 @@ function source(input, ErrorClass, code, label) {
57
57
  throw new ErrorClass(code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
58
58
  }
59
59
 
60
- module.exports = { view: view, source: source };
60
+ // snapshot(input, ErrorClass, code, label) -> private Buffer copy | throws ErrorClass
61
+ //
62
+ // The parse-then-verify time-of-check/time-of-use defence. A verification entry
63
+ // point that PARSES its input synchronously and then VERIFIES a signature over
64
+ // the same bytes in a later promise turn is reading the caller's memory twice
65
+ // with an await in between. Every byte range the parse surfaced -- the signed
66
+ // content, the signer set, the values the verdict is built from -- is a VIEW into
67
+ // that memory, so anything that rewrites the buffer in the gap makes the verdict
68
+ // describe bytes other than the ones the signature was checked against
69
+ // (CWE-367 TOCTOU reaching a CWE-347 wrong-verdict). The window is real without
70
+ // an attacker in the process: a caller recycling a pooled read buffer across
71
+ // concurrent verifies hits it by accident.
72
+ //
73
+ // So take one private copy at the boundary and read EVERYTHING from it. `view`
74
+ // re-views and is the right guard where the input is consumed in one synchronous
75
+ // pass; this is its sibling for the boundary that spans an await.
76
+ // @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any
77
+ // `Buffer.from(x)` is one, and most are legitimate). The guard is the RED vector
78
+ // that mutates the caller's buffer between parse and verify and asserts the
79
+ // verdict still describes the bytes that were verified.
80
+ function snapshot(input, ErrorClass, code, label) {
81
+ return Buffer.from(view(input, ErrorClass, code, label));
82
+ }
83
+
84
+ // snapshotSource(input, ErrorClass, code, label) -> private Buffer copy | throws
85
+ // The same parse-then-verify defence as `snapshot`, over the FULL W3C BufferSource
86
+ // (raw ArrayBuffer / DataView / any typed-array view) rather than only the
87
+ // Buffer / Uint8Array contract. A parser that accepts a BufferSource must snapshot
88
+ // the same set: leaving an ArrayBuffer or a DataView aliased reopens the window for
89
+ // exactly the inputs that took the wider path in.
90
+ // @enforced-by behavioral -- a copy has no rename-proof shape to detect; the guard
91
+ // is the RED vector that mutates the caller's backing buffer across the await.
92
+ function snapshotSource(input, ErrorClass, code, label) {
93
+ return Buffer.from(source(input, ErrorClass, code, label));
94
+ }
95
+
96
+ module.exports = { view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource };
@@ -99,4 +99,26 @@ function uint64(value, E, code, label) {
99
99
  return v;
100
100
  }
101
101
 
102
- module.exports = { int: int, uint31: uint31, positiveInt31: positiveInt31, uint64: uint64 };
102
+ // authoredInteger(value, E, code, label) -> BigInt. The AUTHORING counterpart to
103
+ // int(): a value a CALLER supplies for an ASN.1 INTEGER with no upper bound (a CMC
104
+ // Transaction Identifier, a CRMF certReqId), normalized to the BigInt the builders
105
+ // take. Accepts a bigint, or a Number that is a SAFE integer -- isSafeInteger, not
106
+ // isInteger, because a Number above 2^53 has already lost precision by the time it
107
+ // arrives, so a large identifier MUST come as a bigint rather than be silently
108
+ // rounded to a neighbour. Everything else is a config-time reject.
109
+ //
110
+ // Distinct from int() on purpose: int() BOUNDS a value the wire decoded and narrows
111
+ // it to Number; this one is unbounded and returns BigInt, because the field has no
112
+ // ceiling to check against and the encoder wants the wide type.
113
+ // @enforced-by guard-shape-reinlined
114
+ // @guard-shape Number\.isSafeInteger\(\s*[A-Za-z_$][\w$]*\s*\)\s*\)\s*return BigInt
115
+ function authoredInteger(value, E, code, label) {
116
+ if (typeof value === "bigint") return value;
117
+ if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
118
+ throw E(code, label + " must be an integer (a safe-integer number, or a bigint for a large value)");
119
+ }
120
+
121
+ module.exports = {
122
+ int: int, uint31: uint31, positiveInt31: positiveInt31, uint64: uint64,
123
+ authoredInteger: authoredInteger,
124
+ };
@@ -13,11 +13,17 @@
13
13
  * drive -- `pki.est` now, `pki.acme` and `pki.cmp` next. This is the ONLY module in
14
14
  * the toolkit that opens a socket; every protocol layer stays transport-agnostic and
15
15
  * composes it (or an injected substitute) through one contract:
16
- * `transport(request) -> Promise<{ status, headers, body }>`. The response triple is
16
+ * `transport(request) -> Promise<{ status, headers, body, tls }>`. The first three are
17
17
  * exactly what a message layer's classifier consumes, so no protocol semantics leak
18
18
  * into the socket layer -- the transport owns socket lifecycle, the TLS trust policy,
19
19
  * the streaming size cap, and the timeout budget; the caller owns HTTP status,
20
- * content-type, redirect, and authentication decisions.
20
+ * content-type, redirect, and authentication decisions. `tls` reports the negotiated
21
+ * channel -- `{ protocol, cipher, peerCertificate }` -- which is the one fact a caller
22
+ * cannot recover from the response bytes. An INJECTED substitute should return it too:
23
+ * `pki.est.serverkeygen` asserts the negotiated cipher can protect the private key it
24
+ * is about to accept, and a transport that reports no cipher is trusted rather than
25
+ * refused (so a loopback test channel works), which means omitting the field silently
26
+ * skips that assertion.
21
27
  *
22
28
  * `pki.transport.https(defaults?)` binds TLS + budget defaults and returns a
23
29
  * transport. Trust is EXPLICIT and fail-closed: a request is refused unless it
@@ -33,7 +39,7 @@
33
39
  * @card
34
40
  * The shared fail-closed node:https transport (est / acme / cmp): explicit trust
35
41
  * anchors, rejectUnauthorized always on, a TLS floor, a streaming response-size cap,
36
- * and a timeout -- behind one `transport(request) -> {status, headers, body}` seam.
42
+ * and a timeout -- behind one `transport(request) -> {status, headers, body, tls}` seam.
37
43
  */
38
44
 
39
45
  var nodeHttps = require("node:https");
package/lib/inspect.js CHANGED
@@ -607,7 +607,7 @@ function _parse(input) {
607
607
  * @primitive pki.inspect.certificate
608
608
  * @signature pki.inspect.certificate(input) -> string
609
609
  * @since 0.2.4
610
- * @status experimental
610
+ * @status stable
611
611
  * @spec RFC 5280
612
612
  * @related pki.schema.x509.parse
613
613
  *
@@ -618,6 +618,10 @@ function _parse(input) {
618
618
  * hex dump rather than failing the whole report. Pure -- no OpenSSL dependency.
619
619
  *
620
620
  * @example
621
+ * var pair = await pki.key.generate("Ed25519");
622
+ * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
623
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
624
+ * { key: await pki.key.export(pair.privateKey) });
621
625
  * var cert = pki.schema.x509.parse(der);
622
626
  * pki.inspect.certificate(cert).split("\n")[0]; // "Certificate:"
623
627
  */
@@ -753,7 +757,7 @@ function _crlExtension(ext, pad) {
753
757
  * @primitive pki.inspect.crl
754
758
  * @signature pki.inspect.crl(input) -> string
755
759
  * @since 0.3.8
756
- * @status experimental
760
+ * @status stable
757
761
  * @spec RFC 5280
758
762
  * @related pki.schema.crl.parse, pki.inspect.certificate
759
763
  *
@@ -765,6 +769,13 @@ function _crlExtension(ext, pad) {
765
769
  * extension renders as hex rather than failing the report.
766
770
  *
767
771
  * @example
772
+ * var pair = await pki.key.generate("Ed25519");
773
+ * var key = await pki.key.export(pair.privateKey);
774
+ * var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
775
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
776
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["cRLSign"] } }, { key: key });
777
+ * var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
778
+ * { cert: caCert, key: key });
768
779
  * pki.inspect.crl(crlDer).split("\n")[0]; // "Certificate Revocation List (CRL):"
769
780
  */
770
781
  function crlReport(input) {
@@ -818,7 +829,7 @@ function _attribute(attr, pad) {
818
829
  * @primitive pki.inspect.csr
819
830
  * @signature pki.inspect.csr(input) -> string
820
831
  * @since 0.3.8
821
- * @status experimental
832
+ * @status stable
822
833
  * @spec RFC 2986
823
834
  * @related pki.schema.csr.parse, pki.inspect.certificate
824
835
  *
@@ -829,6 +840,9 @@ function _attribute(attr, pad) {
829
840
  * input `inspect/bad-input`. Best-effort like `certificate`.
830
841
  *
831
842
  * @example
843
+ * var pair = await pki.key.generate("Ed25519");
844
+ * var csrDer = await pki.csr.sign({ subject: "req.example", subjectPublicKey: await pki.key.export(pair.publicKey) },
845
+ * { key: await pki.key.export(pair.privateKey) });
832
846
  * pki.inspect.csr(csrDer).split("\n")[0]; // "Certificate Request:"
833
847
  */
834
848
  function csrReport(input) {
@@ -927,7 +941,7 @@ function _signerInfo(si, pad) {
927
941
  * @primitive pki.inspect.cms
928
942
  * @signature pki.inspect.cms(input) -> string
929
943
  * @since 0.3.8
930
- * @status experimental
944
+ * @status stable
931
945
  * @spec RFC 5652
932
946
  * @related pki.schema.cms.parse, pki.inspect.certificate
933
947
  *
@@ -939,6 +953,11 @@ function _signerInfo(si, pad) {
939
953
  * `pki.schema.cms.parse` result; a non-CMS throws `inspect/bad-cms`. Best-effort.
940
954
  *
941
955
  * @example
956
+ * var pair = await pki.key.generate("Ed25519");
957
+ * var key = await pki.key.export(pair.privateKey);
958
+ * var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
959
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
960
+ * var cmsDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
942
961
  * pki.inspect.cms(cmsDer).split("\n")[0]; // "CMS ContentInfo:"
943
962
  */
944
963
  // A ContentInfo whose content type pki.schema.cms.parse does not dispatch (id-data,
@@ -1003,7 +1022,7 @@ var _INSPECT_BY_FORMAT = { x509: certificate, crl: crlReport, csr: csrReport, cm
1003
1022
  * @primitive pki.inspect.any
1004
1023
  * @signature pki.inspect.any(input) -> string
1005
1024
  * @since 0.3.8
1006
- * @status experimental
1025
+ * @status stable
1007
1026
  * @spec RFC 5280
1008
1027
  * @related pki.schema.detectFormat, pki.inspect.certificate
1009
1028
  *
@@ -1015,6 +1034,10 @@ var _INSPECT_BY_FORMAT = { x509: certificate, crl: crlReport, csr: csrReport, cm
1015
1034
  * `inspect/bad-input`.
1016
1035
  *
1017
1036
  * @example
1037
+ * var pair = await pki.key.generate("Ed25519");
1038
+ * var der = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
1039
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
1040
+ * { key: await pki.key.export(pair.privateKey) });
1018
1041
  * pki.inspect.any(der); // routes to the right report by detected format
1019
1042
  */
1020
1043
  function any(input) {
package/lib/jose.js CHANGED
@@ -327,7 +327,7 @@ function assertPublicJwk(jwk) {
327
327
 
328
328
  /**
329
329
  * @primitive pki.jose.verify
330
- * @signature pki.jose.verify(jws, opts) -> Promise<{ header, payload }>
330
+ * @signature pki.jose.verify(jws, opts) -> Promise<{ header, payload, keySource }>
331
331
  * @since 0.1.25
332
332
  * @status stable
333
333
  * @spec RFC 7515, RFC 7518, RFC 8555
@@ -337,16 +337,31 @@ function assertPublicJwk(jwk) {
337
337
  * `"acme-outer"`). Structural rules fail closed BEFORE any crypto: the
338
338
  * `signatures`/`header` members and a detached payload are rejected, the
339
339
  * protected header is validated against the profile (alg registry, nonce, url,
340
- * exactly-one-of jwk/kid, crit), the signature byte length is pinned per alg, and
341
- * the verification key is the header `jwk` (only where the profile permits it) or
342
- * `opts.key` (a JWK). Returns the decoded `{ header, payload }` (payload a raw
343
- * `Buffer`); a failed signature throws `jose/verify-failed`.
340
+ * exactly-one-of jwk/kid, crit), and the signature byte length is pinned per alg.
341
+ *
342
+ * `opts.key` names the key the message must be signed under, and it governs: where
343
+ * the profile also permits an embedded header `jwk`, the two must be the SAME key
344
+ * or the message is refused with `jose/key-mismatch`. They are compared as RFC 7638
345
+ * thumbprints, so member order and members outside the key itself cannot make equal
346
+ * keys look different. Without `opts.key` the embedded `jwk` is used where the
347
+ * profile permits one -- which verifies that the message is internally consistent,
348
+ * not that any particular signer produced it. `keySource` reports which of the two
349
+ * answered, so a signature checked against a caller-named key is distinguishable
350
+ * from one checked against the key the message brought with it.
351
+ *
352
+ * Returns `{ header, payload, keySource }` (payload a raw `Buffer`); a failed
353
+ * signature throws `jose/verify-failed`.
344
354
  *
345
355
  * @opts
346
356
  * profile: string // "acme-outer" | "eab-inner" | "keychange-inner"
347
357
  * key: object // a public JWK, required unless the profile embeds jwk
348
358
  *
349
359
  * @example
360
+ * var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
361
+ * var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
362
+ * var jws = await pki.jose.sign({ protected: { alg: "ES256", jwk: accountJwk,
363
+ * nonce: "oFvnlFP1wIhRlYS2jTaXbA", url: "https://ca.example/acme/new-acct" },
364
+ * payload: Buffer.from("{}"), key: ec.privateKey });
350
365
  * var v = await pki.jose.verify(jws, { profile: "acme-outer", key: accountJwk });
351
366
  * v.header.alg; // -> "ES256"
352
367
  */
@@ -360,8 +375,38 @@ async function verify(jws, opts) {
360
375
  var header = parseJson(b64uDecode(jws.protected));
361
376
  var profileName = opts.profile || "acme-outer";
362
377
  var checked = _checkHeader(header, profileName);
378
+ // A caller supplying opts.key is NAMING the key this message must be signed under. A profile
379
+ // that also permits an embedded jwk lets the message carry one, and preferring that would let
380
+ // the sender choose which key verifies it -- exactly the question opts.key was asked to settle.
381
+ // So when both are present they must be the SAME key, compared as RFC 7638 thumbprints: that
382
+ // canonicalizes member order and ignores members outside the key itself, so two spellings of
383
+ // one key agree and two different keys cannot.
384
+ // A SUPPLIED key that cannot be used is refused, never quietly treated as absent. Falling back
385
+ // to the embedded jwk there would drop the caller's intent to pin a signer at the moment it
386
+ // matters most -- a `key` that came back null from a lookup would verify against whatever the
387
+ // message carried, and the verdict would report the embedded key as though none was named.
388
+ // `undefined` alone means "not supplied", so spreading an options object stays safe.
389
+ if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || Array.isArray(opts.key))) {
390
+ throw E("jose/bad-key", "opts.key was supplied but is not a JWK object, so the key this message must be signed under cannot be established");
391
+ }
363
392
  var jwk = header.jwk || opts.key;
364
393
  if (!jwk) throw E("jose/bad-key", "a verification key is required (opts.key) when the profile does not embed a jwk");
394
+ var keySource = "embedded-jwk";
395
+ if (opts.key) {
396
+ keySource = "opts.key";
397
+ if (header.jwk) {
398
+ var embeddedTp, suppliedTp;
399
+ try { embeddedTp = await thumbprint(header.jwk); suppliedTp = await thumbprint(opts.key); }
400
+ catch (e) { throw E("jose/bad-key", "the embedded jwk and opts.key could not be compared as RFC 7638 thumbprints", e); }
401
+ if (embeddedTp !== suppliedTp) {
402
+ throw E("jose/key-mismatch",
403
+ "the JWS embeds a jwk that is not the key supplied as opts.key, so the message names a " +
404
+ "different signer than the caller expects (embedded thumbprint " + embeddedTp +
405
+ ", supplied " + suppliedTp + ")");
406
+ }
407
+ }
408
+ jwk = opts.key;
409
+ }
365
410
  _assertKeyType(checked.algRow, jwk);
366
411
  var sig = b64uDecode(jws.signature);
367
412
  var want = _expectedSigBytes(checked.algRow, jwk);
@@ -373,7 +418,10 @@ async function verify(jws, opts) {
373
418
  catch (e) { throw E("jose/bad-key", "the JWK could not be imported for verification", e); }
374
419
  var ok = await webcrypto.subtle.verify(_cryptoAlg(checked.algRow, jwk), key, sig, signingInput);
375
420
  if (!ok) throw E("jose/verify-failed", "the JWS signature did not verify");
376
- return { header: header, payload: b64uDecode(jws.payload) };
421
+ // WHICH key answered. A caller auditing the decision needs "checked against the key I named"
422
+ // to be distinguishable from "checked against the one the message brought with it"; the two
423
+ // are different claims and only the first says anything about who the signer is.
424
+ return { header: header, payload: b64uDecode(jws.payload), keySource: keySource };
377
425
  }
378
426
 
379
427
  // ---- flattened-JWS sign (RFC 7515 sec. 5.1) ------------------------------
@@ -405,6 +453,10 @@ async function verify(jws, opts) {
405
453
  * jwk: object // the public JWK, when the header embeds jwk
406
454
  *
407
455
  * @example
456
+ * var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
457
+ * var priv = ec.privateKey;
458
+ * var hdr = { alg: "ES256", jwk: await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey),
459
+ * nonce: "oFvnlFP1wIhRlYS2jTaXbA", url: "https://ca.example/acme/new-acct" };
408
460
  * var jws = await pki.jose.sign({ protected: hdr, payload: Buffer.from("{}"), key: priv });
409
461
  */
410
462
  async function sign(opts) {
@@ -491,6 +543,12 @@ var THUMBPRINT_MEMBERS = {
491
543
  * always yields the same thumbprint (the ACME key-authorization anchor).
492
544
  *
493
545
  * @example
546
+ * // the RFC 7638 sec. 3.1 worked example, so the thumbprint below is the spec's own
547
+ * var accountJwk = { kty: "RSA", e: "AQAB", n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78L" +
548
+ * "hWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXA" +
549
+ * "rwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajr" +
550
+ * "n1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-" +
551
+ * "kEgU8awapJzKnqDKgw" };
494
552
  * await pki.jose.thumbprint(accountJwk); // -> "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
495
553
  */
496
554
  async function thumbprint(jwk) {
package/lib/lint.js CHANGED
@@ -715,6 +715,10 @@ function _applyThreshold(report, severity) {
715
715
  * @opts severity Suppress findings below this floor (default `"notice"`). `counts` and
716
716
  * `worst` always reflect the complete, unfiltered result.
717
717
  * @example
718
+ * var pair = await pki.key.generate("Ed25519");
719
+ * var pemString = await pki.x509.sign({ subject: "example.com", subjectPublicKey: await pki.key.export(pair.publicKey),
720
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
721
+ * { key: await pki.key.export(pair.privateKey) }, { pem: true });
718
722
  * var report = pki.lint.certificate(pemString);
719
723
  * report.worst; // "notice" | "error" | ...
720
724
  * report.findings.map(function (f) { return f.id; });
package/lib/merkle.js CHANGED
@@ -102,7 +102,7 @@ function _ctEq(a, b) {
102
102
  * @primitive pki.merkle.leafHash
103
103
  * @signature pki.merkle.leafHash(entry) -> Buffer
104
104
  * @since 0.1.28
105
- * @status experimental
105
+ * @status stable
106
106
  * @spec RFC 6962, RFC 9162
107
107
  * @related pki.merkle.nodeHash, pki.merkle.verifyInclusion
108
108
  *
@@ -122,7 +122,7 @@ function leafHash(entry) {
122
122
  * @primitive pki.merkle.nodeHash
123
123
  * @signature pki.merkle.nodeHash(left, right) -> Buffer
124
124
  * @since 0.1.28
125
- * @status experimental
125
+ * @status stable
126
126
  * @spec RFC 6962, RFC 9162
127
127
  * @related pki.merkle.leafHash
128
128
  *
@@ -146,7 +146,7 @@ function nodeHash(left, right) {
146
146
  * @primitive pki.merkle.emptyRootHash
147
147
  * @signature pki.merkle.emptyRootHash() -> Buffer
148
148
  * @since 0.1.28
149
- * @status experimental
149
+ * @status stable
150
150
  * @spec RFC 6962, RFC 9162
151
151
  * @related pki.merkle.verifyConsistency
152
152
  *
@@ -164,7 +164,7 @@ function emptyRootHash() {
164
164
  * @primitive pki.merkle.verifyInclusion
165
165
  * @signature pki.merkle.verifyInclusion(opts) -> boolean
166
166
  * @since 0.1.28
167
- * @status experimental
167
+ * @status stable
168
168
  * @spec RFC 6962, RFC 9162
169
169
  * @related pki.merkle.leafHash, pki.merkle.verifyConsistency
170
170
  *
@@ -223,7 +223,7 @@ function verifyInclusion(opts) {
223
223
  * @primitive pki.merkle.verifyConsistency
224
224
  * @signature pki.merkle.verifyConsistency(opts) -> boolean
225
225
  * @since 0.1.28
226
- * @status experimental
226
+ * @status stable
227
227
  * @spec RFC 6962, RFC 9162
228
228
  * @related pki.merkle.verifyInclusion, pki.merkle.emptyRootHash
229
229
  *