@blamejs/pki 0.6.13 → 0.6.15

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
package/lib/sigstore.js CHANGED
@@ -44,40 +44,32 @@ function _err(code, message, cause) { return new SigstoreError(code, message, ca
44
44
  var JSON_MAX = C.LIMITS.JSON_MAX_BYTES;
45
45
  var SP = Buffer.from(" ", "ascii");
46
46
  var DSSEV1 = Buffer.from("DSSEv1", "ascii");
47
- // The checkpoint signature-line marker is EM DASH (U+2014) + space; built at runtime so the source
48
- // file stays pure ASCII. The line is parsed by character scan (no regex): "<EM DASH> <keyId> <sig>",
49
- // two whitespace-separated non-empty tokens, nothing after.
50
47
  var _EM_DASH = String.fromCharCode(0x2014);
51
48
 
52
- // The \S token scan uses the shared JS regex \s code-point predicate (pkix.isJsWhitespace) so the
53
- // note-line grammar stays exactly the /\S/ complement without a second copy of the code-point set.
54
49
  function _parseNoteSig(line) {
55
50
  var prefix = _EM_DASH + " ";
56
51
  if (line.slice(0, prefix.length) !== prefix) return null;
57
52
  var p = prefix.length, n = line.length;
58
53
  var t1s = p;
59
54
  while (p < n && !pkix.isJsWhitespace(line.charCodeAt(p))) p += 1;
60
- if (p === t1s) return null; // \S+ requires at least one character
55
+ if (p === t1s) return null;
61
56
  var keyId = line.slice(t1s, p);
62
- if (line.charAt(p) !== " ") return null; // the literal single space between the tokens
57
+ if (line.charAt(p) !== " ") return null;
63
58
  p += 1;
64
59
  var t2s = p;
65
60
  while (p < n && !pkix.isJsWhitespace(line.charCodeAt(p))) p += 1;
66
61
  if (p === t2s) return null;
67
62
  var sig = line.slice(t2s, p);
68
- if (p !== n) return null; // $ -- nothing after the second token
63
+ if (p !== n) return null;
69
64
  return { keyId: keyId, sig: sig };
70
65
  }
71
66
 
72
- // A run of trailing "=" removed (the /=+$/ base64-padding strip).
73
67
  function _stripTrailingEquals(s) {
74
68
  var i = s.length;
75
69
  while (i > 0 && s.charAt(i - 1) === "=") i -= 1;
76
70
  return s.slice(0, i);
77
71
  }
78
72
 
79
- // The recognized bundle media types (draft: the inline ".v0.N+json" form and the
80
- // "+json;version=0.N" form, N in 1..3) -- an explicit set in place of the anchored alternation regex.
81
73
  var _BUNDLE_MEDIA_TYPES = {
82
74
  "application/vnd.dev.sigstore.bundle.v0.1+json": 1,
83
75
  "application/vnd.dev.sigstore.bundle.v0.2+json": 1,
@@ -90,7 +82,6 @@ function _isBundleMediaType(mt) {
90
82
  return typeof mt === "string" && Object.prototype.hasOwnProperty.call(_BUNDLE_MEDIA_TYPES, mt);
91
83
  }
92
84
 
93
- // Parse untrusted JSON through the bounded, duplicate-member-rejecting guard.
94
85
  function _jsonParse(input, code, label) {
95
86
  return guard.json.parse(input, _err, {
96
87
  maxBytes: JSON_MAX, maxDepth: 64,
@@ -98,7 +89,6 @@ function _jsonParse(input, code, label) {
98
89
  });
99
90
  }
100
91
 
101
- // ---- DSSE PAE (secure-systems-lab/dsse protocol.md) --------------------------
102
92
 
103
93
  /**
104
94
  * @primitive pki.sigstore.pae
@@ -129,10 +119,7 @@ function pae(payloadType, payloadBytes) {
129
119
  ]);
130
120
  }
131
121
 
132
- // ---- strict base64 decode (fail-closed, canonical) ---------------------------
133
122
 
134
- // Sigstore fields are standard (padded) base64; DSSE permits URL-safe. Decode
135
- // either, but reject a non-canonical re-encoding (an encoding-malleability guard).
136
123
  function _b64(s, label) {
137
124
  if (typeof s !== "string") throw _err("sigstore/bad-bundle", label + " must be a base64 string");
138
125
  var urlSafe = s.indexOf("-") !== -1 || s.indexOf("_") !== -1;
@@ -144,7 +131,6 @@ function _b64(s, label) {
144
131
  return buf;
145
132
  }
146
133
 
147
- // ---- JSON bundle reader (fail-closed) ----------------------------------------
148
134
 
149
135
  /**
150
136
  * @primitive pki.sigstore.parseBundle
@@ -176,9 +162,6 @@ function parseBundle(input) {
176
162
  throw _err("sigstore/bad-bundle", "bundle must be a JSON object, string, or Buffer");
177
163
  }
178
164
  if (!obj || typeof obj !== "object" || Array.isArray(obj)) throw _err("sigstore/bad-bundle", "bundle must be a JSON object");
179
- // Accept only the bundle versions this verifier actually handles (v0.1-v0.3);
180
- // a newer version is recognized-and-deferred, never accepted by an over-broad
181
- // match (the inline `.v0.N+json` form and the `+json;version=0.N` form).
182
165
  var mt = obj.mediaType;
183
166
  if (!_isBundleMediaType(mt)) {
184
167
  throw _err("sigstore/bad-bundle-version", "unsupported or unknown bundle media type: " + (mt === undefined ? "(none)" : mt));
@@ -200,10 +183,7 @@ function parseBundle(input) {
200
183
  return obj;
201
184
  }
202
185
 
203
- // ---- leaf-key + signature primitives (node:crypto, sync) ---------------------
204
186
 
205
- // A bundle X509Certificate element is `{ rawBytes: <base64 DER> }`; a null /
206
- // non-object / non-string element fails closed, never a raw property deref.
207
187
  function _certBytes(c, label) {
208
188
  if (!c || typeof c !== "object" || typeof c.rawBytes !== "string") throw _err("sigstore/bad-bundle", label + " is not a { rawBytes } certificate");
209
189
  return _b64(c.rawBytes, label);
@@ -222,14 +202,9 @@ function _chainDers(vm) {
222
202
  return [_leafCertDer(vm)];
223
203
  }
224
204
 
225
- // Verify a DER ECDSA / Ed25519 signature over `data` under a node public key.
226
205
  function _rawVerify(keyObj, data, derSig) {
227
206
  var t = keyObj.asymmetricKeyType;
228
207
  if (t === "ed25519" || t === "ed448") {
229
- // Defense in depth at the verify SINK (not only at _pubFromSpki): node imports a low-order /
230
- // off-curve OKP key without complaint and such a key verifies a FORGED EdDSA signature, so re-gate
231
- // the point here regardless of how keyObj was obtained -- no verify sink may hand an unvalidated
232
- // Edwards key to node's verify.
233
208
  edwardsPoint.validateSpki(keyObj.export({ type: "spki", format: "der" }), t === "ed25519" ? 6 : 7, SigstoreError, "sigstore/bad-key");
234
209
  return nodeCrypto.verify(null, data, keyObj, derSig);
235
210
  }
@@ -241,35 +216,21 @@ function _pubFromSpki(spkiDer, label) {
241
216
  var key;
242
217
  try { key = nodeCrypto.createPublicKey({ key: spkiDer, format: "der", type: "spki" }); }
243
218
  catch (e) { throw _err("sigstore/bad-key", "invalid " + label + " public key", e); }
244
- // node imports a low-order (e.g. all-zeroes) OKP public key without complaint, and such a key
245
- // verifies a FORGED EdDSA signature -- route an Ed25519/Ed448 SPKI through the shared full-order,
246
- // on-curve Edwards-point gate (as the webauthn and path-validation EdDSA paths already do) before it
247
- // can be handed to verify.
248
219
  var t = key.asymmetricKeyType;
249
220
  if (t === "ed25519" || t === "ed448") {
250
221
  edwardsPoint.validateSpki(spkiDer, t === "ed25519" ? 6 : 7, SigstoreError, "sigstore/bad-key");
251
222
  }
252
223
  return key;
253
224
  }
254
- // Parse a certificate, re-typing a malformed-DER fault to a sigstore/* error so
255
- // the caller never sees a raw certificate/* / asn1/* leak from this boundary.
256
225
  function _parseCert(der, label) {
257
226
  try { return x509.parse(der); }
258
227
  catch (e) { throw _err("sigstore/bad-certificate", "invalid " + label, e); }
259
228
  }
260
229
 
261
- // ---- Rekor leg (RFC 9162 inclusion + Rekor-signed root + entry binding) -------
262
230
 
263
231
  function _sha256(buf) { return nodeCrypto.createHash("sha256").update(buf).digest(); }
264
232
 
265
- // Select the caller Rekor key whose logId.keyId matches the entry's logId (full
266
- // 32-byte id) or a 4-byte checkpoint keyhint. Never trust a key from the bundle.
267
- // A key carrying a validFor window is used only if `timeMs` (the entry's log
268
- // time) falls inside it -- a rotated-out key must not verify a later entry.
269
- // A validFor bound is an epoch-ms number, a Date, or an ISO-8601 string (the form
270
- // a Sigstore trusted_root carries). Returns null for absent/unparseable.
271
233
  function _toMs(x) {
272
- // Coverage residual -- _inWindow gates every _toMs call behind a != null check; the null guard is dead defensive code.
273
234
  if (x == null) return null;
274
235
  if (typeof x === "number") return Number.isFinite(x) ? x : null;
275
236
  if (guard.time.isDate(x)) { var d = guard.time.instantOf(x); return isNaN(d) ? null : d; }
@@ -278,8 +239,6 @@ function _toMs(x) {
278
239
  }
279
240
  function _inWindow(timeMs, vf) {
280
241
  if (!vf) return true;
281
- // A present-but-unparseable bound fails closed (the key/CA is not used) rather
282
- // than silently disabling the window it configures.
283
242
  if (vf.start != null) { var s = _toMs(vf.start); if (s === null || !(timeMs >= s)) return false; }
284
243
  if (vf.end != null) { var e = _toMs(vf.end); if (e === null || !(timeMs <= e)) return false; }
285
244
  return true;
@@ -294,15 +253,12 @@ function _rekorKey(rekorKeys, keyIdBuf, hint, timeMs) {
294
253
  return null;
295
254
  }
296
255
 
297
- // Verify the checkpoint (C2SP signed-note): ECDSA-P256-SHA256 over the note body
298
- // (bytes up to and including the newline before the blank-line separator).
299
256
  function _verifyCheckpoint(envelope, ip, rekorKeys, timeMs) {
300
257
  var buf = Buffer.from(envelope, "utf8");
301
258
  var sep = buf.indexOf("\n\n");
302
259
  if (sep < 0) throw _err("sigstore/bad-checkpoint", "the checkpoint has no note/signature separator");
303
260
  var body = buf.subarray(0, sep + 1);
304
261
  var lines = body.toString("utf8").split("\n");
305
- // The note body's third line is the base64 tree root; it MUST equal the proof root.
306
262
  if (_b64(lines[2], "checkpoint root").toString("hex") !== _b64(ip.rootHash, "inclusionProof.rootHash").toString("hex")) {
307
263
  throw _err("sigstore/inclusion-proof-mismatch", "the checkpoint root does not match the inclusion-proof root");
308
264
  }
@@ -320,8 +276,6 @@ function _verifyCheckpoint(envelope, ip, rekorKeys, timeMs) {
320
276
  return false;
321
277
  }
322
278
 
323
- // Verify the SET (inclusionPromise.signedEntryTimestamp): ECDSA over the RFC 8785
324
- // canonical JSON { body, integratedTime, logID, logIndex } (keys sorted).
325
279
  function _verifySet(te, rekorKeys, timeMs) {
326
280
  var promise = te.inclusionPromise;
327
281
  if (!promise || typeof promise.signedEntryTimestamp !== "string") return false;
@@ -338,9 +292,6 @@ function _verifySet(te, rekorKeys, timeMs) {
338
292
  return _rawVerify(_pubFromSpki(k.spki, "Rekor log"), Buffer.from(canonical, "utf8"), sig);
339
293
  }
340
294
 
341
- // Bind the tlog entry to THIS bundle's signature: the dsse-kind body's embedded
342
- // signature + payload hash must be the envelope's (RFC 9162 proves the body is
343
- // logged; this proves the body is OUR envelope, not some other logged entry).
344
295
  function _bindEntry(te, envelope, leafDer) {
345
296
  var body = _jsonParse(_b64(te.canonicalizedBody, "canonicalizedBody"), "sigstore/bad-tlog-entry", "Rekor entry body");
346
297
  if (!body || typeof body !== "object" || Array.isArray(body)) throw _err("sigstore/bad-tlog-entry", "the Rekor entry body is not a JSON object");
@@ -353,16 +304,10 @@ function _bindEntry(te, envelope, leafDer) {
353
304
  var bodySig = _b64(spec.signatures[0].signature, "Rekor entry signature");
354
305
  if (!envSig.equals(bodySig)) throw _err("sigstore/entry-mismatch", "the Rekor entry signature does not match the bundle signature");
355
306
  var payload = _b64(envelope.payload, "dsseEnvelope.payload");
356
- // payloadHash is a required binding field: bind it to the envelope payload
357
- // rather than skipping the check when it is absent.
358
307
  if (!spec.payloadHash || typeof spec.payloadHash.value !== "string") throw _err("sigstore/bad-tlog-entry", "the Rekor dsse entry is missing its payloadHash");
359
308
  if (spec.payloadHash.value !== _sha256(payload).toString("hex")) {
360
309
  throw _err("sigstore/entry-mismatch", "the Rekor entry payloadHash does not match the bundle payload");
361
310
  }
362
- // The dsse entry's verifier certificate is REQUIRED and MUST be the bundle's
363
- // leaf cert -- otherwise a valid inclusion proof for an entry signed by a
364
- // DIFFERENT Fulcio cert could be paired with this envelope (the signature match
365
- // alone leaves cert substitution).
366
311
  var vf = spec.signatures[0].verifier;
367
312
  if (typeof vf !== "string") throw _err("sigstore/bad-tlog-entry", "the Rekor dsse entry is missing its verifier certificate");
368
313
  var vDer;
@@ -376,13 +321,9 @@ function _verifyRekor(te, envelope, rekorKeys, leafDer) {
376
321
  if (!ip || typeof ip.rootHash !== "string" || !Array.isArray(ip.hashes)) {
377
322
  throw _err("sigstore/bad-inclusion-proof", "the transparency-log entry has no inclusion proof");
378
323
  }
379
- // The entry's CLAIMED log time selects which caller Rekor key is in its validity
380
- // window; the SET (required below) re-confirms it by signing it.
381
324
  var claimedSec = Number(te.integratedTime);
382
325
  var claimedMs = Number.isFinite(claimedSec) ? C.TIME.seconds(claimedSec) : NaN;
383
- // Bind the entry to this signature FIRST (a proof for another entry is not evidence).
384
326
  _bindEntry(te, envelope, leafDer);
385
- // Fold the audit path to the tree root (RFC 9162), reusing pki.merkle.
386
327
  var included;
387
328
  try {
388
329
  included = merkle.verifyInclusion({
@@ -396,33 +337,19 @@ function _verifyRekor(te, envelope, rekorKeys, leafDer) {
396
337
  throw _err("sigstore/bad-inclusion-proof", "the inclusion proof is malformed: " + e.message, e);
397
338
  }
398
339
  if (!included) throw _err("sigstore/inclusion-proof-mismatch", "the inclusion proof does not reconstruct the tree root");
399
- // The inclusion proof above only reconstructs the attacker-supplied
400
- // inclusionProof.rootHash; that root is trust ONLY once the Rekor-signed
401
- // checkpoint (which signs the tree root) verifies over it -- so the checkpoint
402
- // is REQUIRED, or the inclusion check is theater against an unsigned root.
403
340
  var checkpointOk = ip.checkpoint && typeof ip.checkpoint.envelope === "string" && _verifyCheckpoint(ip.checkpoint.envelope, ip, rekorKeys, claimedMs);
404
341
  if (!checkpointOk) throw _err("sigstore/unsigned-root", "the Rekor checkpoint (the signed tree root) did not verify under the caller Rekor key -- the inclusion-proof root is not attested");
405
- // The integratedTime dates the ephemeral (~10-min) Fulcio cert, so it too must
406
- // be Rekor-attested: ONLY the SET signs it (the checkpoint signs the root, not
407
- // the time). Without a verified SET the time is attacker-controlled and could
408
- // date an expired cert into validity -- require it (the RFC 3161 timestamp
409
- // source is the deferred alternative).
410
342
  var setOk = _verifySet(te, rekorKeys, claimedMs);
411
343
  if (!setOk) throw _err("sigstore/unattested-time", "the Rekor SET (the signed source of integratedTime) did not verify -- the log time is not attested and cannot date the Fulcio certificate");
412
344
  var t = Number(te.integratedTime);
413
345
  if (!Number.isFinite(t) || t < 0) throw _err("sigstore/bad-tlog-entry", "the transparency-log entry has a malformed integratedTime");
414
- // Surface which attested Rekor log entry this is: the global log index and the log's key id (hex), so a
415
- // caller can locate the exact transparency-log record for an audit rather than re-deriving it.
416
346
  var li = Number(te.logIndex);
417
347
  return { integratedTime: t, logIndex: Number.isFinite(li) ? li : null, logId: te.logId && te.logId.keyId ? _b64(te.logId.keyId, "logId.keyId").toString("hex") : null };
418
348
  }
419
349
 
420
- // ---- Fulcio chain leg (RFC 5280 path validation, as-of the log time) ----------
421
350
 
422
351
  function _dn(cert, which) { return cert[which] && cert[which].dn; }
423
352
 
424
- // Normalize a caller Fulcio root: a raw DER Buffer, or { der|rawBytes, validFor }
425
- // (the shape carried by a Sigstore trusted_root certificateAuthorities entry).
426
353
  function _normRoots(fulcioRoots) {
427
354
  return fulcioRoots.map(function (r, i) {
428
355
  var der = Buffer.isBuffer(r) ? r : (r && (r.der || r.rawBytes));
@@ -431,12 +358,6 @@ function _normRoots(fulcioRoots) {
431
358
  });
432
359
  }
433
360
 
434
- // Build the ordered path body [intermediates..., leaf] from the leaf up to (not
435
- // including) a cert issued by `anchor`, drawing intermediates from `intBySubject`
436
- // (caller certs AND bundle links -- all get cryptographically re-checked by
437
- // path.validate). `anchor` itself is never placed in the body. Returns null if no
438
- // such path exists. The terminal trust anchor is always a caller cert; a bundle
439
- // cert can appear only here, as a non-terminal path step.
440
361
  function _pathToAnchor(leaf, anchor, intBySubject) {
441
362
  var anchorSubj = _dn(anchor, "subject");
442
363
  var path = [leaf], cur = leaf, seen = {}, guardN = 0;
@@ -460,15 +381,10 @@ function _pathToAnchor(leaf, anchor, intBySubject) {
460
381
  async function _verifyChain(leaf, chainDers, fulcioRoots, timeMs) {
461
382
  var roots = _normRoots(fulcioRoots);
462
383
  var bundleLinks = chainDers.slice(1).map(function (d, i) { return _parseCert(d, "bundle intermediate [" + i + "]"); });
463
- // Path-body intermediates: caller certs (trusted) + bundle intermediates (path
464
- // steps only). The terminal anchor is chosen ONLY from the caller roots below.
465
384
  var intBySubject = {};
466
385
  function addInt(c) { (intBySubject[_dn(c, "subject")] = intBySubject[_dn(c, "subject")] || []).push(c); }
467
386
  roots.forEach(function (r) { addInt(r.cert); });
468
387
  bundleLinks.forEach(addInt);
469
- // Try each caller anchor within its validity window (the trusted_root carries
470
- // several CA rotations, some sharing a subject DN); accept the first that both
471
- // yields a path and cryptographically validates as of the log time.
472
388
  var candidates = roots.filter(function (r) { return _inWindow(timeMs, r.validFor); });
473
389
  var lastErr = _err("sigstore/chain-incomplete", "no caller-supplied Fulcio anchor (within its validity window) issues the chain");
474
390
  for (var i = 0; i < candidates.length; i++) {
@@ -486,29 +402,18 @@ async function _verifyChain(leaf, chainDers, fulcioRoots, timeMs) {
486
402
  if (res.valid) return;
487
403
  lastErr = _err("sigstore/chain-invalid", "the Fulcio certificate chain is not valid as of the log time");
488
404
  } catch (e) {
489
- // Coverage residual -- pathValidate.validate is fail-safe (builtinVerify never throws; every decodeExt wrapped), so no raw throw escapes for a well-formed path; the catch is a defensive re-type.
490
405
  lastErr = _err("sigstore/chain-invalid", "the Fulcio certificate chain failed validation: " + e.message, e);
491
406
  }
492
407
  }
493
408
  throw lastErr;
494
409
  }
495
410
 
496
- // ---- Fulcio identity leg -----------------------------------------------------
497
411
 
498
- // Decode the identity the Fulcio cert commits to: the SAN plus the Fulcio
499
- // extension arc (Issuer .1.8, SourceRepositoryURI .1.12, etc). `.1.8`+ values are
500
- // DER UTF8String; the legacy `.1.1`-`.1.6` are raw strings (never DER).
501
- // Derive the OIDs from the registry (never a dotted-decimal literal in source):
502
- // the SAN, and the Fulcio arc base (the parent of the `.7` otherName member).
503
412
  var SAN_OID = oid.byName("subjectAltName");
504
413
  var FULCIO_PREFIX = oid.byName("otherName").split(".").slice(0, -1).join(".") + ".";
505
414
  function _identity(leaf) {
506
415
  var out = { san: null, extensions: {} };
507
- // The identity is security-relevant: an undecodable SAN / Fulcio member is a
508
- // malformed certificate and fails closed with a named reason (never a silent
509
- // drop that would hide an identity claim a caller policy relies on).
510
416
  try {
511
- // Coverage residual -- x509.parse always surfaces extensions as an array.
512
417
  (leaf.extensions || []).forEach(function (ext) {
513
418
  if (ext.oid === SAN_OID) { out.san = _sanValue(ext); return; }
514
419
  if (ext.oid.indexOf(FULCIO_PREFIX) === 0) {
@@ -521,86 +426,52 @@ function _identity(leaf) {
521
426
  }
522
427
  return out;
523
428
  }
524
- // The SAN GeneralNames the OIDC identity is carried in: a context-tagged
525
- // [1] rfc822Name / [2] dNSName / [6] uniformResourceIdentifier. Return the URI
526
- // (a machine identity) preferentially, else the first name, with its type.
527
429
  var GN_TYPE = { 1: "rfc822Name", 2: "dNSName", 6: "uri" };
528
430
  function _sanValue(ext) {
529
431
  var seq = asn1.decode(ext.value);
530
432
  var names = [];
531
- // Coverage residual -- path.validate's GeneralName gate (context-only, SEQUENCE SAN, strict otherName [0]) rejects the shapes that would trigger these fallbacks before _identity; asn1.decode guarantees a children array on any constructed node.
532
433
  for (var i = 0; i < (seq.children || []).length; i++) {
533
434
  var n = seq.children[i];
534
435
  if (n.tagClass !== "context") continue;
535
436
  var val = null;
536
437
  if (!n.constructed && GN_TYPE[n.tagNumber]) {
537
- // A primitive IMPLICIT IA5String name: rfc822Name / dNSName / URI.
538
438
  val = { type: GN_TYPE[n.tagNumber], value: asn1.read.octetStringImplicit(n, n.tagNumber).toString("utf8") };
539
439
  } else if (n.constructed && n.tagNumber === 0 && (n.children || []).length >= 2) {
540
- // otherName ::= SEQUENCE { type-id OID, [0] EXPLICIT value } -- Fulcio carries
541
- // machine identities here (type-id .1.7, value a UTF8String).
542
440
  var inner = (n.children[1].children || [])[0] || n.children[1];
543
441
  var v;
544
- // Coverage residual -- asn1.decode gives every node a (truthy) Buffer content, so the || [] fallback is unreachable; the catch itself runs for a non-string otherName inner value.
545
- try { v = asn1.read.string(inner); } catch (_e) { v = Buffer.from(inner.content || []).toString("utf8"); }
442
+ try {
443
+ v = asn1.read.string(inner);
444
+ } catch (_e) {
445
+ v = null;
446
+ }
546
447
  val = { type: "otherName", oid: asn1.read.oid(n.children[0]), value: v };
547
448
  }
548
449
  if (val) names.push(val);
549
450
  }
550
- // A Fulcio certificate binds exactly one identity; more than one SAN lets a
551
- // mis-issued cert pair the expected identity with a smuggled extra one, so a
552
- // caller policy that matches the wrong entry cannot be relied on -- reject.
553
451
  if (names.length > 1) throw _err("sigstore/bad-certificate", "the Fulcio certificate carries multiple SAN identities");
554
452
  return names[0] || null;
555
453
  }
556
454
  function _fulcioExtValue(ext, leafArc) {
557
- // Coverage residual -- ext.value is read.octetString output, always a Buffer.
558
455
  if (!Buffer.isBuffer(ext.value)) throw _err("sigstore/bad-certificate", "Fulcio extension " + ext.oid + " has no value");
559
- // The raw-vs-DER split by member (Fulcio oid-info): .1-.6 legacy are raw UTF-8
560
- // strings; .8+ are DER UTF8String -- the arc is open-ended past .22. An
561
- // undecodable member throws (caught by _identity) rather than dropping silently.
562
456
  if (leafArc >= 1 && leafArc <= 6) return ext.value.toString("utf8");
563
457
  return asn1.read.string(asn1.decode(ext.value));
564
458
  }
565
459
 
566
- // The fields an identity policy may constrain. Named once so the check, the refusal of an
567
- // unknown key, and the report of what ran cannot drift apart.
568
460
  var IDENTITY_FIELDS = ["san", "issuer", "sourceRepositoryURI"];
569
- // The guard tests membership with hasOwnProperty, so the permitted set is a lookup object --
570
- // an array would treat "0"/"1" as the known keys and reject every real field name.
571
461
  var IDENTITY_KEYS = { san: 1, issuer: 1, sourceRepositoryURI: 1 };
572
- // Every option pki.sigstore.verifyBundle reads. Adding one here is the only way to make it
573
- // accepted, so a capability cannot arrive with its option silently ignored at this boundary.
574
462
  var _VERIFY_BUNDLE_OPTS = { fulcioRoots: 1, rekorKeys: 1, identity: 1, predicateType: 1, time: 1 };
575
463
 
576
- // Returns WHICH fields were actually compared. A bundle verifies its own signature and log
577
- // inclusion whoever signed it -- Fulcio issues to anyone who completes an OIDC flow -- so
578
- // `verified: true` without an identity policy says the artifact was signed and logged, not that
579
- // a trusted party signed it. The caller cannot tell those apart from a bare boolean.
580
464
  function _checkIdentity(id, policy) {
581
465
  var ran = { san: false, issuer: false, sourceRepositoryURI: false };
582
466
  if (policy === undefined || policy === null) return ran;
583
467
  if (typeof policy !== "object" || Array.isArray(policy)) {
584
468
  throw _err("sigstore/bad-input", "opts.identity must be an object naming the identity fields to pin (" + IDENTITY_FIELDS.join(", ") + ")");
585
469
  }
586
- // An unknown key is refused, not ignored: cosign spells this `certificateIdentity`, and a
587
- // swallowed spelling checks nothing under a name the operator believes pins the signer.
588
- // The guard rejects through a (code, message) FACTORY. Handing it the error CLASS raises
589
- // "class constructor cannot be invoked without new" -- a raw, untyped throw escaping a public
590
- // verb, on the branch a valid-input test never takes.
591
470
  guard.identifier.assertKnownKeys(policy, IDENTITY_KEYS, _err, "sigstore/bad-input", "opts.identity has an unknown key ");
592
- // A policy that constrains NOTHING is a configuration mistake, and the most dangerous input on
593
- // this surface: every guard below is falsy, so it accepts every signer while reading as though
594
- // an identity policy is in force. Refused at the boundary rather than answered.
595
471
  var asked = IDENTITY_FIELDS.filter(function (f) { return policy[f] !== undefined; });
596
472
  if (!asked.length) {
597
473
  throw _err("sigstore/bad-input", "opts.identity constrains nothing -- name at least one of " + IDENTITY_FIELDS.join(", ") + ", or omit it to state that the signer is not being checked");
598
474
  }
599
- // A named field must carry a value that can actually be compared. The comparisons below are
600
- // truthiness-guarded, so an empty string or a null would be skipped while the field had been
601
- // named -- reporting a signer check that never ran, which is the exact confusion this report
602
- // exists to remove. Deciding "asked" and deciding "compared" must be the SAME test, so a value
603
- // that cannot be compared is refused here rather than quietly becoming "not asked".
604
475
  asked.forEach(function (f) {
605
476
  if (typeof policy[f] !== "string" || policy[f] === "") {
606
477
  throw _err("sigstore/bad-input", "opts.identity." + f + " must be a non-empty string -- a value that cannot be compared would leave the signer unchecked under a policy that names it");
@@ -609,14 +480,11 @@ function _checkIdentity(id, policy) {
609
480
  });
610
481
  var sanValue = id.san && id.san.value;
611
482
  if (policy.san && sanValue !== policy.san) throw _err("sigstore/identity-mismatch", "the certificate SAN " + JSON.stringify(sanValue) + " does not match the expected identity");
612
- // The OIDC issuer is carried by the current Issuer V2 (.1.8) or, on older certs,
613
- // only by the deprecated raw-string issuer (.1.1); match against either.
614
483
  if (policy.issuer && policy.issuer !== id.extensions.issuer && policy.issuer !== id.extensions.issuerLegacy) throw _err("sigstore/identity-mismatch", "the certificate OIDC issuer does not match the expected issuer");
615
484
  if (policy.sourceRepositoryURI && id.extensions.sourceRepositoryURI !== policy.sourceRepositoryURI) throw _err("sigstore/identity-mismatch", "the certificate source-repository URI does not match");
616
485
  return ran;
617
486
  }
618
487
 
619
- // ---- in-toto Statement leg ---------------------------------------------------
620
488
 
621
489
  function _statement(payload, payloadType, expectedPredicate) {
622
490
  if (payloadType !== "application/vnd.in-toto+json") {
@@ -625,16 +493,12 @@ function _statement(payload, payloadType, expectedPredicate) {
625
493
  var st = _jsonParse(payload, "sigstore/bad-statement", "in-toto statement");
626
494
  if (!st || typeof st !== "object" || st._type !== "https://in-toto.io/Statement/v1") throw _err("sigstore/bad-statement", "the payload is not an in-toto Statement v1");
627
495
  if (!Array.isArray(st.subject) || !st.subject.length) throw _err("sigstore/bad-statement", "the in-toto statement has no subject");
628
- // The verdict carries predicateType for the caller's own gate; when the caller
629
- // pins one (opts.predicateType), a mismatch fails closed so a non-SLSA
630
- // attestation (e.g. an SBOM) is not mistaken for the expected provenance.
631
496
  if (expectedPredicate && st.predicateType !== expectedPredicate) {
632
497
  throw _err("sigstore/predicate-mismatch", "the statement predicateType " + JSON.stringify(st.predicateType) + " does not match the expected " + JSON.stringify(expectedPredicate));
633
498
  }
634
499
  return st;
635
500
  }
636
501
 
637
- // ---- orchestrator ------------------------------------------------------------
638
502
 
639
503
  /**
640
504
  * @primitive pki.sigstore.verifyBundle
@@ -685,12 +549,6 @@ async function verifyBundle(bundle, opts) {
685
549
  throw new TypeError("verifyBundle: bundle must be an object, JSON string, or Buffer");
686
550
  }
687
551
  opts = opts || {};
688
- // An unrecognized option is refused, not swallowed. The identity policy one level down already
689
- // closes this door for exactly the reason it needs closing here too: cosign spells the signer pin
690
- // `certificateIdentity`, and a swallowed spelling checks nothing under a name the operator
691
- // believes pins the signer. At the top level the same slip loses the SLSA predicate pin as well,
692
- // and `predicateType` has no `identityChecked`-style field, so nothing in the verdict reveals
693
- // that the pin never ran.
694
552
  guard.identifier.assertKnownKeys(opts, _VERIFY_BUNDLE_OPTS, _err, "sigstore/bad-input", "pki.sigstore.verifyBundle has an unknown option ");
695
553
  var b = parseBundle(bundle);
696
554
  var vm = b.verificationMaterial;
@@ -698,7 +556,6 @@ async function verifyBundle(bundle, opts) {
698
556
  var rekorKeys = opts.rekorKeys || [];
699
557
  var fulcioRoots = opts.fulcioRoots || [];
700
558
 
701
- // Leg 1 -- DSSE signature over PAE under the Fulcio leaf public key.
702
559
  var leafDer = _leafCertDer(vm);
703
560
  var leaf = _parseCert(leafDer, "Fulcio leaf certificate");
704
561
  var payload = _b64(env.payload, "dsseEnvelope.payload");
@@ -707,9 +564,6 @@ async function verifyBundle(bundle, opts) {
707
564
  var leafKey = _pubFromSpki(leaf.subjectPublicKeyInfo.bytes, "Fulcio leaf");
708
565
  if (!_rawVerify(leafKey, preimage, derSig)) throw _err("sigstore/dsse-verify-failed", "the DSSE signature does not verify under the Fulcio leaf key");
709
566
 
710
- // Leg 2 -- Rekor inclusion + Rekor-signed root + entry binding (yields the
711
- // trusted time). Try every transparency-log entry; accept the first that fully
712
- // verifies (and binds this signature), rejecting only if none do.
713
567
  var tlogs = Array.isArray(vm.tlogEntries) ? vm.tlogEntries : [];
714
568
  if (!tlogs.length) throw _err("sigstore/bad-bundle", "a keyless bundle requires at least one transparency-log entry");
715
569
  var rekor = null, lastErr = null;
@@ -721,22 +575,15 @@ async function verifyBundle(bundle, opts) {
721
575
  if (rekor === null) throw lastErr;
722
576
  var integratedTime = rekor.integratedTime;
723
577
 
724
- // Leg 3 -- Fulcio chain, validated as of the (trusted) log time; then identity.
725
- // The instant comes through the time guard's intrinsic read. `getTime` is an ordinary method a
726
- // subclass answers, and the slot predicate above accepts a subclass from any realm, so reading
727
- // the method off the value let a caller name the moment the Fulcio chain is validated at --
728
- // epoch zero, say, where every certificate in it is still unissued.
729
578
  var checkTime = guard.time.isDate(opts.time) ? guard.time.instantOf(opts.time) : C.TIME.seconds(integratedTime);
730
579
  await _verifyChain(leaf, _chainDers(vm), fulcioRoots, checkTime);
731
580
  var identity = _identity(leaf);
732
581
  var identityChecked = _checkIdentity(identity, opts.identity);
733
582
 
734
- // Leg 4 -- the in-toto SLSA statement + subject binding (with an optional
735
- // caller-pinned predicateType).
736
583
  var st = _statement(payload, env.payloadType, opts.predicateType);
737
584
 
738
585
  return {
739
- valid: true, // canonical toolkit-wide verdict alias, beside the verb's own `verified`
586
+ valid: true,
740
587
  verified: true,
741
588
  payload: payload,
742
589
  statement: st,
@@ -744,16 +591,9 @@ async function verifyBundle(bundle, opts) {
744
591
  predicateType: st.predicateType,
745
592
  predicate: st.predicate,
746
593
  identity: identity,
747
- // Which identity fields were actually compared. `verified` says the artifact was signed and
748
- // logged; only these say a signer the caller named was the one who signed it.
749
594
  identityChecked: identityChecked,
750
- // Whether the caller pinned a predicateType (opts.predicateType) that was checked. The verdict is
751
- // only reached when a pinned predicate matched (a mismatch throws sigstore/predicate-mismatch), so
752
- // a caller that omitted the pin gets `false` and knows the SLSA predicate went unverified.
753
595
  predicateTypeChecked: opts.predicateType != null,
754
596
  integratedTime: integratedTime,
755
- // The attested Rekor entry this verdict rests on: `logIndex` is its global log index and `logId` the
756
- // log's key id (hex), so a caller can fetch or audit the exact transparency-log record.
757
597
  logIndex: rekor.logIndex,
758
598
  logId: rekor.logId,
759
599
  };
package/lib/sleep.js CHANGED
@@ -1,16 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- the shared bounded poll sleeper for the stateful network clients (pki.acme.client and
6
- // pki.cmp.session). A delay above Node's 32-bit setTimeout ceiling is SPLIT into chained maximum-size
7
- // chunks: a bare setTimeout(fn, > 2^31-1) is silently clamped to 1 ms (a TimeoutOverflowWarning) and would
8
- // then rapidly re-poll instead of waiting the full interval. Each client's opts.sleep overrides this in
9
- // tests, so this default, the one sleeper that touches a real timer, is never driven by a test wait.
4
+ // @internal
10
5
 
11
- var SETTIMEOUT_MAX_MS = 2147483647; // 2^31 - 1: Node's setTimeout delay ceiling
6
+ var SETTIMEOUT_MAX_MS = 2147483647;
12
7
 
13
- // sleep(ms) -> Promise resolved after `ms` milliseconds, chunking a delay past the timer ceiling.
14
8
  function sleep(ms) {
15
9
  return new Promise(function (resolve) {
16
10
  (function step(remaining) {