@blamejs/core 0.8.42 → 0.8.49

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 (222) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
@@ -5,6 +5,7 @@ var fs = require("node:fs");
5
5
  var path = require("node:path");
6
6
  var nodeCrypto = require("node:crypto");
7
7
 
8
+ var blamejsCrypto = require("./crypto");
8
9
  var C = require("./constants");
9
10
  var safeBuffer = require("./safe-buffer");
10
11
  var validateOpts = require("./validate-opts");
@@ -13,6 +14,7 @@ var safeAsync = require("./safe-async");
13
14
  var { defineClass } = require("./framework-error");
14
15
 
15
16
  var TlsTrustError = defineClass("TlsTrustError", { alwaysPermanent: true });
17
+ var NetworkTlsError = defineClass("NetworkTlsError", { alwaysPermanent: true });
16
18
 
17
19
  var observability = lazyRequire(function () { return require("./observability"); });
18
20
  var audit = lazyRequire(function () { return require("./audit"); });
@@ -26,7 +28,7 @@ var STATE = {
26
28
  cas: [],
27
29
  systemTrust: false,
28
30
  baselineFingerprints: null,
29
- tlsKeyShares: ["SecP384r1MLKEM1024", "X25519MLKEM768", "X25519"],
31
+ tlsKeyShares: ["X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024", "X25519"],
30
32
  };
31
33
 
