@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
@@ -7,14 +7,14 @@
7
7
  // surface (build / transfer / wellKnownUrl / verify) so the whole pki.cmp namespace wires through here
8
8
  // (index.js requires this file as `cmp`).
9
9
  //
10
- // pki.cmp.session -- the STATEFUL CMP enrollment-transaction orchestrator (the pki.acme.client analogue).
10
+ // pki.cmp.session -- the stateful CMP enrollment-transaction orchestrator (the pki.acme.client analogue).
11
11
  // It composes the shipped message layer (build / transfer / verify) into a single enroll(request): mint a
12
- // stable transactionID, build + protect + transfer a request, VERIFY every response's protection BEFORE
12
+ // stable transactionID, build + protect + transfer a request, verify every response's protection before
13
13
  // reading its body, chain the nonces (recipNonce echoes the peer's senderNonce, a fresh senderNonce per
14
14
  // request) and the transactionID (RFC 9810 sec. 5.1.1 anti-replay / anti-interleave), interpret the
15
15
  // CertResponse PKIStatus (grant -> extract the cert, waiting -> a bounded pollReq/pollRep loop, rejection
16
16
  // -> a terminal verdict), and confirm (certConf -> pkiConf, unless implicitConfirm was granted). The
17
- // single invariant is transfer -> verify (fail-closed) -> ONLY THEN read the body off the verdict.
17
+ // single invariant is transfer -> verify (fail-closed) -> only then read the body off the verdict.
18
18
 
19
19
  var cmp = require("./cmp-verify"); // re-exports build / transfer / wellKnownUrl / verify (the whole message layer)
20
20
  var asn1 = require("./asn1-der");
@@ -35,12 +35,12 @@ function _err(code, message, cause) { return new CmpError(code, message, cause);
35
35
  var OID_IMPLICIT_CONFIRM = oid.byName("implicitConfirm"); // classify a granted implicitConfirm by its IMMUTABLE OID
36
36
 
37
37
  // The certification-path build/validate engine, injected by path-validate (the crl/ocsp/cmp-verify seam) so
38
- // the session can VALIDATE the issued leaf certificate's signature + chain -- x509.parse is structural only.
38
+ // the session can validate the issued leaf certificate's signature + chain; x509.parse is structural only.
39
39
  var _engine = null;
40
40
  function setEngine(engine) { _engine = engine; }
41
41
 
42
- // The signature algorithms whose OID conveys NO message hash -- EdDSA (the hash is the scheme) and the FIPS
43
- // PQC signatures (ML-DSA / SLH-DSA, internally hashed). ONLY these may take the certConf SHA-256 + explicit
42
+ // The signature algorithms whose OID conveys no message hash: EdDSA (the hash is the scheme) and the FIPS
43
+ // PQC signatures (ML-DSA / SLH-DSA, internally hashed). Only these may take the certConf SHA-256 + explicit
44
44
  // hashAlg fallback (RFC 9810 sec. 5.3.18); any other indeterminate/non-signature AlgorithmIdentifier (e.g.
45
45
  // rsaEncryption, an ML-KEM OID) is refused. Resolved to a dotted-OID set at load (never a runtime lookup).
46
46
  var HASHLESS_SIG_OIDS = {};
@@ -58,8 +58,8 @@ var SIG_OID_TO_HASH = {};
58
58
  });
59
59
  var OID_RSASSA_PSS = oid.byName("rsassaPss");
60
60
  var OID_RSA_ENCRYPTION = oid.byName("rsaEncryption");
61
- // Message-digest OID -> the WebCrypto digest name, for the RSASSA-PSS hashAlgorithm parameter -- dispatched by
62
- // the IMMUTABLE OID (never oid.name, which pki.oid.register can rename). Resolved to a dotted-OID map at load.
61
+ // Message-digest OID -> the WebCrypto digest name, for the RSASSA-PSS hashAlgorithm parameter, dispatched by
62
+ // the immutable OID (never oid.name, which pki.oid.register can rename). Resolved to a dotted-OID map at load.
63
63
  var HASH_OID_TO_DIGEST = {};
64
64
  [["sha256", "SHA-256"], ["sha384", "SHA-384"], ["sha512", "SHA-512"]].forEach(function (row) { var o = oid.byName(row[0]); if (o) HASH_OID_TO_DIGEST[o] = row[1]; });
