@blamejs/core 0.8.43 → 0.8.50
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.
- package/CHANGELOG.md +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/network-tls.js
CHANGED
|
@@ -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: ["
|
|
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
|
-
"
|
|
440
|
-
"
|
|
441
|
-
"
|
|
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
|
};
|