32
34
  function _normalizePem(pem) {
@@ -435,10 +437,29 @@ function applyToContext(opts) {
435
437
  // setKeyShares(["X25519MLKEM768", "X25519"]) → string[] (after)
436
438
  // resetKeyShares() → restores default
437
439
 
440
+ // RFC 9794 (PQ TLS Hybrid Key Exchange) named-group ordering. The
441
+ // preferred groups (the first the peer mutually supports wins) put the
442
+ // IANA-registered hybrid named groups ahead of the classical fallback:
443
+ //
444
+ // X25519MLKEM768 — codepoint 0x11EC, RFC 9794 default hybrid
445
+ // SecP256r1MLKEM768 — codepoint 0x11EB, RFC 9794 optional hybrid
446
+ // (NIST-curve fallback for FIPS-mandated peers
447
+ // that refuse X25519)
448
+ // SecP384r1MLKEM1024 — draft-kwiatkowski-tls-ecdhe-mlkem-02 codepoint
449
+ // 0x11ED; highest-PQC hybrid; only ML-KEM-1024
450
+ // offering for FIPS-mandated peers wanting
451
+ // CNSA-2.0-aligned key strength
452
+ // X25519 — classical fallback (modern non-PQC peers)
453
+ //
454
+ // Operators FIPS-mandated to a NIST curve set `setKeyShares([
455
+ // "SecP256r1MLKEM768", "SecP384r1MLKEM1024" ])` and drop the X25519-
456
+ // based groups. Operators on legacy peers without any PQC support set
457
+ // `setKeyShares(["X25519"])` to opt out of the hybrid groups entirely.
438
458
  var DEFAULT_PQC_KEY_SHARES = Object.freeze([
439
- "SecP384r1MLKEM1024", // highest-PQC hybrid (codepoint 0x11ED, draft-kwiatkowski-tls-ecdhe-mlkem-02)
440
- "X25519MLKEM768", // mid-PQC hybrid (codepoint 0x11EC, IETF/Cloudflare/Chrome interop)
441
- "X25519", // classical fallback (modern non-PQC peers)
459
+ "X25519MLKEM768",
460
+ "SecP256r1MLKEM768",
461
+ "SecP384r1MLKEM1024",
462
+ "X25519",
442
463
  ]);
443
464
 
444
465
  function _validateKeyShare(name) {
@@ -474,6 +495,18 @@ function resetKeyShares() {
474
495
  return getKeyShares();
475
496
  }
476
497
 
498
+ // preferredGroups — RFC 9794 alias surface for the named-group list.
499
+ // `set(list)` overrides the default ordering; `get()` reads the active
500
+ // list; `reset()` restores the framework default. The setKeyShares /
501
+ // getKeyShares / resetKeyShares names are kept as the lower-level
502
+ // alias under `b.network.tls.pqc.*`.
503
+ var preferredGroups = Object.freeze({
504
+ set: setKeyShares,
505
+ get: getKeyShares,
506
+ reset: resetKeyShares,
507
+ DEFAULT: DEFAULT_PQC_KEY_SHARES,
508
+ });
509
+
477
510
  var pqc = Object.freeze({
478
511
  setKeyShares: setKeyShares,
479
512
  getKeyShares: getKeyShares,
@@ -485,6 +518,139 @@ function getCaPems() {
485
518
  return STATE.cas.map(function (e) { return e.pem; });
486
519
  }
487
520
 
521
+ // b.network.tls.buildOptions(opts) — assemble a plain options object
522
+ // suitable for tls.connect / new https.Agent(...) / https.request,
523
+ // pre-populated with the framework's PQC group preference + TLSv1.3
524
+ // floor. Operators that build their own outbound transport (custom
525
+ // https.Agent, raw tls.connect for protocol clients other than HTTP)
526
+ // route through this primitive so the same posture lands everywhere.
527
+ //
528
+ // Throws NetworkTlsError("network-tls/bad-tls-options") on invalid
529
+ // shape (config-time entry point — operator catches typo at boot).
530
+ //
531
+ // buildOptions({ ecdhCurve, groups, cert, key, ca, minVersion, sni })
532
+ // returns { minVersion, ecdhCurve, groups, cert, key, ca, servername }
533
+ //
534
+ // `ca` accepts a PEM string OR Buffer OR Array<string|Buffer>; arrays
535
+ // are concatenated with `\n` so Node's TLS layer parses every block.
536
+ function _normalizeCaInput(ca) {
537
+ if (ca === undefined || ca === null) return undefined;
538
+ if (Buffer.isBuffer(ca)) return ca.toString("utf8");
539
+ if (typeof ca === "string") return ca;
540
+ if (!Array.isArray(ca)) {
541
+ throw new NetworkTlsError("network-tls/bad-tls-options",
542
+ "buildOptions: ca must be a PEM string, Buffer, or array thereof");
543
+ }
544
+ var parts = [];
545
+ for (var i = 0; i < ca.length; i += 1) {
546
+ var entry = ca[i];
547
+ if (Buffer.isBuffer(entry)) parts.push(entry.toString("utf8"));
548
+ else if (typeof entry === "string") parts.push(entry);
549
+ else {
550
+ throw new NetworkTlsError("network-tls/bad-tls-options",
551
+ "buildOptions: ca[" + i + "] must be a PEM string or Buffer");
552
+ }
553
+ }
554
+ return parts.join("\n");
555
+ }
556
+
557
+ function buildOptions(opts) {
558
+ opts = opts || {};
559
+ if (typeof opts !== "object" || Array.isArray(opts)) {
560
+ throw new NetworkTlsError("network-tls/bad-tls-options",
561
+ "buildOptions: opts must be a plain object");
562
+ }
563
+ validateOpts(opts,
564
+ ["ecdhCurve", "groups", "cert", "key", "ca", "minVersion", "sni"],
565
+ "network.tls.buildOptions");
566
+ var out = {};
567
+ // TLS-1.3 floor — matches the framework's locked posture in
568
+ // pqc-agent. Operators may pass minVersion: "TLSv1.3" explicitly;
569
+ // anything else fails closed.
570
+ var minV = opts.minVersion === undefined ? "TLSv1.3" : opts.minVersion;
571
+ if (minV !== "TLSv1.3") {
572
+ throw new NetworkTlsError("network-tls/bad-tls-options",
573
+ "buildOptions: minVersion must be 'TLSv1.3' (got " +
574
+ JSON.stringify(opts.minVersion) + ") — framework posture is " +
575
+ "TLS-1.3-only outbound; construct tls.connect opts directly to " +
576
+ "negotiate weaker protocol versions.");
577
+ }
578
+ out.minVersion = minV;
579
+
580
+ // PQC group preference. Caller may narrow (drop a group) but not
581
+ // widen — every requested group must appear in the framework
582
+ // preferred list. Both `groups` (RFC 9794 alias) and `ecdhCurve`
583
+ // (Node TLS option) are accepted; `groups` wins when both supplied.
584
+ var requested = null;
585
+ if (Array.isArray(opts.groups)) {
586
+ requested = opts.groups.slice();
587
+ } else if (typeof opts.groups === "string" && opts.groups.length > 0) {
588
+ requested = opts.groups.split(":");
589
+ } else if (typeof opts.ecdhCurve === "string" && opts.ecdhCurve.length > 0) {
590
+ requested = opts.ecdhCurve.split(":");
591
+ } else if (opts.groups !== undefined || opts.ecdhCurve !== undefined) {
592
+ throw new NetworkTlsError("network-tls/bad-tls-options",
593
+ "buildOptions: groups must be string or string[], ecdhCurve must be string");
594
+ }
595
+ var preferred = STATE.tlsKeyShares.length > 0
596
+ ? STATE.tlsKeyShares.slice()
597
+ : DEFAULT_PQC_KEY_SHARES.slice();
598
+ var resolved;
599
+ if (requested === null) {
600
+ resolved = preferred;
601
+ } else {
602
+ if (requested.length === 0) {
603
+ throw new NetworkTlsError("network-tls/bad-tls-options",
604
+ "buildOptions: groups/ecdhCurve must list at least one named group");
605
+ }
606
+ for (var rgi = 0; rgi < requested.length; rgi += 1) {
607
+ if (typeof requested[rgi] !== "string" || requested[rgi].length === 0) {
608
+ throw new NetworkTlsError("network-tls/bad-tls-options",
609
+ "buildOptions: groups[" + rgi + "] must be a non-empty string");
610
+ }
611
+ if (preferred.indexOf(requested[rgi]) === -1) {
612
+ throw new NetworkTlsError("network-tls/bad-tls-options",
613
+ "buildOptions: group '" + requested[rgi] + "' is not in the " +
614
+ "framework preferred list (" + preferred.join(":") + "); " +
615
+ "construct tls.connect opts directly to negotiate weaker groups.");
616
+ }
617
+ }
618
+ resolved = requested;
619
+ }
620
+ var resolvedStr = resolved.join(":");
621
+ out.ecdhCurve = resolvedStr;
622
+ out.groups = resolvedStr;
623
+
624
+ // cert / key — pass-through with light shape check. Both are
625
+ // typically PEM strings or Buffers; arrays are valid for cert
626
+ // bundles per Node's tls API, so allow array<string|Buffer>.
627
+ if (opts.cert !== undefined) {
628
+ if (!(typeof opts.cert === "string" || Buffer.isBuffer(opts.cert) ||
629
+ Array.isArray(opts.cert))) {
630
+ throw new NetworkTlsError("network-tls/bad-tls-options",
631
+ "buildOptions: cert must be a string, Buffer, or array thereof");
632
+ }
633
+ out.cert = opts.cert;
634
+ }
635
+ if (opts.key !== undefined) {
636
+ if (!(typeof opts.key === "string" || Buffer.isBuffer(opts.key) ||
637
+ Array.isArray(opts.key))) {
638
+ throw new NetworkTlsError("network-tls/bad-tls-options",
639
+ "buildOptions: key must be a string, Buffer, or array thereof");
640
+ }
641
+ out.key = opts.key;
642
+ }
643
+ if (opts.ca !== undefined) out.ca = _normalizeCaInput(opts.ca);
644
+
645
+ // SNI override — Node spells this `servername`.
646
+ if (opts.sni !== undefined) {
647
+ validateOpts.requireNonEmptyString(opts.sni, "buildOptions: sni",
648
+ NetworkTlsError, "network-tls/bad-tls-options");
649
+ out.servername = opts.sni;
650
+ }
651
+ return out;
652
+ }
653
+
488
654
  function _emitAuditAdd(metaList, opts) {
489
655
  if (opts.audit === false) return;
490
656
  var sink;
@@ -1686,6 +1852,186 @@ function verifyScts(certDer, opts) {
1686
1852
  };
1687
1853
  }
1688
1854
 
1855
+ // ---- RFC 9162 §2.1 Merkle tree primitives ----
1856
+ //
1857
+ // CT v2 (RFC 9162) inclusion + consistency proofs operate on a binary
1858
+ // Merkle tree with the following node hashes (RFC 9162 §2.1.1):
1859
+ //
1860
+ // MTH(empty) = SHA-256("") — empty tree
1861
+ // MTH({d}) = SHA-256(0x00 || d) — leaf
1862
+ // MTH(D) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n])) — internal
1863
+ //
1864
+ // SHA-256 is the algorithm RFC 9162 mandates; the framework's PQC-first
1865
+ // posture does not apply here because the algorithm is wire-defined
1866
+ // by the CT log itself and changing it would break interop with every
1867
+ // public log. A future SHA3-flavoured CT (no draft as of writing) ships
1868
+ // alongside, not in place.
1869
+ //
1870
+ // LEAF_HASH_PREFIX = 0x00
1871
+ // INNER_HASH_PREFIX = 0x01
1872
+ // k = largest power of 2 < n (RFC 9162 §2.1.1)
1873
+
1874
+ var CT_LEAF_HASH_PREFIX = 0x00;
1875
+ var CT_INNER_HASH_PREFIX = 0x01;
1876
+
1877
+ function _ctSha256(buf) {
1878
+ return nodeCrypto.createHash("sha256").update(buf).digest();
1879
+ }
1880
+ function _ctLeafHash(leafBytes) {
1881
+ return _ctSha256(Buffer.concat([Buffer.from([CT_LEAF_HASH_PREFIX]), leafBytes]));
1882
+ }
1883
+ function _ctInnerHash(left, right) {
1884
+ return Buffer.concat([Buffer.from([CT_INNER_HASH_PREFIX]), left, right]);
1885
+ }
1886
+ function _ctInnerHashFinal(left, right) {
1887
+ return _ctSha256(_ctInnerHash(left, right));
1888
+ }
1889
+
1890
+ // _ctLargestPowerOf2LessThan — k from RFC 9162 §2.1.1: the largest
1891
+ // power of 2 that is strictly less than n. n must be > 1.
1892
+ function _ctLargestPowerOf2LessThan(n) {
1893
+ if (n < 2) {
1894
+ throw new TlsTrustError("tls/ct-bad-tree-size",
1895
+ "ct: largest-power-of-2-less-than requires n >= 2 (got " + n + ")");
1896
+ }
1897
+ var k = 1;
1898
+ while ((k << 1) < n) k = k << 1;
1899
+ return k;
1900
+ }
1901
+
1902
+ // _ctVerifyInclusion — RFC 9162 §2.1.3 algorithm. Walks the audit path
1903
+ // from the leaf hash up to the tree's expected root using the supplied
1904
+ // audit path siblings. The leafIndex (0-based) selects which side at
1905
+ // each level the leaf sits on; the audit path provides the sibling
1906
+ // hash for that level.
1907
+ //
1908
+ // args:
1909
+ // leafHash: Buffer (32 bytes) — MTH({d}) of the leaf
1910
+ // leafIndex: integer 0 <= idx < treeSize
1911
+ // treeSize: integer >= 1
1912
+ // auditPath: Array of Buffer (each 32 bytes) — siblings bottom-up
1913
+ //
1914
+ // returns: Buffer (32 bytes) — computed root hash to compare
1915
+ // throws: TlsTrustError on shape errors
1916
+ function _ctVerifyInclusionPath(leafHash, leafIndex, treeSize, auditPath) {
1917
+ if (!Buffer.isBuffer(leafHash) || leafHash.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
1918
+ throw new TlsTrustError("tls/ct-bad-leaf-hash",
1919
+ "ct.verifyInclusion: leafHash must be a 32-byte Buffer");
1920
+ }
1921
+ if (typeof leafIndex !== "number" || leafIndex < 0 || leafIndex >= treeSize ||
1922
+ Math.floor(leafIndex) !== leafIndex) {
1923
+ throw new TlsTrustError("tls/ct-bad-index",
1924
+ "ct.verifyInclusion: leafIndex must be an integer 0..treeSize-1");
1925
+ }
1926
+ if (typeof treeSize !== "number" || treeSize < 1 || Math.floor(treeSize) !== treeSize) {
1927
+ throw new TlsTrustError("tls/ct-bad-tree-size",
1928
+ "ct.verifyInclusion: treeSize must be a positive integer");
1929
+ }
1930
+ if (!Array.isArray(auditPath)) {
1931
+ throw new TlsTrustError("tls/ct-bad-audit-path",
1932
+ "ct.verifyInclusion: auditPath must be an array of 32-byte Buffers");
1933
+ }
1934
+
1935
+ // Per RFC 9162 §2.1.3 — climb the tree using the audit path. fn=leafIndex,
1936
+ // sn=treeSize-1 (last index in the tree at this level). Pop one
1937
+ // sibling from the audit path per level.
1938
+ var fn = leafIndex;
1939
+ var sn = treeSize - 1;
1940
+ var r = leafHash;
1941
+ var pathPos = 0;
1942
+ while (sn > 0) {
1943
+ if (pathPos >= auditPath.length) {
1944
+ throw new TlsTrustError("tls/ct-audit-path-short",
1945
+ "ct.verifyInclusion: audit path exhausted before tree root reached");
1946
+ }
1947
+ var sibling = auditPath[pathPos++];
1948
+ if (!Buffer.isBuffer(sibling) || sibling.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
1949
+ throw new TlsTrustError("tls/ct-bad-audit-path",
1950
+ "ct.verifyInclusion: audit path entry " + (pathPos - 1) + " is not a 32-byte Buffer");
1951
+ }
1952
+ if ((fn & 1) === 1 || fn === sn) {
1953
+ r = _ctInnerHashFinal(sibling, r);
1954
+ // Right-side leaf — climb until we hit a left-side ancestor.
1955
+ while ((fn & 1) === 0 && fn !== 0) { fn >>>= 1; sn >>>= 1; }
1956
+ } else {
1957
+ r = _ctInnerHashFinal(r, sibling);
1958
+ }
1959
+ fn >>>= 1;
1960
+ sn >>>= 1;
1961
+ }
1962
+ if (pathPos !== auditPath.length) {
1963
+ throw new TlsTrustError("tls/ct-audit-path-long",
1964
+ "ct.verifyInclusion: audit path has " + (auditPath.length - pathPos) +
1965
+ " trailing entries beyond the root");
1966
+ }
1967
+ return r;
1968
+ }
1969
+
1970
+ // _ctVerifyConsistency — RFC 9162 §2.1.4 consistency proof verification.
1971
+ // Given a first STH (size m) and a second STH (size n, n >= m), the
1972
+ // consistency proof shows the second tree contains the first tree as a
1973
+ // prefix. Returns the computed roots (oldRoot, newRoot) so the caller
1974
+ // can compare against the operator-supplied STHs.
1975
+ function _ctVerifyConsistencyPath(m, n, consistencyProof, firstHash) {
1976
+ if (typeof m !== "number" || m < 1 || Math.floor(m) !== m) {
1977
+ throw new TlsTrustError("tls/ct-bad-first-size",
1978
+ "ct.verifyConsistency: m (first tree size) must be a positive integer");
1979
+ }
1980
+ if (typeof n !== "number" || n < m || Math.floor(n) !== n) {
1981
+ throw new TlsTrustError("tls/ct-bad-second-size",
1982
+ "ct.verifyConsistency: n (second tree size) must be an integer >= m");
1983
+ }
1984
+ if (!Buffer.isBuffer(firstHash) || firstHash.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
1985
+ throw new TlsTrustError("tls/ct-bad-first-hash",
1986
+ "ct.verifyConsistency: firstHash must be a 32-byte Buffer");
1987
+ }
1988
+ if (!Array.isArray(consistencyProof)) {
1989
+ throw new TlsTrustError("tls/ct-bad-consistency-proof",
1990
+ "ct.verifyConsistency: consistencyProof must be an array of Buffers");
1991
+ }
1992
+ // RFC 9162 §2.1.4.2 — algorithm is the same as the inclusion-proof
1993
+ // walk, with the leaf-index seeded at the first-tree size minus 1 and
1994
+ // the special case for m being a complete subtree.
1995
+ var path = consistencyProof.slice();
1996
+ var node;
1997
+ var fn = m - 1;
1998
+ var sn = n - 1;
1999
+ // Walk past the right-side bits — the consistency proof omits the
2000
+ // path while the first tree is a complete subtree of the second.
2001
+ while ((fn & 1) === 1) { fn >>>= 1; sn >>>= 1; }
2002
+
2003
+ if (fn === 0) {
2004
+ // m was a complete subtree — its root is the firstHash itself.
2005
+ node = firstHash;
2006
+ } else {
2007
+ if (path.length === 0) {
2008
+ throw new TlsTrustError("tls/ct-consistency-empty",
2009
+ "ct.verifyConsistency: consistency proof empty but first tree is not a complete subtree");
2010
+ }
2011
+ node = path.shift();
2012
+ }
2013
+ while (sn > 0) {
2014
+ if (path.length === 0) {
2015
+ throw new TlsTrustError("tls/ct-consistency-short",
2016
+ "ct.verifyConsistency: consistency proof exhausted before second-tree root");
2017
+ }
2018
+ var sibling = path.shift();
2019
+ if (!Buffer.isBuffer(sibling) || sibling.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
2020
+ throw new TlsTrustError("tls/ct-bad-consistency-entry",
2021
+ "ct.verifyConsistency: consistency-proof entry is not a 32-byte Buffer");
2022
+ }
2023
+ if ((fn & 1) === 1 || fn === sn) {
2024
+ node = _ctInnerHashFinal(sibling, node);
2025
+ while ((fn & 1) === 0 && fn !== 0) { fn >>>= 1; sn >>>= 1; }
2026
+ } else {
2027
+ node = _ctInnerHashFinal(node, sibling);
2028
+ }
2029
+ fn >>>= 1;
2030
+ sn >>>= 1;
2031
+ }
2032
+ return node;
2033
+ }
2034
+
1689
2035
  function _findSctOid(rawDer) {
1690
2036
  // Cheap presence check — used by inspect() before ASN.1 walking.
1691
2037
  // OID 1.3.6.1.4.1.11129.2.4.2 = 06 0A 2B 06 01 04 01 D6 79 02 04 02.
@@ -1726,6 +2072,187 @@ var ct = Object.freeze({
1726
2072
  verifyScts: verifyScts,
1727
2073
  // Operator middleware predicate: refuse a peer cert lacking SCT
1728
2074
  // verification. Composes verifyScts under the hood.
2075
+ // verifyInclusion — RFC 9162 §4.5/§5.1 inclusion-proof verifier.
2076
+ // Composes with inspect() / parseScts() / verifyScts() for the
2077
+ // signature side: an SCT proves a log promised to include the cert,
2078
+ // and verifyInclusion proves that promise was kept (the leaf actually
2079
+ // sits in the published tree).
2080
+ //
2081
+ // opts: {
2082
+ // sct: { logIdHex, timestamp, signedEntryDer? } — from parseScts
2083
+ // leafCertificate: Buffer — leaf cert DER (the entry hashed at the leaf)
2084
+ // leafIndex: integer — position in the tree (from RFC 9162 §6.7 get-proof-by-hash)
2085
+ // auditPath: [Buffer] — the inclusion-proof siblings, bottom-up
2086
+ // sthFromLog: { treeSize, rootHash[, sha256RootHash] }
2087
+ // — operator fetched the signed tree head from the log
2088
+ // (RFC 9162 §6.4 get-sth) and supplies treeSize +
2089
+ // rootHash (32-byte Buffer or hex string)
2090
+ // consistency: { firstSize, firstRoot, proof } — optional
2091
+ // — when provided, also verifies that the supplied
2092
+ // STH is consistent with an earlier STH the operator
2093
+ // pinned (RFC 9162 §6.5 get-sth-consistency)
2094
+ // }
2095
+ //
2096
+ // returns: { valid: bool, reason?: string, computedRoot?: hex,
2097
+ // consistency?: { ok, computedSecondRoot? } }
2098
+ verifyInclusion: function (opts) {
2099
+ if (!opts || typeof opts !== "object") {
2100
+ return { valid: false, reason: "missing-opts" };
2101
+ }
2102
+ if (!opts.sct || typeof opts.sct !== "object") {
2103
+ return { valid: false, reason: "missing-sct" };
2104
+ }
2105
+ if (!Buffer.isBuffer(opts.leafCertificate)) {
2106
+ return { valid: false, reason: "missing-leaf-certificate" };
2107
+ }
2108
+ if (!opts.sthFromLog || typeof opts.sthFromLog !== "object") {
2109
+ return { valid: false, reason: "missing-sth" };
2110
+ }
2111
+ if (typeof opts.leafIndex !== "number" || !isFinite(opts.leafIndex) ||
2112
+ opts.leafIndex < 0 || Math.floor(opts.leafIndex) !== opts.leafIndex) {
2113
+ return { valid: false, reason: "bad-leaf-index" };
2114
+ }
2115
+ if (!Array.isArray(opts.auditPath)) {
2116
+ return { valid: false, reason: "bad-audit-path" };
2117
+ }
2118
+
2119
+ // Build the leaf bytes per RFC 9162 §4.6 — TimestampedEntry.
2120
+ // entry_type = x509_entry (0); signed_entry = strip-SCT-extension(cert).
2121
+ // Operators may pass a pre-built signedEntryDer when the SCT was
2122
+ // already extracted via parseScts() + the framework has the
2123
+ // pre-issuance cert; otherwise we strip the SCT extension here.
2124
+ var signedEntryDer = opts.sct.signedEntryDer;
2125
+ if (!Buffer.isBuffer(signedEntryDer)) {
2126
+ try { signedEntryDer = _stripSctExtensionFromCert(opts.leafCertificate); }
2127
+ catch (e) {
2128
+ return { valid: false, reason: "strip-failed",
2129
+ error: (e && e.message) || String(e) };
2130
+ }
2131
+ }
2132
+
2133
+ // RFC 9162 §4.6 MerkleTreeLeaf — version (1) + leaf_type (0) +
2134
+ // timestamp (uint64) + entry_type (uint16) + signed_entry (variable-
2135
+ // length cert DER with 24-bit length prefix) + extensions (variable-
2136
+ // length, 16-bit length prefix, empty for x509_entry).
2137
+ var ts = opts.sct.timestamp;
2138
+ if (typeof ts !== "number" && typeof ts !== "bigint") {
2139
+ return { valid: false, reason: "bad-sct-timestamp" };
2140
+ }
2141
+ var tsBuf = Buffer.alloc(8); // allow:raw-byte-literal — TLS uint64 width
2142
+ var tsBig = typeof ts === "bigint" ? ts : BigInt(Math.floor(ts));
2143
+ tsBuf.writeBigUInt64BE(tsBig);
2144
+ var entryTypeBuf = Buffer.from([0x00, 0x00]);
2145
+ var lenBuf = Buffer.alloc(3); // allow:raw-byte-literal — TLS uint24 length prefix
2146
+ lenBuf.writeUIntBE(signedEntryDer.length, 0, 3);
2147
+ var extensionsBuf = Buffer.from([0x00, 0x00]); // allow:raw-byte-literal — empty extensions vector
2148
+ var leafBytes = Buffer.concat([
2149
+ Buffer.from([0x00]), // version v1
2150
+ Buffer.from([0x00]), // leaf_type timestamped_entry
2151
+ tsBuf,
2152
+ entryTypeBuf,
2153
+ lenBuf, signedEntryDer,
2154
+ extensionsBuf,
2155
+ ]);
2156
+
2157
+ var leafHash = _ctLeafHash(leafBytes);
2158
+ var computedRoot;
2159
+ try {
2160
+ computedRoot = _ctVerifyInclusionPath(leafHash, opts.leafIndex,
2161
+ opts.sthFromLog.treeSize, opts.auditPath);
2162
+ } catch (e) {
2163
+ return { valid: false, reason: "inclusion-walk-failed",
2164
+ error: (e && e.message) || String(e) };
2165
+ }
2166
+
2167
+ // sthFromLog.rootHash may be a Buffer or hex string.
2168
+ var sthRoot = opts.sthFromLog.rootHash || opts.sthFromLog.sha256RootHash;
2169
+ if (typeof sthRoot === "string") {
2170
+ try { sthRoot = Buffer.from(sthRoot, "hex"); }
2171
+ catch (_e) { return { valid: false, reason: "bad-sth-root-encoding" }; }
2172
+ }
2173
+ if (!Buffer.isBuffer(sthRoot) || sthRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
2174
+ return { valid: false, reason: "bad-sth-root" };
2175
+ }
2176
+ if (!blamejsCrypto.timingSafeEqual(computedRoot, sthRoot)) {
2177
+ return { valid: false, reason: "root-mismatch",
2178
+ computedRoot: computedRoot.toString("hex") };
2179
+ }
2180
+
2181
+ // Optional consistency proof — RFC 9162 §2.1.4.
2182
+ var consistencyResult = null;
2183
+ if (opts.consistency && typeof opts.consistency === "object") {
2184
+ var firstRoot = opts.consistency.firstRoot;
2185
+ if (typeof firstRoot === "string") {
2186
+ try { firstRoot = Buffer.from(firstRoot, "hex"); }
2187
+ catch (_e) {
2188
+ return { valid: false, reason: "bad-consistency-first-root-encoding" };
2189
+ }
2190
+ }
2191
+ try {
2192
+ var computedSecond = _ctVerifyConsistencyPath(
2193
+ opts.consistency.firstSize, opts.sthFromLog.treeSize,
2194
+ opts.consistency.proof || [], firstRoot);
2195
+ var ok = blamejsCrypto.timingSafeEqual(computedSecond, sthRoot);
2196
+ consistencyResult = {
2197
+ ok: ok,
2198
+ computedSecondRoot: computedSecond.toString("hex"),
2199
+ };
2200
+ if (!ok) {
2201
+ return { valid: false, reason: "consistency-mismatch",
2202
+ computedRoot: computedRoot.toString("hex"),
2203
+ consistency: consistencyResult };
2204
+ }
2205
+ } catch (e) {
2206
+ return { valid: false, reason: "consistency-walk-failed",
2207
+ error: (e && e.message) || String(e) };
2208
+ }
2209
+ }
2210
+
2211
+ return {
2212
+ valid: true,
2213
+ computedRoot: computedRoot.toString("hex"),
2214
+ leafHash: leafHash.toString("hex"),
2215
+ consistency: consistencyResult,
2216
+ };
2217
+ },
2218
+ // verifyConsistency — standalone RFC 9162 §2.1.4 consistency-proof
2219
+ // verifier. Operators pinning historical tree-head fingerprints call
2220
+ // this whenever they fetch a fresh STH to confirm the log hasn't
2221
+ // forked. Returns { valid, computedRoot } / { valid:false, reason }.
2222
+ verifyConsistency: function (opts) {
2223
+ if (!opts || typeof opts !== "object") {
2224
+ return { valid: false, reason: "missing-opts" };
2225
+ }
2226
+ var firstRoot = opts.firstRoot;
2227
+ if (typeof firstRoot === "string") {
2228
+ try { firstRoot = Buffer.from(firstRoot, "hex"); }
2229
+ catch (_e) { return { valid: false, reason: "bad-first-root-encoding" }; }
2230
+ }
2231
+ var secondRoot = opts.secondRoot;
2232
+ if (typeof secondRoot === "string") {
2233
+ try { secondRoot = Buffer.from(secondRoot, "hex"); }
2234
+ catch (_e) { return { valid: false, reason: "bad-second-root-encoding" }; }
2235
+ }
2236
+ if (!Buffer.isBuffer(firstRoot) || firstRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
2237
+ return { valid: false, reason: "bad-first-root" };
2238
+ }
2239
+ if (!Buffer.isBuffer(secondRoot) || secondRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
2240
+ return { valid: false, reason: "bad-second-root" };
2241
+ }
2242
+ var computed;
2243
+ try {
2244
+ computed = _ctVerifyConsistencyPath(opts.firstSize, opts.secondSize,
2245
+ opts.proof || [], firstRoot);
2246
+ } catch (e) {
2247
+ return { valid: false, reason: "consistency-walk-failed",
2248
+ error: (e && e.message) || String(e) };
2249
+ }
2250
+ if (!blamejsCrypto.timingSafeEqual(computed, secondRoot)) {
2251
+ return { valid: false, reason: "root-mismatch",
2252
+ computedRoot: computed.toString("hex") };
2253
+ }
2254
+ return { valid: true, computedRoot: computed.toString("hex") };
2255
+ },
1729
2256
  requireScts: function (opts) {
1730
2257
  opts = opts || {};
1731
2258
  return function (peerCert) {
@@ -1766,10 +2293,13 @@ module.exports = {
1766
2293
  captureBaselineFingerprints: captureBaselineFingerprints,
1767
2294
  detectBaselineDrift: detectBaselineDrift,
1768
2295
  applyToContext: applyToContext,
2296
+ buildOptions: buildOptions,
1769
2297
  getCaPems: getCaPems,
1770
2298
  ocsp: ocsp,
1771
2299
  ct: ct,
1772
2300
  pqc: pqc,
2301
+ preferredGroups: preferredGroups,
1773
2302
  TlsTrustError: TlsTrustError,
2303
+ NetworkTlsError: NetworkTlsError,
1774
2304
  _resetForTest: _resetForTest,
1775
2305
  };