65
65
  // A composite signature's declared prehash (COMPOSITE_ALGS[oid].ph) -> the certConf hashAlg field value.
@@ -83,7 +83,7 @@ var DEFAULT_CERT_REQ_ID = 0; // the CRMF single-request certReqId when the cal
83
83
  var P10CR_CERT_REQ_ID = -1; // a PKCS#10 request has no CRMF id; a conforming cp identifies it with the -1 sentinel (RFC 9483)
84
84
  var ENROLL_ARMS = { ir: 1, cr: 1, kur: 1, p10cr: 1 }; // the initial request arms enroll() accepts
85
85
  // The response body arm each enrollment request is answered by (RFC 9810 sec. 5.3.2/5.3.4): ir->ip, cr/p10cr
86
- // ->cp, kur->kup. A response of a DIFFERENT cert-response arm than the request is misrouted -- never confirmed.
86
+ // ->cp, kur->kup. A response on a different cert-response arm than the request is misrouted, never confirmed.
87
87
  var RESPONSE_ARM = { ir: "ip", cr: "cp", kur: "kup", p10cr: "cp" };
88
88
 
89
89
  // PKIStatus codes (RFC 9810 sec. 5.2.3): 0 accepted, 1 grantedWithMods, 2 rejection, 3 waiting.
