@blamejs/pki 0.5.7 → 0.5.8

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 (104) hide show
  1. package/CHANGELOG.md +405 -386
  2. package/MIGRATING.md +43 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +19 -19
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +97 -92
  11. package/lib/cmc-verify.js +106 -30
  12. package/lib/cmp-build.js +25 -25
  13. package/lib/cmp-session.js +70 -70
  14. package/lib/cmp-verify.js +71 -57
  15. package/lib/cms-compress.js +7 -7
  16. package/lib/cms-decrypt.js +90 -72
  17. package/lib/cms-encrypt.js +32 -32
  18. package/lib/cms-sign.js +74 -55
  19. package/lib/cms-verify.js +97 -75
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +22 -22
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +14 -14
  25. package/lib/csr-sign.js +8 -8
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +98 -55
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +3 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +79 -79
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-encoding.js +15 -15
  36. package/lib/guard-header.js +3 -3
  37. package/lib/guard-identifier.js +16 -16
  38. package/lib/guard-json.js +15 -15
  39. package/lib/guard-limits.js +7 -7
  40. package/lib/guard-name.js +81 -16
  41. package/lib/guard-parsed.js +80 -80
  42. package/lib/guard-range.js +19 -19
  43. package/lib/guard-secret.js +11 -10
  44. package/lib/guard-text.js +6 -6
  45. package/lib/guard-time.js +10 -10
  46. package/lib/hpke.js +18 -17
  47. package/lib/http-digest.js +35 -35
  48. package/lib/http-retry-after.js +13 -13
  49. package/lib/http-transport.js +20 -19
  50. package/lib/inspect.js +53 -53
  51. package/lib/ip-utils.js +2 -2
  52. package/lib/jose.js +13 -13
  53. package/lib/key.js +16 -16
  54. package/lib/lint.js +51 -51
  55. package/lib/merkle.js +51 -36
  56. package/lib/mime.js +18 -18
  57. package/lib/ocsp-verify.js +10 -10
  58. package/lib/ocsp.js +13 -13
  59. package/lib/oid.js +29 -29
  60. package/lib/path-validate.js +114 -113
  61. package/lib/pbes2.js +16 -16
  62. package/lib/pkcs12-build.js +53 -53
  63. package/lib/pki-build.js +21 -19
  64. package/lib/rc2.js +1 -1
  65. package/lib/rfc3339.js +5 -5
  66. package/lib/schema-all.js +31 -31
  67. package/lib/schema-attrcert.js +12 -12
  68. package/lib/schema-c509.js +144 -142
  69. package/lib/schema-cmc.js +58 -58
  70. package/lib/schema-cmp.js +43 -43
  71. package/lib/schema-cms.js +45 -45
  72. package/lib/schema-crl.js +7 -7
  73. package/lib/schema-crmf.js +28 -28
  74. package/lib/schema-csr.js +12 -12
  75. package/lib/schema-csrattrs.js +16 -16
  76. package/lib/schema-engine.js +18 -18
  77. package/lib/schema-ocsp.js +15 -15
  78. package/lib/schema-pkcs12.js +20 -20
  79. package/lib/schema-pkcs8.js +2 -2
  80. package/lib/schema-pkix.js +131 -126
  81. package/lib/schema-smime.js +19 -19
  82. package/lib/schema-tsp.js +12 -12
  83. package/lib/schema-x509.js +3 -3
  84. package/lib/shbs.js +18 -18
  85. package/lib/sign-scheme.js +13 -13
  86. package/lib/sigstore.js +10 -11
  87. package/lib/sleep.js +1 -1
  88. package/lib/smime.js +308 -96
  89. package/lib/tls-cert-compress.js +18 -18
  90. package/lib/trust.js +27 -27
  91. package/lib/tsp-sign.js +17 -17
  92. package/lib/validator-all.js +1 -1
  93. package/lib/validator-attcert.js +1 -1
  94. package/lib/validator-cose.js +43 -44
  95. package/lib/validator-keydesc.js +3 -3
  96. package/lib/validator-sig.js +13 -13
  97. package/lib/validator-tls.js +11 -11
  98. package/lib/validator-tpm.js +20 -19
  99. package/lib/webauthn-mds.js +66 -66
  100. package/lib/webauthn.js +33 -33
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +13 -13
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
package/lib/cmc-verify.js CHANGED
@@ -8,13 +8,13 @@
8
8
  * @intro Interpret an RFC 5272 Full PKI Response. `verify(response, sent)` takes the response
9
9
  * a CA returned and the state the client retained from its request, binds the two together
10
10
  * (transaction identifier, the Sender/Recipient Nonce echo, the Data Return echo), reads the
11
- * ordered status verdicts, and reduces them to ONE terminal outcome: `issued`, `pending`,
11
+ * ordered status verdicts, and reduces them to one terminal outcome: `issued`, `pending`,
12
12
  * `confirm-required`, `pop-required` or `rejected`. The issued certificates come from the CMS
13
13
  * certificate bag, where RFC 5272 sec. 4.2 puts them. Nothing here is trusted: the bag and any
14
14
  * Publish Trust Anchors control are surfaced as DATA for the caller to validate through
15
15
  * `pki.path.validate`.
16
16
  * @spec RFC 5272, RFC 5273, RFC 6402
17
- * @card Interpret a CMC Full PKI Response into one terminal verdict -- transaction and nonce
17
+ * @card Interpret a CMC Full PKI Response into one terminal verdict: transaction and nonce
18
18
  * binding, the status verdicts, the certificate bag surfaced untrusted.