@@ -100,18 +100,18 @@ function _certReqIdEq(a, b) { return a != null && b != null && BigInt(a) === Big
100
100
  function _asCertList(v) { return v == null ? [] : (Array.isArray(v) ? v.slice() : [v]); }
101
101
 
102
102
  // Append session-supplied pool material (a CA-delivered caPubs, a cached protection-signer chain part-derived
103
- // from the response's UNSIGNED extraCerts) to a `base` pool (the caller's OWN intermediates) up to the path
103
+ // from the response's unsigned extraCerts) to a `base` pool (the caller's own intermediates) up to the path
104
104
  // builder's candidate ceiling, deduped against each other AND the base's Buffer entries so a copy of an existing
105
105
  // candidate never spends a slot. The base is never truncated (if it alone exceeds the ceiling, that is a genuine
106
106
  // config error path.build reports). So neither a legitimate caPubs nor a meddler's extraCerts flood can push a
107
- // valid caller pool over the ceiling and fail an otherwise-valid grant. Used for BOTH the response signer path
107
+ // valid caller pool over the ceiling and fail an otherwise-valid grant. Used for both the response signer path
108
108
  // (cmp.verify) and the issued-leaf path (path.build), the two places session material joins a caller pool.
109
109
  function _boundedPool(base, added) {
110
- // Dedup the BASE (the priority pool) FIRST: duplicate copies would otherwise inflate its length, drive the
111
- // remaining room to zero, and evict genuinely needed added material even though the DISTINCT candidate count is
110
+ // Dedup the base (the priority pool) first: duplicate copies would otherwise inflate its length, drive the
111
+ // remaining room to zero, and evict genuinely needed added material even though the distinct candidate count is
112
112
  // small (path.build dedups internally, so dropping exact duplicates is behavior-preserving). An uncanonicalizable
113
113
  // entry is kept as-is (path.build judges it) and cannot dedup an added cert. The base holds the priority material
114
- // (a signed response's OWN delivered issuers + the cached chain, or the grant's caPubs) so it is never truncated;
114
+ // (a signed response's own delivered issuers + the cached chain, or the grant's caPubs) so it is never truncated;
115
115
  // the added caller pool fills the remaining room to the candidate ceiling.
116
116
  var ceiling = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES;
117
117
  var seen = Object.create(null), distinctBase = [];
@@ -122,21 +122,21 @@ function _boundedPool(base, added) {
122
122
  added.forEach(function (c) { var k = _certIdentity(c); if (k != null && seen[k]) return; if (k != null) seen[k] = 1; useful.push(c); });
123
123
  return distinctBase.concat(useful.slice(0, room));
124
124
  }
125
- // A canonical byte identity for a certificate in ANY form path.build accepts -- a DER Buffer, a PEM string, or
126
- // an already-parsed certificate object -- so a caller intermediate supplied as PEM or parsed still dedups against
125
+ // A canonical byte identity for a certificate in any form path.build accepts (a DER Buffer, a PEM string, or
126
+ // an already-parsed certificate object), so a caller intermediate supplied as PEM or parsed still dedups against
127
127
  // a byte-identical DER caPubs / cached certificate and does not spend a scarce candidate slot on a copy. The
128
- // identity is the tbsCertificate bytes PLUS the signature (mirroring cmp.verify's _certKey): a meddler controls
129
- // the unsigned extraCerts ordering, so a corrupted-signature copy sharing a valid issuer's TBS must NOT collapse
130
- // onto it and evict the valid one. Returns null when the identity cannot be derived; a NON-deduped entry is safe
128
+ // identity is the tbsCertificate bytes plus the signature (mirroring cmp.verify's _certKey): a meddler controls
129
+ // the unsigned extraCerts ordering, so a corrupted-signature copy sharing a valid issuer's TBS must not collapse
130
+ // onto it and evict the valid one. Returns null when the identity cannot be derived; a non-deduped entry is safe
131
131
  // (a redundant slot), a wrong merge (dropping a distinct or the only valid certificate) is not.
132
- // The identity comes from the SAME derivation every other certificate door uses: the bytes the
133
- // parser recorded, never the fields of the object handed in. This is a dedupe rather than a verdict,
134
- // and no collision attack on it is apparent -- carrying another certificate's exact tbsBytes AND
132
+ // The identity comes from the same derivation every other certificate door uses: the bytes the
133
+ // parser recorded, never the fields of the object handed in. This is a dedupe, not a verdict,
134
+ // and no collision attack on it is apparent, since carrying another certificate's exact tbsBytes and
135
135
  // signature means being that certificate. But "no attack is apparent" is the reasoning that put a
136
136
  // completeness-only door on nine deciding boundaries, so it is not the reasoning this uses: one
137
137
  // derivation for certificates, everywhere, and the exceptions have to argue for themselves.
138
138
  //
139
- // Failure returns null rather than throwing, which is this function's own contract and is why the
139
+ // Failure returns null instead of throwing, which is this function's own contract and is why the
140
140
  // door is wrapped: an underivable identity is a redundant pool slot, while a wrong merge (dropping
141
141
  // a distinct or the only valid certificate) is not. So a rebuilt entry simply does not dedupe.
142
142
  function _certIdentity(cert) {
@@ -150,9 +150,9 @@ function _certIdentity(cert) {
150
150
  }
151
151
 
152
152
  // A canonical identity for a SubjectPublicKeyInfo: the algorithm OID + the AlgorithmIdentifier parameters +
153
- // the raw subjectPublicKey BIT STRING. The parameters ARE part of the key identity (an EC curve OID, an
154
- // RSASSA-PSS constraint set) and are kept -- EXCEPT for rsaEncryption, whose parameters are NULL or omitted,
155
- // both naming the same key. So the issued-cert key-match compares KEYS (with their constraints), not byte
153
+ // the raw subjectPublicKey BIT STRING. The parameters are part of the key identity (an EC curve OID, an
154
+ // RSASSA-PSS constraint set) and are kept, with one exception: rsaEncryption, whose parameters are NULL or
155
+ // omitted, both naming the same key. So the issued-cert key-match compares keys (with their constraints), not byte
156
156
  // encodings, and neither rejects an equivalent rsaEncryption re-encoding nor accepts a constraint-changed key.
157
157
  function _spkiKeyIdentity(spkiDer) {
158
158
  var node = asn1.decode(spkiDer); // SEQUENCE { AlgorithmIdentifier { OID, params? }, BIT STRING }
@@ -161,10 +161,10 @@ function _spkiKeyIdentity(spkiDer) {
161
161
  var pn = algId.children[1];
162
162
  var params;
163
163
  if (algOid === OID_RSA_ENCRYPTION) {
164
- // rsaEncryption parameters MUST be absent or a NULL (RFC 3279 sec. 2.3.1). Normalize ONLY those two
165
- // equivalent forms to "" so a NULL-vs-omitted re-encoding matches. ANY other value (a malformed empty OCTET
166
- // STRING the parser/importer may tolerate, or a changed parameter) keeps its bytes -- so a parameter-changed
167
- // certificate a stricter consumer rejects gets a DISTINCT identity and the key-match refuses it.
164
+ // rsaEncryption parameters MUST be absent or a NULL (RFC 3279 sec. 2.3.1). Normalize only those two
165
+ // equivalent forms to "" so a NULL-vs-omitted re-encoding matches. Any other value (a malformed empty OCTET
166
+ // STRING the parser/importer may tolerate, or a changed parameter) keeps its bytes, so a parameter-changed
167
+ // certificate a stricter consumer rejects gets a distinct identity and the key-match refuses it.
168
168
  params = (pn == null || _isDerNull(pn)) ? "" : pn.bytes.toString("latin1");
169
169
  } else {
170
170
  // Every other algorithm's parameters ARE part of the key identity (an EC curve OID, an RSASSA-PSS constraint set).
@@ -176,28 +176,28 @@ function _spkiKeyIdentity(spkiDer) {
176
176
  // as equivalent to an absent parameter.
177
177
  function _isDerNull(pn) { return pn.tagClass === "universal" && pn.tagNumber === 5 && pn.content.length === 0; }
178
178
 
179
- // The BOUNDED, distinct, parseable extraCerts of an already-verified response, cached so a later leg that
180
- // omits extraCerts can rebuild the signer path -- MIRRORING cmp.verify's own extraCerts bounding: dedup, drop
179
+ // The bounded, distinct, parseable extraCerts of an already-verified response, cached so a later leg that
180
+ // omits extraCerts can rebuild the signer path, mirroring cmp.verify's own extraCerts bounding: dedup, drop
181
181
  // any non-X.509 entry, cap at MAX_EXTRA_CERTS, and stop after MAX_EXTRA_SCAN entries. So a meddler appending a
182
182
  // flood of unsigned certs cannot make an otherwise-valid enrollment fail when the cache reaches path.build.
183
183
  var MAX_EXTRA_CERTS = 32, MAX_EXTRA_SCAN = 256;
184
- // The caller intermediates pool is capped BELOW the path-builder candidate ceiling, reserving room for the CA's
185
- // OWN authenticated material (a response's extraCerts + the cached signer chain, or the grant's caPubs + cache --
186
- // each <= MAX_EXTRA_CERTS). So the authenticated certs AND the whole caller pool ALWAYS fit in ONE candidate pool:
187
- // no priority attempt has to choose between them, and a chain assembled from BOTH sources validates in one build.
188
- var CAPUBS_MAX = 2 * MAX_EXTRA_CERTS; // the leaf's authenticated issuer material (caPubs) -- bounded so caPubs + cached chain + the caller pool fit under the ceiling
189
- // The caller intermediates pool is capped below the path-builder ceiling by the room the session's OWN material
190
- // can occupy. A SIGNATURE session reserves CAPUBS_MAX (a grant's caPubs, leaf validation) PLUS MAX_EXTRA_CERTS (the
191
- // response's extraCerts + cached SIGNER chain -- the signer-path pool). A MAC session authenticates the response by
192
- // the shared secret and NEVER adds a signer chain (its cache gate is isSig), so it reserves ONLY CAPUBS_MAX -- its
184
+ // The caller intermediates pool is capped below the path-builder candidate ceiling, reserving room for the CA's
185
+ // own authenticated material (a response's extraCerts + the cached signer chain, or the grant's caPubs + cache,
186
+ // each <= MAX_EXTRA_CERTS). So the authenticated certs and the whole caller pool always fit in one candidate pool:
187
+ // no priority attempt has to choose between them, and a chain assembled from both sources validates in one build.
188
+ var CAPUBS_MAX = 2 * MAX_EXTRA_CERTS; // the leaf's authenticated issuer material (caPubs), bounded so caPubs + cached chain + the caller pool fit under the ceiling
189
+ // The caller intermediates pool is capped below the path-builder ceiling by the room the session's own material
190
+ // can occupy. A signature session reserves CAPUBS_MAX (a grant's caPubs, leaf validation) plus MAX_EXTRA_CERTS (the
191
+ // response's extraCerts + cached signer chain, the signer-path pool). A MAC session authenticates the response by
192
+ // the shared secret and never adds a signer chain (its cache gate is isSig), so it reserves CAPUBS_MAX alone; its
193
193
  // sole pool is _validateLeaf's caPubs + caller. So every candidate pool the session builds holds the whole caller
194
- // pool AND all authenticated material at once -- one build, no priority choice, no retry.
194
+ // pool and all authenticated material at once: one build, no priority choice, no retry.
195
195
  var SESSION_MAX_INTERMEDIATES = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX - MAX_EXTRA_CERTS; // signature
196
196
  var SESSION_MAX_INTERMEDIATES_MAC = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX; // MAC (no signer chain reserved)
197
197
  // A verify verdict whose failure a DIFFERENT candidate pool or the cached/prebound signer might still recover:
198
198
  // extraCerts is outside the protected part, so a signer that did not resolve, a wrong-key/wrong-subject decoy
199
199
  // selected first, or an untrusted chain can all be retried. A transaction-integrity failure (transactionID /
200
- // recipNonce mismatch) is NOT here -- it is decoy-independent and re-running would only mask the real desync.
200
+ // recipNonce mismatch) is deliberately absent: it is decoy-independent, and re-running would only mask the desync.
201
201
  function _isRecoverableVerify(code) {
202
202
  return code === "cmp/signer-cert-not-found" || code === "cmp/protection-failed" ||
203
203
  code === "cmp/sender-mismatch" || code === "cmp/untrusted-signer";
@@ -219,8 +219,8 @@ function _responseExtraCerts(responseBytes) {
219
219
  return out;
220
220
  }
221
221
 
222
- // Normalize a caller-supplied CRMF certReqId to the value the session echoes + matches, the SAME way
223
- // crmf-sign._certReqId encodes it: a number or bigint is kept; a STRING (decimal or 0x-hex) is parsed via
222
+ // Normalize a caller-supplied CRMF certReqId to the value the session echoes + matches, the same way
223
+ // crmf-sign._certReqId encodes it: a number or bigint is kept; a string (decimal or 0x-hex) is parsed via
224
224
  // BigInt so a supported form ("5" / "0x5") is not silently replaced by the default; anything else -> `dflt`.
225
225
  function _normalizeCertReqId(cid, dflt) {
226
226
  if (typeof cid === "bigint" || typeof cid === "number") return cid;
@@ -251,11 +251,11 @@ function _pssDigest(paramsBytes) {
251
251
  return null;
252
252
  }
253
253
 
254
- // The certConf certHash algorithm (RFC 9810 sec. 5.3.18): use the SAME hash the certificate signature uses.
255
- // If the signatureAlgorithm OID conveys the hash (sha256WithRSAEncryption / ecdsaWithSHA384 / ...) use THAT
256
- // hash and OMIT hashAlg. For id-RSASSA-PSS the hash is carried in the params, so decode it and likewise omit
254
+ // The certConf certHash algorithm (RFC 9810 sec. 5.3.18): use the same hash the certificate signature uses.
255
+ // If the signatureAlgorithm OID conveys the hash (sha256WithRSAEncryption / ecdsaWithSHA384 / ...) use that
256
+ // hash and omit hashAlg. For id-RSASSA-PSS the hash is carried in the params, so decode it and likewise omit
257
257
  // hashAlg. Only when the hash is genuinely not conveyed (Ed25519 / Ed448; ML-DSA / SLH-DSA) is SHA-256 used
258
- // and DECLARED in the explicit hashAlg field so the CA recomputes certHash under the same stated hash.
258
+ // and declared in the explicit hashAlg field so the CA recomputes certHash under the same stated hash.
259
259
  function _certConfHash(certDer) {
260
260
  var sa;
261
261
  try { sa = x509.parse(certDer).signatureAlgorithm; }
@@ -265,10 +265,10 @@ function _certConfHash(certDer) {
265
265
  // hashAlg; id-RSASSA-PSS -> the hash from its parameters, no hashAlg.
266
266
  if (HASHLESS_SIG_OIDS[sa.oid]) return { digest: "SHA-256", hashAlg: "sha256" };
267
267
  // A composite signature (draft-ietf-lamps-pq-composite-sigs): compute certHash under the composite's own
268
- // declared PREHASH digest (COMPOSITE_ALGS[oid].ph) and DECLARE it in the explicit hashAlg. If the prehash is
269
- // not a certConf-representable hash (SHAKE256, which the CMP CertStatus hashAlg cannot name), FAIL closed --
268
+ // declared prehash digest (COMPOSITE_ALGS[oid].ph) and declare it in the explicit hashAlg. If the prehash is
269
+ // not a certConf-representable hash (SHAKE256, which the CMP CertStatus hashAlg cannot name), fail closed:
270
270
  // substituting SHA-256 would send a certHash under a hash that contradicts the signature's declared prehash,
271
- // which a conforming CA rejects. Refuse rather than confirm under a false algorithm (RFC 9810 sec. 5.3.18).
271
+ // which a conforming CA rejects. Refuse, never confirm under a false algorithm (RFC 9810 sec. 5.3.18).
272
272
  var comp = compositeSig.COMPOSITE_ALGS[sa.oid];
273
273
  if (comp) {
274
274
  var ha = COMPOSITE_PH_HASHALG[comp.ph];
@@ -281,8 +281,8 @@ function _certConfHash(certDer) {
281
281
  if (pd) return { digest: pd, hashAlg: null };
282
282
  throw _err("cmp/bad-cert-response", "the issued RSASSA-PSS certificate's hash cannot be resolved from its parameters (RFC 4055); the certConf hash is indeterminate");
283
283
  }
284
- // Any other AlgorithmIdentifier -- an unregistered OID, or a registered NON-signature / indeterminate one
285
- // (rsaEncryption, an ML-KEM OID, ...): the required certConf hash cannot be determined. Do NOT guess
284
+ // Any other AlgorithmIdentifier (an unregistered OID, or a registered non-signature / indeterminate one
285
+ // such as rsaEncryption or an ML-KEM OID): the required certConf hash cannot be determined. Do not guess
286
286
  // SHA-256 (a wrong certHash the CA rejects); fail the transaction closed before confirming.
287
287
  throw _err("cmp/bad-cert-response", "the issued certificate's signature algorithm does not determine a certConf hash (an unrecognized or non-signature algorithm); the transaction is refused (RFC 9810 sec. 5.3.18)");
288
288
  }
@@ -295,46 +295,46 @@ function _certConfHash(certDer) {
295
295
  * @spec RFC 9810, RFC 9811, RFC 9483
296
296
  * @related pki.cmp.build, pki.cmp.verify, pki.cmp.transfer
297
297
  *
298
- * A stateful RFC 9810 CMP enrollment-transaction orchestrator -- the `pki.acme.client` analogue. It drives
298
+ * A stateful RFC 9810 CMP enrollment-transaction orchestrator, the `pki.acme.client` analogue. It drives
299
299
  * an enrollment (`ir` / `cr` / `kur` / `p10cr`) end to end over the shared `pki.transport` (inject
300
300
  * `opts.transport`, else a fail-closed `pki.transport.https`), composing the shipped message layer
301
301
  * (`build` / `transfer` / `verify`). It mints a stable 128-bit `transactionID`, and on every request a
302
- * FRESH `senderNonce`, echoing the peer's last `senderNonce` back as `recipNonce` (RFC 9810 sec. 5.1.1
303
- * anti-replay / anti-interleave). The load-bearing invariant: every response is protection-VERIFIED and
304
- * nonce-bound to this exchange BEFORE its body is read -- so a meddler who flips an HTTP response cannot
302
+ * fresh `senderNonce`, echoing the peer's last `senderNonce` back as `recipNonce` (RFC 9810 sec. 5.1.1
303
+ * anti-replay / anti-interleave). The load-bearing invariant: every response is protection-verified and
304
+ * nonce-bound to this exchange before its body is read, so a meddler who flips an HTTP response cannot
305
305
  * forge a granted status or a poison `checkAfter`. A `waiting` status drives a bounded `pollReq`/`pollRep`
306
306
  * loop (an injectable sleeper, capped by `maxPolls` + `maxTotalWait`); a grant extracts the issued cert and
307
307
  * confirms it (`certConf` -> `pkiConf`, unless an `implicitConfirm` was granted). A verified `rejection` /
308
- * `error` or a poll-budget timeout is a terminal typed VERDICT the caller reads (`outcome`:
308
+ * `error` or a poll-budget timeout is a terminal typed verdict the caller reads (`outcome`:
309
309
  * `issued` / `rejected` / `poll-timeout`); a tampered / unverifiable / desynchronized response is a
310
- * hard-stop `CmpError` throw. Exactly ONE protection flavor: `{ key, cert }` (signature) XOR `{ mac }`
311
- * (PBMAC1). A crypto-valid response is not enough -- the signer must chain to a supplied trust anchor
310
+ * hard-stop `CmpError` throw. Exactly one protection flavor: `{ key, cert }` (signature) XOR `{ mac }`
311
+ * (PBMAC1). A crypto-valid response is not enough: the signer must chain to a supplied trust anchor
312
312
  * (signature) or the shared secret must match (MAC); a valid-but-untrusted response is a hard stop, so the
313
313
  * signature flavor REQUIRES `opts.trustAnchors` at construction. Returns a session with `enroll(request)`,
314
314
  * and read-only `transactionID` + `transcript` (each leg's request/response bytes, retained up to a
315
315
  * transaction-wide cap; a later leg beyond the cap keeps its metadata + `byteLength` but drops the payload as
316
316
  * `bytes: null, truncated: true`, so a padded-response flood across polls cannot exhaust memory). The granted certificate must be valid X.509, carry the key
317
- * the request submitted (else it is a misrouted certificate the caller cannot use), and -- for the signature
318
- * flavor -- have its signature + chain validated to a supplied trust anchor before it is confirmed. The `certConf`
319
- * `certHash` uses the certificate's signature hash -- from the OID when it conveys one, or from the
317
+ * the request submitted (else it is a misrouted certificate the caller cannot use), and, for the signature
318
+ * flavor, have its signature + chain validated to a supplied trust anchor before it is confirmed. The `certConf`
319
+ * `certHash` uses the certificate's signature hash: from the OID when it conveys one, or from the
320
320
  * RSASSA-PSS parameters when it does not, with `hashAlg` omitted; only a truly hashless signature
321
321
  * (Ed25519 / Ed448, ML-DSA / SLH-DSA) computes under SHA-256 and declares an explicit `hashAlg`
322
322
  * (RFC 9810 sec. 5.3.18). The `certReqId` echoed in `pollReq` / `certConf` and matched in every
323
323
  * `CertResponse` is the caller's CRMF request id (`request.ir.certReqId`, ...) when supplied, else the
324
- * single-request default. One transaction per session -- a second or concurrent `enroll`, or a batched
324
+ * single-request default. One transaction per session: a second or concurrent `enroll`, or a batched
325
325
  * CRMF request, is refused (a local build error leaves the session retryable). The returned `chain` is the
326
326
  * issued leaf plus any authenticated `caPubs` the CA delivered (chain material, never trust anchors); a
327
327
  * server-generated (central key generation) private key is out of scope and the grant is refused.
328
328
  *
329
329
  * @opts
330
330
  * - `url` -- REQUIRED: the CMP endpoint URL.
331
- * - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }` -- PBMAC1 protection.
332
- * - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the ISSUED certificate's own signature + chain before confirmation (not the response protection). `intermediates` -- extra chain pool.
333
- * - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an EMPTY subject (identified only by its subjectAltName) REQUIRES an explicit `sender` -- the empty subject cannot name the requester for a peer that binds the sender to the SAN.
331
+ * - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }` for PBMAC1 protection.
332
+ * - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the issued certificate's own signature + chain before confirmation (not the response protection). `intermediates` supplies an extra chain pool.
333
+ * - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an empty subject (identified only by its subjectAltName) REQUIRES an explicit `sender`, because the empty subject cannot name the requester for a peer that binds the sender to the SAN.
334
334
  * - `senderKID` / `recipKID` -- optional key identifiers emitted on every request header, so a CA selecting among several shared secrets (senderKID) or recipient keys resolves the right credential.
335
- * - `expectedSender` -- optional CA signer CERTIFICATE (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it ALSO resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it -- rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
335
+ * - `expectedSender` -- optional CA signer certificate (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it also resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it, rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
336
336
  * - `implicitConfirm` -- request implicit confirmation (skip the certConf leg when the CA grants it).
337
- * - `acceptCert` -- an async policy `(certDer, { status, grantedWithMods }) => boolean` consulted before the certConf; return true to accept, anything else to veto (a `grantedWithMods` certificate the CA changed). A veto sends a REJECTING certConf and yields `outcome: "rejected"` with the certificate still surfaced. Incompatible with `implicitConfirm` (no reject leg exists) -- the combination throws at construction.
337
+ * - `acceptCert` -- an async policy `(certDer, { status, grantedWithMods }) => boolean` consulted before the certConf; return true to accept, anything else to veto (a `grantedWithMods` certificate the CA changed). A veto sends a rejecting certConf and yields `outcome: "rejected"` with the certificate still surfaced. Incompatible with `implicitConfirm` (no reject leg exists), so the combination throws at construction.
338
338
  * - `transport` -- injectable transport(request) -> {status, headers, body}; default pki.transport.https.
339
339
  * - `tls` / `headers` / `timeout` / `maxResponseBytes` -- transport config + budgets.
340
340
  * - `maxPolls` / `maxTotalWait` / `sleep` -- poll-loop budgets + an injectable sleeper; `time` -- verify-time clock.