19
19
  */
20
20
 
@@ -53,7 +53,7 @@ var OUTCOME_BY_STATUS = {
53
53
 
54
54
  // Which outcome governs when a response carries SEVERAL status controls (sec.
55
55
  // 6.1 requires a client to cope with that). Ranked worst-first so the verdict can
56
- // never be improved by the ORDER the controls happen to appear in -- reporting
56
+ // never be improved by the order the controls happen to appear in: reporting
57
57
  // the first one seen would let a server bury a failure behind a success.
58
58
  var OUTCOME_SEVERITY = { rejected: 4, "pop-required": 3, "confirm-required": 2, pending: 1, issued: 0 };
59
59
 
@@ -67,9 +67,9 @@ function _singleValue(control, what) {
67
67
  // The single instance of a control, or null when absent.
68
68
  //
69
69
  // Body-part identity is unique per element, so a responder can legally carry the
70
- // SAME control type twice under different bodyPartIDs. Taking the first match
70
+ // same control type twice under different bodyPartIDs. Taking the first match
71
71
  // would let an attacker pair one correct echo with a contradictory one and still
72
- // satisfy the binding -- so a duplicate is refused as ambiguous rather than
72
+ // satisfy the binding, so a duplicate is refused as ambiguous instead of
73
73
  // resolved. The exchange either has one answer or it has none.
74
74
  function _findControl(controls, attrType, what) {
75
75
  var found = null;
@@ -90,7 +90,7 @@ function _octets(control, what) {
90
90
  // The certificate / CRL bag, from whichever carrier holds it. A SignedData keeps
91
91
  // them at the top level; an AuthenticatedData keeps them under `originatorInfo`
92
92
  // (RFC 5652 sec. 9.1). Both carriers are accepted for a Full PKI Response, so
93
- // reading only the SignedData shape would silently return an empty bag -- and
93
+ // reading only the SignedData shape would silently return an empty bag, and
94
94
  // "no certificates were issued" is exactly the wrong thing to report when some
95
95
  // were.
96
96
  function _bagOf(parsedCms, originatorKey, topKey) {
@@ -106,10 +106,30 @@ function _bagOf(parsedCms, originatorKey, topKey) {
106
106
  * Both halves are CONDITIONAL on what the client sent, which is the subtlety: a
107
107
  * client that sent no transactionId cannot demand one back, and a client that
108
108
  * sent no senderNonce has nothing to compare a recipientNonce against. What is
109
- * NOT conditional is the converse -- having sent one, an absent or differing echo
109
+ * not conditional is the converse: having sent one, an absent or differing echo
110
110
  * is a refusal, because that is exactly the replay the nonce exists to stop.
111
+ *
112
+ * Returns which halves ran, because "conditional" and "checked" are different
113
+ * facts and the verdict has to be able to tell them apart.
111
114
  */
112
115
  function _assertBound(body, sent) {
116
+ // A zero-length value binds nothing. An empty senderNonce echoes equal in every
117
+ // exchange that also used one, so counting its presence would report boundToRequest
118
+ // true for a response captured from any of them -- the exact replay this gate exists
119
+ // to refuse, passing because the comparison it ran was vacuous. The same holds for an
120
+ // empty transactionId or dataReturn: a binding has to carry entropy to be a binding.
121
+ function _carries(v) {
122
+ if (v == null) return false;
123
+ if (typeof v === "string") return v.length > 0;
124
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return v.length > 0;
125
+ return true; // an integer or other scalar identifier is its own value
126
+ }
127
+ var bound = {
128
+ transactionId: _carries(sent.transactionId),
129
+ senderNonce: _carries(sent.senderNonce),
130
+ dataReturn: _carries(sent.dataReturn),
131
+ bodyPartIDs: Array.isArray(sent.bodyPartIDs),
132
+ };
113
133
  if (sent.transactionId != null) {
114
134
  var txControl = _findControl(body.controls, OID_TRANSACTION_ID, "Transaction Identifier");
115
135
  if (!txControl) {
@@ -132,8 +152,8 @@ function _assertBound(body, sent) {
132
152
  throw E("cmc/nonce-mismatch",
133
153
  "the request carried a Sender Nonce, so the response MUST reflect it back as a Recipient Nonce control (RFC 5272 sec. 6.6)");
134
154
  }
135
- // Constant-time and by FULL value: guard.crypto.constantTimeEqual gates the
136
- // length first, so a truncation or a prefix is an honest false rather than a
155
+ // Constant-time and by full value: guard.crypto.constantTimeEqual gates the
156
+ // length first, so a truncation or a prefix is an honest false and not a
137
157
  // match on the bytes that happen to line up.
138
158
  if (!guard.crypto.constantTimeEqual(_octets(rn, "Recipient Nonce"), Buffer.from(sent.senderNonce))) {
139
159
  throw E("cmc/nonce-mismatch", "the response Recipient Nonce does not match the Sender Nonce the request sent");
@@ -142,7 +162,7 @@ function _assertBound(body, sent) {
142
162
 
143
163
  // RFC 5272 sec. 6.4: "If the Data Return control appears in a Full PKI Request,
144
164
  // the server MUST return it as part of the PKI Response." The data is opaque to
145
- // the server, so the check is that the SAME bytes came back.
165
+ // the server, so the check is that the same bytes came back.
146
166
  if (sent.dataReturn != null) {
147
167
  var dr = _findControl(body.controls, OID_DATA_RETURN, "Data Return");
148
168
  if (!dr) {
@@ -175,14 +195,14 @@ function _assertBound(body, sent) {
175
195
  var known = Object.create(null);
176
196
  sent.bodyPartIDs.forEach(function (id) { known[String(id)] = true; });
177
197
  // 0 is reserved as the reference to the enclosing PKIData itself (sec. 3.2.1),
178
- // so a status about the request AS A WHOLE is in the set by definition.
198
+ // so a status about the request as a whole is in the set by definition.
179
199
  known["0"] = true;
180
200
  body.statuses.forEach(function (s) {
181
201
  (s.bodyList || []).forEach(function (ref) {
182
202
  var path = ref.bodyPartPath;
183
203
  // A bodyPartPath descends INTO a nested message. Checking only its head would
184
- // accept `[a part we sent, 999]` -- a status about something arbitrary, wearing a
185
- // reference that passes -- so the WHOLE path is matched against the paths the
204
+ // accept `[a part we sent, 999]`, a status about something arbitrary wearing a
205
+ // reference that passes, so the whole path is matched against the paths the
186
206
  // request actually composed. Those are retained by reading each nested message
187
207
  // back; a nested message that could not be read back contributes none, so a path
188
208
  // into it finds no match and is refused. Either way the tail is never waved through.
@@ -201,6 +221,34 @@ function _assertBound(body, sent) {
201
221
  });
202
222
  });
203
223
  }
224
+
225
+ // Every check above is conditional on the caller having retained something, so a
226
+ // caller who retained nothing runs none of them and still gets a full verdict
227
+ // (`issued`, signature verified, certificates surfaced) off a Full PKI Response
228
+ // captured from any earlier exchange with the same CA. That is the CWE-294 this
229
+ // module names as its defence, and leaving it opt-in leaves the defence off for
230
+ // whoever did not know to ask.
231
+ //
232
+ // The posture here is the one _assertAuthentic already takes for the carrier
233
+ // signature, applied to the other question: check it when the caller supplies
234
+ // what it takes, and refuse when nothing does, unless the caller names that. The
235
+ // two opt-outs stay separate because they answer separate questions. A replayed
236
+ // response is authentic, so `allowUnverified` covering it would let "I could not
237
+ // check the signature" stand in for "I did not check which exchange this
238
+ // answers".
239
+ //
240
+ // bodyPartIDs does not count toward it. Identifiers are allocated per message
241
+ // from a small range (sec. 3.2.2), so the same set recurs across requests and a
242
+ // status naming one says nothing about which request it answers.
243
+ bound.boundToRequest = bound.transactionId || bound.senderNonce || bound.dataReturn;
244
+ if (!bound.boundToRequest && sent.allowUnbound !== true) {
245
+ throw E("cmc/unbound-response",
246
+ "nothing ties this response to a request. Pass what the request retained (`transactionId`, " +
247
+ "`senderNonce`, whose echo is the replay defence of RFC 5272 sec. 6.6, or `dataReturn`) so the " +
248
+ "echo can be checked, or `allowUnbound: true` to interpret a response that could be a replay of " +
249
+ "any earlier exchange with this CA");
250
+ }
251
+ return bound;
204
252
  }
205
253
 
206
254
  // The worst outcome across every status control, with success assumed when there
@@ -234,7 +282,7 @@ function _governingStatus(statuses, outcome) {
234
282
  * @defends cmc-response-replay (CWE-294)
235
283
  * @related pki.schema.cmc.parse, pki.path.validate, pki.cms.verify
236
284
  *
237
- * Interpret a Full PKI Response into ONE terminal verdict. `response` is the DER or a PEM `CMS`
285
+ * Interpret a Full PKI Response into one terminal verdict. `response` is the DER or a PEM `CMS`
238
286
  * block; an already-parsed message is interpreted too, but only under `allowUnverified: true`,
239
287
  * because both carriers authenticate over BYTES and a parsed object the caller still owns pins
240
288
  * none. `sent` is what the client retained from its request --
@@ -245,6 +293,16 @@ function _governingStatus(statuses, outcome) {
245
293
  * with `cmc/body-part-unknown`, which the transaction and nonce cannot catch, since a server can
246
294
  * echo both correctly while reporting on something else.
247
295
  *
296
+ * Because each half is conditional, a caller who retains nothing runs none of them. A Full PKI
297
+ * Response captured from any earlier exchange with the same CA would then read as this request's
298
+ * answer, correctly signed. That case is refused as `cmc/unbound-response` unless the caller names
299
+ * it with `allowUnbound: true`, a separate opt-out from `allowUnverified` because it answers a
300
+ * separate question: a replayed response is authentic. `bodyPartIDs` alone does not satisfy it,
301
+ * since identifiers are allocated per message from a small range (RFC 5272 sec. 3.2.2) and the
302
+ * same set recurs across requests. The verdict reports which halves ran as `bound.transactionId`,
303
+ * `bound.senderNonce`, `bound.dataReturn` and `bound.bodyPartIDs`, with `boundToRequest` for the
304
+ * roll-up.
305
+ *
248
306
  * The verdict carries the response's own `cmsSequence` and `otherMsgs` raw, because a request whose
249
307
  * only arm was the other-message form has no certificate to return and RFC 5272 sec. 4.1 puts its
250
308
  * answer there instead.
@@ -269,9 +327,9 @@ function _governingStatus(statuses, outcome) {
269
327
  *
270
328
  * The other carrier sec. 3.2 permits, AuthenticatedData, is authenticated by its MAC instead: pass
271
329
  * `recipient` with the key material and the MAC is checked through `pki.cms.decrypt`, so a caller
272
- * who holds the key gets an authenticated verdict rather than the unauthenticated opt-out.
330
+ * who holds the key gets an authenticated verdict instead of the unauthenticated opt-out.
273
331
  *
274
- * Nothing is trusted here. `certificates` is the CMS certificate bag -- where RFC 5272 sec. 4.2
332
+ * Nothing is trusted here. `certificates` is the CMS certificate bag, where RFC 5272 sec. 4.2
275
333
  * puts the issued certificates -- surfaced raw for the caller to run through `pki.path.validate`,
276
334
  * and a Publish Trust Anchors control is surfaced as `publishTrustAnchors` with `trusted: false`,
277
335
  * never added to any store (RFC 5272 sec. 6.15 makes accepting one a four-part manual decision).
@@ -288,6 +346,9 @@ function _governingStatus(statuses, outcome) {
288
346
  * `signatureVerified: true`; the content it authenticates is bound to the content the verdict
289
347
  * was read from, so a MAC over other bytes cannot stand in for it.
290
348
  * - `allowUnverified` (boolean) -- interpret without verifying the carrier; sets `signatureVerified: false`.
349
+ * - `allowUnbound` (boolean) -- interpret a response nothing ties to a request; sets
350
+ * `boundToRequest: false`. Needed only when none of `transactionId`, `senderNonce` or
351
+ * `dataReturn` was retained, and what it accepts is a possible replay.
291
352
  * @example
292
353
  * var b = pki.asn1.build, oid = pki.oid;
293
354
  * var sid = b.sequence([b.sequence([b.set([b.sequence([b.oid(oid.byName("commonName")),
@@ -301,9 +362,10 @@ function _governingStatus(statuses, outcome) {
301
362
  * var encap = b.sequence([b.oid(oid.byName("id-cct-PKIResponse")), b.explicit(0, b.octetString(body))]);
302
363
  * var sd = b.sequence([b.integer(3n), b.set([]), encap, b.set([si])]);
303
364
  * var der = b.sequence([b.oid(oid.byName("signedData")), b.explicit(0, sd)]);
304
- * var v = await pki.cmc.verify(der, { allowUnverified: true });
365
+ * var v = await pki.cmc.verify(der, { allowUnverified: true, allowUnbound: true });
305
366
  * v.outcome; // "issued" -- no status control means success is assumed
306
367
  * v.signatureVerified; // false -- the opt-out was named, so nothing was checked
368
+ * v.boundToRequest; // false: nothing ties this response to a request either
307
369
  */
308
370
  function verify(response, sent) {
309
371
  // Both snapshots are taken SYNCHRONOUSLY, here, before anything is deferred.
@@ -398,7 +460,10 @@ function _snapshotSent(sent) {
398
460
  out.recipient = r;
399
461
  }
400
462
  if (Array.isArray(out.certs)) out.certs = out.certs.map(_copyAnyBytes);
463
+ // Both opt-outs normalized to a strict boolean off the ORIGINAL object, so a
464
+ // truthy-but-not-true value cannot switch a check off by accident.
401
465
  out.allowUnverified = sent.allowUnverified === true;
466
+ out.allowUnbound = sent.allowUnbound === true;
402
467
  return out;
403
468
  }
404
469
 
@@ -415,8 +480,7 @@ function _verify(response, sent) {
415
480
  }
416
481
 
417
482
  return _assertAuthentic(body, sent, response).then(function (signatureVerified) {
418
- _assertBound(body, sent);
419
- return _shape(body, sent, signatureVerified);
483
+ return _shape(body, sent, signatureVerified, _assertBound(body, sent));
420
484
  });
421
485
  }
422
486
 
@@ -555,7 +619,7 @@ function _assertAuthentic(body, sent, responseBytes) {
555
619
  });
556
620
  }
557
621
 
558
- function _shape(body, sent, signatureVerified) {
622
+ function _shape(body, sent, signatureVerified, bound) {
559
623
  var outcome = _reduceOutcome(body.statuses);
560
624
  var governing = _governingStatus(body.statuses, outcome);
561
625
  var anchors = _findControl(body.controls, OID_TRUSTED_ANCHORS, "Publish Trust Anchors");
@@ -591,6 +655,18 @@ function _shape(body, sent, signatureVerified) {
591
655
  // Whether the CARRIER's signature was checked. False only via the explicit
592
656
  // allowUnverified opt-out -- there is no path that leaves it false silently.
593
657
  signatureVerified: signatureVerified,
658
+ // Which halves of the exchange binding ran (RFC 5272 sec. 6.6 / 6.4). Each is
659
+ // conditional on the caller having retained the value, so a bare `outcome`
660
+ // cannot say whether this response answers this request or an earlier one.
661
+ // These can. `boundToRequest` is false only via the explicit allowUnbound
662
+ // opt-out; nothing leaves it false silently.
663
+ bound: {
664
+ transactionId: bound.transactionId,
665
+ senderNonce: bound.senderNonce,
666
+ dataReturn: bound.dataReturn,
667
+ bodyPartIDs: bound.bodyPartIDs,
668
+ },
669
+ boundToRequest: bound.boundToRequest,
594
670
  // Whether anything in here was TRUSTED, which is never: the certificate bag
595
671
  // and any Publish Trust Anchors control are the caller's to path-validate.
596
672
  trusted: false,
@@ -610,33 +686,33 @@ function _shape(body, sent, signatureVerified) {
610
686
  * @related pki.cmc.verify, pki.schema.cmc.parse, pki.cms.sign
611
687
  *
612
688
  * Build and sign an RFC 5272 Full PKI Request. `spec.requests` is the list of certification
613
- * requests, each naming exactly one arm -- `tcr` (a PKCS#10 CSR), `crm` (a CRMF CertReqMsg, or the
689
+ * requests, each naming exactly one arm: `tcr` (a PKCS#10 CSR), `crm` (a CRMF CertReqMsg, or the
614
690
  * CertReqMessages `pki.crmf.build` returns when it carries exactly one) or `orm`
615
691
  * (`{ type, value }`). `spec.controls` are additional controls as `{ type, value }`, and `signer`
616
692
  * is the `{ cert, key }` that signs the enclosing CMS SignedData.
617
693
  *
618
694
  * Body part identifiers are allocated automatically, unique across the whole message and never 0
619
- * (RFC 5272 sec. 3.2.2). A caller may pin one, and a clash is REFUSED rather than renumbered --
695
+ * (RFC 5272 sec. 3.2.2). A caller may pin one, and a clash is refused, never renumbered, because
620
696
  * silently moving an identifier would break any control that already referenced it. For a `crm`
621
697
  * arm the identity is the CertReqMsg's own `certReqId`, read back out of the supplied message.
622
698
  *
623
699
  * `spec.identityProof: { secret, identity? }` attaches an Identity Proof V2 control whose witness is
624
- * computed over the reqSequence bytes exactly as they are emitted (sec. 6.2.1 step 1 -- "encoded
700
+ * computed over the reqSequence bytes exactly as they are emitted (sec. 6.2.1 step 1: "encoded
625
701
  * exactly as it appears in the Full PKI Request including the sequence type and length"). Supplying
626
- * `identity` also emits the Identification control naming the shared secret, and -- per sec. 6.2.3 --
627
- * derives the MAC key from `hash(secret || identity)` rather than `hash(secret)`: the two travel
702
+ * `identity` also emits the Identification control naming the shared secret and, per sec. 6.2.3,
703
+ * derives the MAC key from `hash(secret || identity)` in place of `hash(secret)`: the two travel
628
704
  * together because the control's presence is what changes the derivation. And
629
705
  * `spec.popLink: { secret }` attaches a POP Link Witness V2 together with the POP Link Random
630
706
  * control that PL1 requires in the same request. `spec.renewal: true` marks a renewal, which MUST
631
- * carry neither Identification nor Identity Proof (sec. 3.2 (a)) -- asking for both is refused
632
- * rather than silently dropped.
707
+ * carry neither Identification nor Identity Proof (sec. 3.2 (a)), so asking for both is refused
708
+ * and never silently dropped.
633
709
  *
634
710
  * `spec.transactionId` (number|bigint), `spec.senderNonce` and `spec.dataReturn` (bytes) attach the
635
- * exchange-binding controls (RFC 5272 sec. 6.6 / 6.4) -- the same three `pki.cmc.verify` checks the
636
- * response against. They are named fields rather than something to hand-encode into
711
+ * exchange-binding controls (RFC 5272 sec. 6.6 / 6.4), the same three `pki.cmc.verify` checks the
712
+ * response against. They are named fields, not something to hand-encode into
637
713
  * `spec.controls`, because a request that quietly omits them has no replay defence and neither end
638
714
  * can tell: the verifier only enforces the halves the client says it sent. An unrecognized spec
639
- * field is refused for the same reason -- a misspelling would otherwise build and sign a message
715
+ * field is refused for the same reason: a misspelling would otherwise build and sign a message
640
716
  * that simply does not carry what was asked for.
641
717
  *
642
718
  * @opts
package/lib/cmp-build.js CHANGED
@@ -19,8 +19,8 @@
19
19
  * @card Build a CMP PKIMessage with signature or PBMAC1 protection.
20
20
  */
21
21
  //
22
- // RFC 9810 Appendix A is DEFINITIONS EXPLICIT TAGS: every context tag in the PKIMessage envelope --
23
- // each PKIHeader [0..8] optional, every PKIBody [n] arm, protection [0], extraCerts [1] -- is an EXPLICIT
22
+ // RFC 9810 Appendix A is DEFINITIONS EXPLICIT TAGS: every context tag in the PKIMessage envelope
23
+ // (each PKIHeader [0..8] optional, every PKIBody [n] arm, protection [0], extraCerts [1]) is an EXPLICIT
24
24
  // wrapper (asn1.build.explicit), the exact inverse of the RFC 4211 CRMF interior (IMPLICIT TAGS). The two
25
25
  // IMPLICIT islands are foreign imports produced wholesale by pki.crmf.build (the CertReqMessages /
26
26
  // CertTemplate interior) and embedded verbatim, never re-encoded here. Protection is computed over the
@@ -67,7 +67,7 @@ var KNOWN_BODY_KEYS = {
67
67
  ir: 1, cr: 1, kur: 1, p10cr: 1, certConf: 1, pollReq: 1, genm: 1, rr: 1, // request-side
68
68
  ip: 1, cp: 1, kup: 1, ccp: 1, krp: 1, rp: 1, genp: 1, error: 1, pollRep: 1, pkiconf: 1, // CA/responder-side
69
69
  };
70
- // The PKIBody arm -> its EXPLICIT context tag number (schema-cmp.js BODY_ARMS). rr is [11], NOT [15].
70
+ // The PKIBody arm -> its EXPLICIT context tag number (schema-cmp.js BODY_ARMS). rr is [11], not [15].
71
71
  var BODY_TAG = {
72
72
  ir: 0, cr: 2, p10cr: 4, kur: 7, rr: 11, genm: 21, certConf: 24, pollReq: 25,
73
73
  ip: 1, cp: 3, kup: 8, krp: 10, rp: 12, ccp: 14, pkiconf: 19, genp: 22, error: 23, pollRep: 26,
@@ -88,7 +88,7 @@ var PBMAC1_MIN_ITER = 1000; // RFC 8018 sec. 4.2 recommended minimum -- re
88
88
  var PBMAC1_PRF = { "SHA-256": "hmacWithSHA256", "SHA-384": "hmacWithSHA384", "SHA-512": "hmacWithSHA512" };
89
89
  var PBMAC1_MAC_OID = { "SHA-256": "hmacWithSHA256", "SHA-384": "hmacWithSHA384", "SHA-512": "hmacWithSHA512" };
90
90
  var PBMAC1_PRF_HLEN = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 }; // PBKDF2 PRF output length (one derived block)
91
- // PKIFailureInfo named bits (RFC 9810 sec. 5.2.3), position = bit index -- mirrors schema-cmp's decode list;
91
+ // PKIFailureInfo named bits (RFC 9810 sec. 5.2.3), position = bit index, mirroring schema-cmp's decode list;
92
92
  // the build -> parse round-trip cross-checks the positions against the parser.
93
93
  var FAIL_INFO_NAMES = ["badAlg", "badMessageCheck", "badRequest", "badTime", "badCertId", "badDataFormat",
94
94
  "wrongAuthority", "incorrectData", "missingTimeStamp", "badPOP", "certRevoked", "certConfirmed",
@@ -97,7 +97,7 @@ var FAIL_INFO_NAMES = ["badAlg", "badMessageCheck", "badRequest", "badTime", "ba
97
97
  "unsupportedVersion", "notAuthorized", "systemUnavail", "systemFailure", "duplicateCertReq"];
98
98
  var FAIL_INFO_INDEX = {};
99
99
  FAIL_INFO_NAMES.forEach(function (n, i) { FAIL_INFO_INDEX[n] = i; });
100
- // CertStatus.hashAlg names the hash used to compute certHash -- restrict it to hash algorithms, not any OID.
100
+ // CertStatus.hashAlg names the hash used to compute certHash, restricted to hash algorithms and not any OID.
101
101
  var CERT_CONF_HASH_ALGS = { sha1: 1, sha256: 1, sha384: 1, sha512: 1, "sha3-256": 1, "sha3-512": 1 };
102
102
 
103
103
  // ---- small shared encoders (byte-exact inverses of schema-cmp.js readers) ----
@@ -107,7 +107,7 @@ function _reqOctets(v, what) {
107
107
  return b.octetString(buf);
108
108
  }
109
109
 
110
- // PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String -- non-empty, every element UTF8String.
110
+ // PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String: non-empty, every element UTF8String.
111
111
  function _encodePkiFreeText(strings, code, what) {
112
112
  if (!Array.isArray(strings) || !strings.length) throw _err(code, what + " must be a non-empty array of strings");
113
113
  return b.sequence(strings.map(function (s) {
@@ -193,7 +193,7 @@ function _encodeHeader(headerSpec, protectionAlgDer, pvno) {
193
193
  // or a bigint (a large value beyond 2^53), reject a non-integer / other type. Returns a BigInt for b.integer.
194
194
  function _reqIdInt(v, code, what) { return guard.range.authoredInteger(v, _err, code, what); }
195
195
 
196
- // PKIFailureInfo ::= BIT STRING (named bits, RFC 9810 sec. 5.2.3) -- a minimal NamedBitList from bit names.
196
+ // PKIFailureInfo ::= BIT STRING (named bits, RFC 9810 sec. 5.2.3): a minimal NamedBitList from bit names.
197
197
  function _encodeFailInfo(names, code) {
198
198
  if (!Array.isArray(names)) throw _err(code, "statusInfo.failInfo must be an array of PKIFailureInfo bit names");
199
199
  return b.namedBitString(names.map(function (n) {
@@ -288,7 +288,7 @@ function _encodeCertOrEncCert(coec, state) {
288
288
  }
289
289
  if (coec.encryptedCert != null) {
290
290
  // encryptedCert [1] wraps an EncryptedKey (pre-encoded DER hatch in v1). Like the privateKey, only the
291
- // EnvelopedData [0] form is cmp2021 -- the deprecated EncryptedValue (a universal SEQUENCE) is cmp2000.
291
+ // EnvelopedData [0] form is cmp2021; the deprecated EncryptedValue (a universal SEQUENCE) is cmp2000.
292
292
  var ec = _b.reqDer(coec.encryptedCert, "certifiedKeyPair.encryptedCert (a pre-encoded EncryptedKey DER)");
293
293
  var ecNode;
294
294
  try { ecNode = asn1.decode(ec); } catch (e) { throw _err("cmp/bad-cert-response", "certifiedKeyPair.encryptedCert is not valid DER", e); }
@@ -475,7 +475,7 @@ function _encodeBody(bodySpec, key, opts) {
475
475
 
476
476
 
477
477
  // Resolve the protection selector to { protectionAlgDer, computeBits(protectedPartDer)->Promise<Buffer> },
478
- // senderSpki, senderScheme } BEFORE the header is built (protectionAlg is opts-derived, not header-derived).
478
+ // senderSpki, senderScheme } before the header is built (protectionAlg is opts-derived, not header-derived).
479
479
  function _resolveProtection(opts) {
480
480
  var hasSig = opts.key != null || opts.cert != null;
481
481
  var hasMac = opts.mac != null;
@@ -517,11 +517,11 @@ function _resolveProtection(opts) {
517
517
  if (!PBMAC1_PRF[prf]) throw _err("cmp/bad-input", "opts.mac.prf must be SHA-256 / SHA-384 / SHA-512");
518
518
  var iterationCount = m.iterationCount != null ? m.iterationCount : PBMAC1_DEFAULT_ITER;
519
519
  if (typeof iterationCount !== "number" || !Number.isInteger(iterationCount) || iterationCount < PBMAC1_MIN_ITER) throw _err("cmp/bad-input", "opts.mac.iterationCount must be at least " + PBMAC1_MIN_ITER + " (RFC 8018 sec. 4.2)");
520
- // Bound the PBKDF2 work factors BEFORE deriving -- a huge iterationCount / keyLength is self-inflicted work.
520
+ // Bound the PBKDF2 work factors before deriving: a huge iterationCount or keyLength is self-inflicted work.
521
521
  if (iterationCount > constants.LIMITS.PBKDF2_MAX_ITERATIONS) throw _err("cmp/bad-input", "opts.mac.iterationCount exceeds the PBKDF2 work-factor cap " + constants.LIMITS.PBKDF2_MAX_ITERATIONS);
522
522
  var keyLength = m.keyLength != null ? m.keyLength : PBMAC1_DEFAULT_KEYLEN;
523
523
  // RFC 9579 sec. 9 floor (>= 20 bytes): produce only messages the verifier (pki.cmp.verify, same floor) can
524
- // accept -- a shorter derived key is refused at production time rather than emitted as an unverifiable message.
524
+ // accept, so a shorter derived key is refused at production time and never emitted as an unverifiable message.
525
525
  if (typeof keyLength !== "number" || !Number.isInteger(keyLength) || keyLength < PBMAC1_MIN_KEYLEN) throw _err("cmp/bad-input", "opts.mac.keyLength must be an integer >= " + PBMAC1_MIN_KEYLEN + " bytes (RFC 9579 sec. 9)");
526
526
  if (keyLength > PBMAC1_MAX_KEYLEN) throw _err("cmp/bad-input", "opts.mac.keyLength exceeds the cap " + PBMAC1_MAX_KEYLEN + " bytes");
527
527
  // Bound the COMBINED work: a keyLength spanning multiple PRF blocks costs iterationCount HMACs per block, so
@@ -530,7 +530,7 @@ function _resolveProtection(opts) {
530
530
  var blocks = Math.ceil(keyLength / PBMAC1_PRF_HLEN[prf]);
531
531
  if (iterationCount * blocks > constants.LIMITS.PBKDF2_MAX_ITERATIONS) throw _err("cmp/bad-input", "opts.mac combined work (iterationCount " + iterationCount + " x " + blocks + " derived blocks) exceeds the PBKDF2 work-factor cap " + constants.LIMITS.PBKDF2_MAX_ITERATIONS);
532
532
  var salt = m.salt != null ? _b.reqDer(m.salt, "opts.mac.salt") : Buffer.from(webcrypto.webcrypto.getRandomValues(new Uint8Array(PBMAC1_DEFAULT_SALT_BYTES)));
533
- // RFC 8018 sec. 4.1 (64-bit) floor: an empty / short salt loses precomputation resistance -- produce only
533
+ // RFC 8018 sec. 4.1 (64-bit) floor: an empty or short salt loses precomputation resistance, so produce only
534
534
  // messages pki.cmp.verify (same floor) accepts, refusing a below-minimum salt at construction time.
535
535
  if (salt.length < PBMAC1_MIN_SALT) throw _err("cmp/bad-input", "opts.mac.salt must be at least " + PBMAC1_MIN_SALT + " octets (RFC 8018 sec. 4.1)");
536
536
  if (salt.length > constants.LIMITS.PBKDF2_MAX_SALT) throw _err("cmp/bad-input", "opts.mac.salt exceeds " + constants.LIMITS.PBKDF2_MAX_SALT + " bytes");
@@ -550,7 +550,7 @@ function _resolveProtection(opts) {
550
550
  // ---- orchestrator ----
551
551
 
552
552
  function build(message, opts) {
553
- // Every caller-owned argument copied at entry and released when the call settles -- see the note
553
+ // Every caller-owned argument copied at entry and released when the call settles; see the note
554
554
  // on the same call in x509-sign. `opts.mac.secret` is why the copy has to be deep: it sits a
555
555
  // level below the options object and is read by the PBMAC1 derivation after the first turn.
556
556
  return guard.bytes.fixedCall(CmpError, "cmp/bad-input", [
@@ -626,23 +626,23 @@ function _collectExtraCerts(opts, protCertDer) {
626
626
  * single-key object naming the arm. Request-side: `{ ir }` / `{ cr }` / `{ kur }` (a `CertReqMessages` spec
627
627
  * delegated to `pki.crmf.build`; the proof-of-possession key is `key` on the arm spec), `{ p10cr }` (a PKCS#10
628
628
  * CertificationRequest DER), `{ certConf }`, `{ pollReq }`, `{ genm }`, `{ rr }`. CA/responder-side: `{ ip }` /
629
- * `{ cp }` / `{ kup }` / `{ ccp }` (a `CertRepMessage` -- `caPubs` + `response` of `CertResponse` each with a
629
+ * `{ cp }` / `{ kup }` / `{ ccp }` (a `CertRepMessage`: `caPubs` plus `response` of `CertResponse` each with a
630
630
  * `PKIStatusInfo` and, under a granting status, a `certifiedKeyPair`), `{ rp }` (`RevRepContent`), `{ genp }`,
631
631
  * `{ error }` (`ErrorMsgContent`), `{ pollRep }`, `{ krp }` (`KeyRecRepContent`), `{ pkiconf }` (NULL). The
632
- * message is protected: `opts` carries EXACTLY
633
- * ONE of `{ key, cert }` (a signature under the sender key over the message -- any registry algorithm, RSA
632
+ * message is protected: `opts` carries exactly
633
+ * one of `{ key, cert }` (a signature under the sender key over the message, using any registry algorithm: RSA
634
634
  * / ECDSA / EdDSA / ML-DSA / SLH-DSA / composite, resolved from the certificate) or `{ mac }` (a PBMAC1
635
635
  * shared-secret MAC). The protection is computed over the exact DER of the virtual
636
636
  * `ProtectedPart ::= SEQUENCE { header, body }` and self-verified before the message is returned. The
637
637
  * emitted PKIMessage round-trips byte-identically through `pki.schema.cmp.parse`.
638
638
  *
639
639
  * @opts
640
- * - `key` (Buffer|CryptoKey) + `cert` (Buffer) -- signature protection under the sender key; `cert` is
640
+ * - `key` (Buffer|CryptoKey) + `cert` (Buffer): signature protection under the sender key; `cert` is
641
641
  * the signer certificate (its SPKI resolves the algorithm) and is placed in `extraCerts`.
642
- * - `mac` ({ secret, salt?, iterationCount?, prf?, keyLength? }) -- PBMAC1 protection from a shared secret.
643
- * - `extraCerts` (array of Buffer) -- additional certificates to carry in `extraCerts [1]`.
644
- * - `pem` (boolean|string) -- return a PEM `CMP` block instead of DER.
645
- * - `pss` (boolean) / `digestAlgorithm` (string) -- signature-protection algorithm options.
642
+ * - `mac` ({ secret, salt?, iterationCount?, prf?, keyLength? }): PBMAC1 protection from a shared secret.
643
+ * - `extraCerts` (array of Buffer): additional certificates to carry in `extraCerts [1]`.
644
+ * - `pem` (boolean|string): return a PEM `CMP` block instead of DER.
645
+ * - `pss` (boolean) / `digestAlgorithm` (string): signature-protection algorithm options.
646
646
  *
647
647
  * @example
648
648
  * var pair = await pki.key.generate("Ed25519");
@@ -839,17 +839,17 @@ function wellKnownUrl(base, opts) {
839
839
  * @related pki.cmp.build, pki.cmp.wellKnownUrl, pki.schema.cmp.parse, pki.transport.https
840
840
  *
841
841
  * POST a DER `PKIMessage` to a CMP endpoint and return the parsed response `PKIMessage`, over the shared
842
- * `pki.transport`. RFC 9811 transfers every CMP exchange identically -- one HTTP POST of a DER PKIMessage,
843
- * one response PKIMessage -- so a single stateless verb carries `ir` / `cr` / `kur` / `p10cr` / `certConf`
842
+ * `pki.transport`. RFC 9811 transfers every CMP exchange identically, as one HTTP POST of a DER PKIMessage
843
+ * and one response PKIMessage, so a single stateless verb carries `ir` / `cr` / `kur` / `p10cr` / `certConf`
844
844
  * / `pollReq` / `rr` / `genm` and their responses; the caller builds and protects the message upstream with
845
845
  * `pki.cmp.build` and hands the finished bytes here. `message` is a DER `Buffer`/`Uint8Array` or a PEM `CMP`
846
- * string, sent VERBATIM (the message-layer protection covers these exact bytes -- they are never re-encoded).
846
+ * string, sent verbatim (the message-layer protection covers these exact bytes; they are never re-encoded).
847
847
  * The response is classified fail-closed: HTTP 200 carrying an `application/pkixcmp` body is parsed and
848
848
  * resolved; another 2xx is `cmp/unexpected-status` (RFC 9811 requires 200); a 3xx is `cmp/redirect-not-followed`
849
849
  * (never auto-followed, sec. 3.1/5); a 4xx/5xx carrying a well-formed CMP `error` PKIMessage FORWARDS that
850
850
  * integrity-protected verdict (sec. 1.2/3.1) with the HTTP status surfaced as data, while a 4xx/5xx that is
851
851
  * not a CMP `error` message (no body, an undecodable body, or a non-error arm) is `cmp/http-error`.
852
- * Protection is SURFACED, not verified -- the client confers no trust; the
852
+ * Protection is surfaced, not verified: the client confers no trust, and the
853
853
  * caller (or a future `pki.cmp.verify`) checks the response protection using the raw `headerBytes`/`bodyBytes`.
854
854
  * By default the transport is https-only and requires an explicit trust anchor; there is no client scheme
855
855
  * gate, so an operator who injects an http-capable transport reaches the RFC-9811-permitted plain-HTTP path.