@blamejs/blamejs-shop 0.0.44
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 +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,3126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var nodeTls = require("node:tls");
|
|
4
|
+
var nodeFs = require("node:fs");
|
|
5
|
+
var nodePath = require("node:path");
|
|
6
|
+
var net = require("node:net");
|
|
7
|
+
var nodeCrypto = require("node:crypto");
|
|
8
|
+
|
|
9
|
+
var bCrypto = require("./crypto");
|
|
10
|
+
var C = require("./constants");
|
|
11
|
+
var safeBuffer = require("./safe-buffer");
|
|
12
|
+
var validateOpts = require("./validate-opts");
|
|
13
|
+
var lazyRequire = require("./lazy-require");
|
|
14
|
+
var safeAsync = require("./safe-async");
|
|
15
|
+
var { defineClass } = require("./framework-error");
|
|
16
|
+
|
|
17
|
+
var TlsTrustError = defineClass("TlsTrustError", { alwaysPermanent: true });
|
|
18
|
+
var NetworkTlsError = defineClass("NetworkTlsError", { alwaysPermanent: true });
|
|
19
|
+
|
|
20
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
21
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
22
|
+
var networkDns = lazyRequire(function () { return require("./network-dns"); });
|
|
23
|
+
var asn1 = require("./asn1-der");
|
|
24
|
+
|
|
25
|
+
// STATE.tlsKeyShares is initialized to the default PQC group list at
|
|
26
|
+
// module load — operator setKeyShares() overrides; resetKeyShares()
|
|
27
|
+
// restores the default. Empty array means "fall back to Node's TLS
|
|
28
|
+
// default groups" (operator opt-out).
|
|
29
|
+
var STATE = {
|
|
30
|
+
cas: [],
|
|
31
|
+
systemTrust: false,
|
|
32
|
+
baselineFingerprints: null,
|
|
33
|
+
tlsKeyShares: ["X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024", "X25519"],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function _normalizePem(pem) {
|
|
37
|
+
if (Buffer.isBuffer(pem)) pem = pem.toString("utf8");
|
|
38
|
+
if (typeof pem !== "string") {
|
|
39
|
+
throw new TlsTrustError("tls/bad-ca", "CA must be a PEM string or path, got " + typeof pem);
|
|
40
|
+
}
|
|
41
|
+
return pem.replace(/\r\n/g, "\n").trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _splitPemBundle(pem) {
|
|
45
|
+
var blocks = [];
|
|
46
|
+
var lines = pem.split("\n");
|
|
47
|
+
var current = null;
|
|
48
|
+
for (var i = 0; i < lines.length; i++) {
|
|
49
|
+
var line = lines[i];
|
|
50
|
+
if (line.indexOf("-----BEGIN CERTIFICATE-----") === 0) {
|
|
51
|
+
current = [line];
|
|
52
|
+
} else if (current) {
|
|
53
|
+
current.push(line);
|
|
54
|
+
if (line.indexOf("-----END CERTIFICATE-----") === 0) {
|
|
55
|
+
blocks.push(current.join("\n"));
|
|
56
|
+
current = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return blocks;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _certMetadata(pem) {
|
|
64
|
+
try {
|
|
65
|
+
var x = new nodeCrypto.X509Certificate(pem);
|
|
66
|
+
return {
|
|
67
|
+
subject: x.subject,
|
|
68
|
+
issuer: x.issuer,
|
|
69
|
+
validFrom: x.validFrom,
|
|
70
|
+
validTo: x.validTo,
|
|
71
|
+
fingerprint256: x.fingerprint256,
|
|
72
|
+
serialNumber: x.serialNumber,
|
|
73
|
+
isSelfSigned: x.subject === x.issuer,
|
|
74
|
+
};
|
|
75
|
+
} catch (e) {
|
|
76
|
+
throw new TlsTrustError("tls/bad-ca-pem", "CA PEM not parseable: " + e.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function _isPathLike(s) {
|
|
81
|
+
if (s.indexOf("-----BEGIN") !== -1) return false;
|
|
82
|
+
if (s.length > C.BYTES.kib(1)) return false;
|
|
83
|
+
if (safeBuffer.hasCrlf(s)) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// CodeQL js/file-system-race defense — fd-based read binds the size +
|
|
88
|
+
// content measurement to the inode the fd holds open. The cert path is
|
|
89
|
+
// operator-supplied (tls.addCa) but routing through openSync + fstatSync
|
|
90
|
+
// + readSync narrows the race window vs the prior statSync + readFileSync
|
|
91
|
+
// shape, where an attacker who could swap the file in-between could
|
|
92
|
+
// short-circuit the PEM marker check downstream.
|
|
93
|
+
function _readPathFile(p) {
|
|
94
|
+
var fd = nodeFs.openSync(p, "r");
|
|
95
|
+
try {
|
|
96
|
+
var fstat = nodeFs.fstatSync(fd);
|
|
97
|
+
var buf = Buffer.alloc(fstat.size);
|
|
98
|
+
var read = 0;
|
|
99
|
+
while (read < fstat.size) {
|
|
100
|
+
var n = nodeFs.readSync(fd, buf, read, fstat.size - read, null);
|
|
101
|
+
if (n === 0) break;
|
|
102
|
+
read += n;
|
|
103
|
+
}
|
|
104
|
+
return buf.slice(0, read).toString("utf8");
|
|
105
|
+
} finally {
|
|
106
|
+
try { nodeFs.closeSync(fd); } catch (_c) { /* close best-effort */ }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function _readPath(p) {
|
|
111
|
+
var stat = nodeFs.statSync(p);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
var files = nodeFs.readdirSync(p)
|
|
114
|
+
.filter(function (f) { return /\.(pem|crt|cer)$/i.test(f); })
|
|
115
|
+
.sort();
|
|
116
|
+
return files.map(function (f) { return _readPathFile(nodePath.join(p, f)); }).join("\n");
|
|
117
|
+
}
|
|
118
|
+
return _readPathFile(p);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function addCa(pemOrPath, opts) {
|
|
122
|
+
opts = opts || {};
|
|
123
|
+
validateOpts(opts, ["label", "audit"], "tls.addCa");
|
|
124
|
+
var raw = pemOrPath;
|
|
125
|
+
if (typeof pemOrPath === "string" && _isPathLike(pemOrPath)) {
|
|
126
|
+
var stat;
|
|
127
|
+
try { stat = nodeFs.statSync(pemOrPath); } catch (_e) {
|
|
128
|
+
throw new TlsTrustError("tls/empty-pem", "tls.addCa: input has no PEM marker and is not a readable path: " +
|
|
129
|
+
pemOrPath);
|
|
130
|
+
}
|
|
131
|
+
raw = _readPath(pemOrPath);
|
|
132
|
+
if (!stat) raw = "";
|
|
133
|
+
}
|
|
134
|
+
raw = _normalizePem(raw);
|
|
135
|
+
var blocks = _splitPemBundle(raw);
|
|
136
|
+
if (blocks.length === 0) {
|
|
137
|
+
throw new TlsTrustError("tls/empty-pem", "no CERTIFICATE blocks found in PEM input");
|
|
138
|
+
}
|
|
139
|
+
var added = [];
|
|
140
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
141
|
+
var meta = _certMetadata(blocks[i]);
|
|
142
|
+
STATE.cas.push({ pem: blocks[i], meta: meta, label: opts.label || null, addedAt: Date.now() });
|
|
143
|
+
added.push(meta);
|
|
144
|
+
}
|
|
145
|
+
_emitAuditAdd(added, opts);
|
|
146
|
+
_emitObs("network.tls.ca.added", { count: added.length });
|
|
147
|
+
return added;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function addCaBundle(p, opts) {
|
|
151
|
+
return addCa(p, opts);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function useSystemTrust(enable) {
|
|
155
|
+
STATE.systemTrust = enable !== false;
|
|
156
|
+
_emitObs("network.tls.system_trust.set", { enabled: STATE.systemTrust });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isSystemTrustEnabled() { return !!STATE.systemTrust; }
|
|
160
|
+
|
|
161
|
+
function getTrustStore() {
|
|
162
|
+
return STATE.cas.map(function (entry) {
|
|
163
|
+
return {
|
|
164
|
+
label: entry.label,
|
|
165
|
+
addedAt: entry.addedAt,
|
|
166
|
+
subject: entry.meta.subject,
|
|
167
|
+
issuer: entry.meta.issuer,
|
|
168
|
+
validFrom: entry.meta.validFrom,
|
|
169
|
+
validTo: entry.meta.validTo,
|
|
170
|
+
fingerprint256: entry.meta.fingerprint256,
|
|
171
|
+
serialNumber: entry.meta.serialNumber,
|
|
172
|
+
isSelfSigned: entry.meta.isSelfSigned,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function _emitAuditRemove(metaList, reason) {
|
|
178
|
+
var sink;
|
|
179
|
+
try { sink = audit(); } catch (_e) { sink = null; }
|
|
180
|
+
if (!sink || typeof sink.safeEmit !== "function") return;
|
|
181
|
+
for (var i = 0; i < metaList.length; i++) {
|
|
182
|
+
var m = metaList[i];
|
|
183
|
+
try {
|
|
184
|
+
sink.safeEmit({
|
|
185
|
+
action: "network.tls.ca.removed",
|
|
186
|
+
outcome: "success",
|
|
187
|
+
metadata: {
|
|
188
|
+
subject: m.subject,
|
|
189
|
+
issuer: m.issuer,
|
|
190
|
+
fingerprint256: m.fingerprint256,
|
|
191
|
+
validFrom: m.validFrom,
|
|
192
|
+
validTo: m.validTo,
|
|
193
|
+
isSelfSigned: m.isSelfSigned,
|
|
194
|
+
label: m.label,
|
|
195
|
+
reason: reason || "operator",
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function removeCa(fingerprint256, opts) {
|
|
203
|
+
if (typeof fingerprint256 !== "string" || fingerprint256.length === 0) {
|
|
204
|
+
throw new TlsTrustError("tls/bad-fingerprint", "tls.removeCa: fingerprint256 must be a non-empty string");
|
|
205
|
+
}
|
|
206
|
+
var fp = fingerprint256.toUpperCase();
|
|
207
|
+
var removed = [];
|
|
208
|
+
STATE.cas = STATE.cas.filter(function (entry) {
|
|
209
|
+
var entryFp = (entry.meta.fingerprint256 || "").toUpperCase();
|
|
210
|
+
if (entryFp === fp) {
|
|
211
|
+
removed.push(Object.assign({ label: entry.label }, entry.meta));
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
});
|
|
216
|
+
if (removed.length === 0) return 0;
|
|
217
|
+
if (!opts || opts.audit !== false) _emitAuditRemove(removed, "operator-remove");
|
|
218
|
+
_emitObs("network.tls.ca.removed", { count: removed.length, reason: "operator" });
|
|
219
|
+
return removed.length;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function removeCaByLabel(label, opts) {
|
|
223
|
+
if (typeof label !== "string" || label.length === 0) {
|
|
224
|
+
throw new TlsTrustError("tls/bad-label", "tls.removeCaByLabel: label must be a non-empty string");
|
|
225
|
+
}
|
|
226
|
+
var removed = [];
|
|
227
|
+
STATE.cas = STATE.cas.filter(function (entry) {
|
|
228
|
+
if (entry.label === label) {
|
|
229
|
+
removed.push(Object.assign({ label: entry.label }, entry.meta));
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
});
|
|
234
|
+
if (removed.length === 0) return 0;
|
|
235
|
+
if (!opts || opts.audit !== false) _emitAuditRemove(removed, "operator-remove-by-label");
|
|
236
|
+
_emitObs("network.tls.ca.removed", { count: removed.length, reason: "label" });
|
|
237
|
+
return removed.length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function clearAll(opts) {
|
|
241
|
+
if (STATE.cas.length === 0) return 0;
|
|
242
|
+
var removed = STATE.cas.map(function (e) { return Object.assign({ label: e.label }, e.meta); });
|
|
243
|
+
STATE.cas = [];
|
|
244
|
+
if (!opts || opts.audit !== false) _emitAuditRemove(removed, "operator-clear-all");
|
|
245
|
+
_emitObs("network.tls.ca.cleared", { count: removed.length });
|
|
246
|
+
return removed.length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function purgeExpired(opts) {
|
|
250
|
+
var nowMs = Date.now();
|
|
251
|
+
var removed = [];
|
|
252
|
+
STATE.cas = STATE.cas.filter(function (entry) {
|
|
253
|
+
var validToMs = entry.meta.validTo ? Date.parse(entry.meta.validTo) : NaN;
|
|
254
|
+
if (isFinite(validToMs) && validToMs < nowMs) {
|
|
255
|
+
removed.push(Object.assign({ label: entry.label }, entry.meta));
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
});
|
|
260
|
+
if (removed.length === 0) return 0;
|
|
261
|
+
if (!opts || opts.audit !== false) _emitAuditRemove(removed, "expired");
|
|
262
|
+
_emitObs("network.tls.ca.purged_expired", { count: removed.length });
|
|
263
|
+
return removed.length;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function expiringSoon(windowMs) {
|
|
267
|
+
if (typeof windowMs !== "number" || !isFinite(windowMs) || windowMs < 0) {
|
|
268
|
+
throw new TlsTrustError("tls/bad-window", "tls.expiringSoon: windowMs must be a non-negative finite number");
|
|
269
|
+
}
|
|
270
|
+
var threshold = Date.now() + windowMs;
|
|
271
|
+
return STATE.cas.filter(function (entry) {
|
|
272
|
+
var validToMs = entry.meta.validTo ? Date.parse(entry.meta.validTo) : NaN;
|
|
273
|
+
return isFinite(validToMs) && validToMs <= threshold;
|
|
274
|
+
}).map(function (entry) {
|
|
275
|
+
return Object.assign({ label: entry.label }, entry.meta);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// expiryMonitor — periodic check that emits audit + observability
|
|
280
|
+
// events when any CA in the trust store falls inside the expiry
|
|
281
|
+
// window. Returns a handle with .stop() for graceful shutdown.
|
|
282
|
+
//
|
|
283
|
+
// var mon = b.network.tls.expiryMonitor({
|
|
284
|
+
// intervalMs: C.TIME.hours(6),
|
|
285
|
+
// windowMs: C.TIME.days(30),
|
|
286
|
+
// onExpiring: function (rows) { /* operator hook — alerts */ },
|
|
287
|
+
// });
|
|
288
|
+
// ...
|
|
289
|
+
// mon.stop();
|
|
290
|
+
//
|
|
291
|
+
// Audit emissions:
|
|
292
|
+
// network.tls.ca.expiry_check — every check, reports total + expiring count
|
|
293
|
+
// network.tls.ca.expiring — when expiringSoon(windowMs) > 0
|
|
294
|
+
//
|
|
295
|
+
// Observability event: network.tls.ca.expiring counter labeled with
|
|
296
|
+
// the count.
|
|
297
|
+
function expiryMonitor(opts) {
|
|
298
|
+
opts = opts || {};
|
|
299
|
+
var intervalMs = opts.intervalMs;
|
|
300
|
+
var windowMs = opts.windowMs;
|
|
301
|
+
var auditOn = opts.audit !== false;
|
|
302
|
+
if (typeof intervalMs !== "number" || !isFinite(intervalMs) || intervalMs <= 0) {
|
|
303
|
+
throw new TlsTrustError("tls/bad-interval",
|
|
304
|
+
"tls.expiryMonitor: intervalMs must be a positive finite number");
|
|
305
|
+
}
|
|
306
|
+
if (typeof windowMs !== "number" || !isFinite(windowMs) || windowMs <= 0) {
|
|
307
|
+
throw new TlsTrustError("tls/bad-window",
|
|
308
|
+
"tls.expiryMonitor: windowMs must be a positive finite number");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function _tick() {
|
|
312
|
+
var rows;
|
|
313
|
+
try { rows = expiringSoon(windowMs); }
|
|
314
|
+
catch (_e) { return; }
|
|
315
|
+
if (auditOn) {
|
|
316
|
+
try {
|
|
317
|
+
audit().safeEmit({
|
|
318
|
+
action: "network.tls.ca.expiry_check",
|
|
319
|
+
outcome: rows.length > 0 ? "warn" : "ok",
|
|
320
|
+
metadata: { total: STATE.cas.length, expiring: rows.length, windowMs: windowMs },
|
|
321
|
+
});
|
|
322
|
+
} catch (_e) { /* drop-silent */ }
|
|
323
|
+
}
|
|
324
|
+
if (rows.length > 0) {
|
|
325
|
+
try { observability().safeEvent("network.tls.ca.expiring", rows.length, {}); }
|
|
326
|
+
catch (_e) { /* drop-silent */ }
|
|
327
|
+
if (auditOn) {
|
|
328
|
+
try {
|
|
329
|
+
audit().safeEmit({
|
|
330
|
+
action: "network.tls.ca.expiring",
|
|
331
|
+
outcome: "success",
|
|
332
|
+
metadata: {
|
|
333
|
+
count: rows.length,
|
|
334
|
+
labels: rows.map(function (r) { return r.label; }),
|
|
335
|
+
earliestValidTo: rows.reduce(function (acc, r) {
|
|
336
|
+
var ms = r.validTo ? Date.parse(r.validTo) : Infinity;
|
|
337
|
+
return ms < acc ? ms : acc;
|
|
338
|
+
}, Infinity),
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
} catch (_e) { /* drop-silent */ }
|
|
342
|
+
}
|
|
343
|
+
if (typeof opts.onExpiring === "function") {
|
|
344
|
+
try { opts.onExpiring(rows); } catch (_e) { /* operator hook */ }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
var handle = safeAsync.repeating(_tick, intervalMs, { name: "tls-expiry-monitor" });
|
|
350
|
+
return {
|
|
351
|
+
stop: function () { if (handle) { handle.stop(); handle = null; } },
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function captureBaselineFingerprints() {
|
|
356
|
+
STATE.baselineFingerprints = STATE.cas.map(function (e) { return e.meta.fingerprint256; });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// pinsetDriftMonitor — periodic check that emits audit + observability
|
|
360
|
+
// events when the trust-store fingerprint set drifts from the captured
|
|
361
|
+
// baseline. Different intent from expiryMonitor: this fires when a
|
|
362
|
+
// CA is added or removed (by operator config-flip OR by a tampered
|
|
363
|
+
// MANIFEST / vendor refresh), not when an existing one approaches
|
|
364
|
+
// validity expiry.
|
|
365
|
+
//
|
|
366
|
+
// b.network.tls.captureBaselineFingerprints(); // at boot
|
|
367
|
+
// var mon = b.network.tls.pinsetDriftMonitor({
|
|
368
|
+
// intervalMs: C.TIME.minutes(15),
|
|
369
|
+
// onDrift: function (drift) { /* operator hook */ },
|
|
370
|
+
// });
|
|
371
|
+
//
|
|
372
|
+
// Audit emissions:
|
|
373
|
+
// network.tls.pinset.drift_check — every check, ok / warn
|
|
374
|
+
// network.tls.pinset.drifted — when added.length || removed.length
|
|
375
|
+
function pinsetDriftMonitor(opts) {
|
|
376
|
+
opts = opts || {};
|
|
377
|
+
var intervalMs = opts.intervalMs;
|
|
378
|
+
var auditOn = opts.audit !== false;
|
|
379
|
+
if (typeof intervalMs !== "number" || !isFinite(intervalMs) || intervalMs <= 0) {
|
|
380
|
+
throw new TlsTrustError("tls/bad-interval",
|
|
381
|
+
"tls.pinsetDriftMonitor: intervalMs must be a positive finite number");
|
|
382
|
+
}
|
|
383
|
+
function _tick() {
|
|
384
|
+
var drift;
|
|
385
|
+
try { drift = detectBaselineDrift(); }
|
|
386
|
+
catch (_e) { return; }
|
|
387
|
+
if (drift === null) return; // baseline not captured; nothing to compare
|
|
388
|
+
if (auditOn) {
|
|
389
|
+
try {
|
|
390
|
+
audit().safeEmit({
|
|
391
|
+
action: "network.tls.pinset.drift_check",
|
|
392
|
+
outcome: drift.drifted ? "warn" : "ok",
|
|
393
|
+
metadata: { added: drift.added.length, removed: drift.removed.length },
|
|
394
|
+
});
|
|
395
|
+
} catch (_e) { /* drop-silent */ }
|
|
396
|
+
}
|
|
397
|
+
if (drift.drifted) {
|
|
398
|
+
try { observability().safeEvent("network.tls.pinset.drifted", 1, {}); }
|
|
399
|
+
catch (_e) { /* drop-silent */ }
|
|
400
|
+
if (auditOn) {
|
|
401
|
+
try {
|
|
402
|
+
audit().safeEmit({
|
|
403
|
+
action: "network.tls.pinset.drifted",
|
|
404
|
+
outcome: "failure",
|
|
405
|
+
metadata: { added: drift.added, removed: drift.removed },
|
|
406
|
+
});
|
|
407
|
+
} catch (_e) { /* drop-silent */ }
|
|
408
|
+
}
|
|
409
|
+
if (typeof opts.onDrift === "function") {
|
|
410
|
+
try { opts.onDrift(drift); } catch (_e) { /* operator hook */ }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
var handle = safeAsync.repeating(_tick, intervalMs, { name: "tls-pinset-drift-monitor" });
|
|
415
|
+
return {
|
|
416
|
+
stop: function () { if (handle) { handle.stop(); handle = null; } },
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function detectBaselineDrift() {
|
|
421
|
+
if (!STATE.baselineFingerprints) return null;
|
|
422
|
+
var current = STATE.cas.map(function (e) { return e.meta.fingerprint256; });
|
|
423
|
+
var added = current.filter(function (fp) { return STATE.baselineFingerprints.indexOf(fp) === -1; });
|
|
424
|
+
var removed = STATE.baselineFingerprints.filter(function (fp) { return current.indexOf(fp) === -1; });
|
|
425
|
+
return { added: added, removed: removed, drifted: added.length > 0 || removed.length > 0 };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function applyToContext(opts) {
|
|
429
|
+
opts = opts || {};
|
|
430
|
+
validateOpts(opts, ["base"], "tls.applyToContext");
|
|
431
|
+
var base = Object.assign({}, opts.base || {});
|
|
432
|
+
var caStrings = STATE.cas.map(function (e) { return e.pem; });
|
|
433
|
+
if (STATE.systemTrust) {
|
|
434
|
+
var rootCAs = nodeTls.rootCertificates;
|
|
435
|
+
if (Array.isArray(rootCAs)) {
|
|
436
|
+
caStrings = caStrings.concat(rootCAs);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (caStrings.length > 0) base.ca = caStrings;
|
|
440
|
+
// PQC TLS handshake — apply the operator-configured key-share groups
|
|
441
|
+
// (default ["X25519MLKEM768", "X25519"]) so https.Server / https.Agent
|
|
442
|
+
// negotiate the hybrid KEM with peers that support it and fall back
|
|
443
|
+
// to classical X25519 with peers that don't. Operators who explicitly
|
|
444
|
+
// pass `groups` in their base config keep the override.
|
|
445
|
+
if (base.groups === undefined && STATE.tlsKeyShares.length > 0) {
|
|
446
|
+
base.groups = STATE.tlsKeyShares.join(":");
|
|
447
|
+
}
|
|
448
|
+
return base;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ---- PQC TLS key shares (RFC draft-ietf-tls-hybrid-design) ----
|
|
452
|
+
//
|
|
453
|
+
// b.network.tls.pqc.setKeyShares(["X25519MLKEM768", "X25519"]) — set the
|
|
454
|
+
// TLS 1.3 key-share groups the framework's https.Server / https.Agent
|
|
455
|
+
// will advertise. The first listed group is the priority; the peer
|
|
456
|
+
// picks the first mutually supported entry. Hybrid groups
|
|
457
|
+
// (X25519MLKEM768) negotiate post-quantum + classical in one
|
|
458
|
+
// handshake so forward-secrecy survives both classical-CRQC and
|
|
459
|
+
// future quantum cryptanalysis.
|
|
460
|
+
//
|
|
461
|
+
// getKeyShares() → string[] (current)
|
|
462
|
+
// setKeyShares(["X25519MLKEM768", "X25519"]) → string[] (after)
|
|
463
|
+
// resetKeyShares() → restores default
|
|
464
|
+
|
|
465
|
+
// RFC 9794 (PQ TLS Hybrid Key Exchange) named-group ordering. The
|
|
466
|
+
// preferred groups (the first the peer mutually supports wins) put the
|
|
467
|
+
// IANA-registered hybrid named groups ahead of the classical fallback:
|
|
468
|
+
//
|
|
469
|
+
// X25519MLKEM768 — codepoint 0x11EC, RFC 9794 default hybrid
|
|
470
|
+
// SecP256r1MLKEM768 — codepoint 0x11EB, RFC 9794 optional hybrid
|
|
471
|
+
// (NIST-curve fallback for FIPS-mandated peers
|
|
472
|
+
// that refuse X25519)
|
|
473
|
+
// SecP384r1MLKEM1024 — draft-kwiatkowski-tls-ecdhe-mlkem-02 codepoint
|
|
474
|
+
// 0x11ED; highest-PQC hybrid; only ML-KEM-1024
|
|
475
|
+
// offering for FIPS-mandated peers wanting
|
|
476
|
+
// CNSA-2.0-aligned key strength
|
|
477
|
+
// X25519 — classical fallback (modern non-PQC peers)
|
|
478
|
+
//
|
|
479
|
+
// Operators FIPS-mandated to a NIST curve set `setKeyShares([
|
|
480
|
+
// "SecP256r1MLKEM768", "SecP384r1MLKEM1024" ])` and drop the X25519-
|
|
481
|
+
// based groups. Operators on legacy peers without any PQC support set
|
|
482
|
+
// `setKeyShares(["X25519"])` to opt out of the hybrid groups entirely.
|
|
483
|
+
var DEFAULT_PQC_KEY_SHARES = Object.freeze([
|
|
484
|
+
"X25519MLKEM768",
|
|
485
|
+
"SecP256r1MLKEM768",
|
|
486
|
+
"SecP384r1MLKEM1024",
|
|
487
|
+
"X25519",
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
function _validateKeyShare(name) {
|
|
491
|
+
if (typeof name !== "string" || name.length === 0 || name.length > C.BYTES.bytes(64)) { // bound
|
|
492
|
+
throw new TlsTrustError("tls/bad-key-share",
|
|
493
|
+
"tls.pqc.setKeyShares: each entry must be a non-empty string up to 64 chars");
|
|
494
|
+
}
|
|
495
|
+
// RFC draft-ietf-tls-hybrid-design + IANA TLS Group Registry only
|
|
496
|
+
// emit alphanumeric + underscore identifiers. Refuse `:` (the join
|
|
497
|
+
// separator) outright so an operator can't smuggle a second entry
|
|
498
|
+
// through one slot.
|
|
499
|
+
if (!/^[A-Za-z0-9_]+$/.test(name)) {
|
|
500
|
+
throw new TlsTrustError("tls/bad-key-share",
|
|
501
|
+
"tls.pqc.setKeyShares: '" + name + "' has illegal characters " +
|
|
502
|
+
"(must match [A-Za-z0-9_]+)");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function setKeyShares(list) {
|
|
507
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
508
|
+
throw new TlsTrustError("tls/bad-key-shares",
|
|
509
|
+
"tls.pqc.setKeyShares: must be a non-empty array of group names");
|
|
510
|
+
}
|
|
511
|
+
for (var i = 0; i < list.length; i += 1) _validateKeyShare(list[i]);
|
|
512
|
+
STATE.tlsKeyShares = list.slice();
|
|
513
|
+
return getKeyShares();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getKeyShares() { return STATE.tlsKeyShares.slice(); }
|
|
517
|
+
|
|
518
|
+
function resetKeyShares() {
|
|
519
|
+
STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
|
|
520
|
+
return getKeyShares();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// preferredGroups — RFC 9794 alias surface for the named-group list.
|
|
524
|
+
// `set(list)` overrides the default ordering; `get()` reads the active
|
|
525
|
+
// list; `reset()` restores the framework default. The setKeyShares /
|
|
526
|
+
// getKeyShares / resetKeyShares names are kept as the lower-level
|
|
527
|
+
// alias under `b.network.tls.pqc.*`.
|
|
528
|
+
var preferredGroups = Object.freeze({
|
|
529
|
+
set: setKeyShares,
|
|
530
|
+
get: getKeyShares,
|
|
531
|
+
reset: resetKeyShares,
|
|
532
|
+
DEFAULT: DEFAULT_PQC_KEY_SHARES,
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
var pqc = Object.freeze({
|
|
536
|
+
setKeyShares: setKeyShares,
|
|
537
|
+
getKeyShares: getKeyShares,
|
|
538
|
+
resetKeyShares: resetKeyShares,
|
|
539
|
+
DEFAULT_KEY_SHARES: DEFAULT_PQC_KEY_SHARES,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
function getCaPems() {
|
|
543
|
+
return STATE.cas.map(function (e) { return e.pem; });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// b.network.tls.buildOptions(opts) — assemble a plain options object
|
|
547
|
+
// suitable for tls.connect / new https.Agent(...) / https.request,
|
|
548
|
+
// pre-populated with the framework's PQC group preference + TLSv1.3
|
|
549
|
+
// floor. Operators that build their own outbound transport (custom
|
|
550
|
+
// https.Agent, raw tls.connect for protocol clients other than HTTP)
|
|
551
|
+
// route through this primitive so the same posture lands everywhere.
|
|
552
|
+
//
|
|
553
|
+
// Throws NetworkTlsError("network-tls/bad-tls-options") on invalid
|
|
554
|
+
// shape (config-time entry point — operator catches typo at boot).
|
|
555
|
+
//
|
|
556
|
+
// buildOptions({ ecdhCurve, groups, cert, key, ca, minVersion, sni })
|
|
557
|
+
// returns { minVersion, ecdhCurve, groups, cert, key, ca, servername }
|
|
558
|
+
//
|
|
559
|
+
// `ca` accepts a PEM string OR Buffer OR Array<string|Buffer>; arrays
|
|
560
|
+
// are concatenated with `\n` so Node's TLS layer parses every block.
|
|
561
|
+
function _normalizeCaInput(ca) {
|
|
562
|
+
if (ca === undefined || ca === null) return undefined;
|
|
563
|
+
if (Buffer.isBuffer(ca)) return ca.toString("utf8");
|
|
564
|
+
if (typeof ca === "string") return ca;
|
|
565
|
+
if (!Array.isArray(ca)) {
|
|
566
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
567
|
+
"buildOptions: ca must be a PEM string, Buffer, or array thereof");
|
|
568
|
+
}
|
|
569
|
+
var parts = [];
|
|
570
|
+
for (var i = 0; i < ca.length; i += 1) {
|
|
571
|
+
var entry = ca[i];
|
|
572
|
+
if (Buffer.isBuffer(entry)) parts.push(entry.toString("utf8"));
|
|
573
|
+
else if (typeof entry === "string") parts.push(entry);
|
|
574
|
+
else {
|
|
575
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
576
|
+
"buildOptions: ca[" + i + "] must be a PEM string or Buffer");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return parts.join("\n");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function buildOptions(opts) {
|
|
583
|
+
opts = opts || {};
|
|
584
|
+
if (typeof opts !== "object" || Array.isArray(opts)) {
|
|
585
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
586
|
+
"buildOptions: opts must be a plain object");
|
|
587
|
+
}
|
|
588
|
+
validateOpts(opts,
|
|
589
|
+
["ecdhCurve", "groups", "cert", "key", "ca", "minVersion", "sni"],
|
|
590
|
+
"network.tls.buildOptions");
|
|
591
|
+
var out = {};
|
|
592
|
+
// TLS-1.3 floor — matches the framework's locked posture in
|
|
593
|
+
// pqc-agent. Operators may pass minVersion: "TLSv1.3" explicitly;
|
|
594
|
+
// anything else fails closed.
|
|
595
|
+
var minV = opts.minVersion === undefined ? "TLSv1.3" : opts.minVersion;
|
|
596
|
+
if (minV !== "TLSv1.3") {
|
|
597
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
598
|
+
"buildOptions: minVersion must be 'TLSv1.3' (got " +
|
|
599
|
+
JSON.stringify(opts.minVersion) + ") — framework posture is " +
|
|
600
|
+
"TLS-1.3-only outbound; construct tls.connect opts directly to " +
|
|
601
|
+
"negotiate weaker protocol versions.");
|
|
602
|
+
}
|
|
603
|
+
out.minVersion = minV;
|
|
604
|
+
|
|
605
|
+
// PQC group preference. Caller may narrow (drop a group) but not
|
|
606
|
+
// widen — every requested group must appear in the framework
|
|
607
|
+
// preferred list. Both `groups` (RFC 9794 alias) and `ecdhCurve`
|
|
608
|
+
// (Node TLS option) are accepted; `groups` wins when both supplied.
|
|
609
|
+
var requested = null;
|
|
610
|
+
if (Array.isArray(opts.groups)) {
|
|
611
|
+
requested = opts.groups.slice();
|
|
612
|
+
} else if (typeof opts.groups === "string" && opts.groups.length > 0) {
|
|
613
|
+
requested = opts.groups.split(":");
|
|
614
|
+
} else if (typeof opts.ecdhCurve === "string" && opts.ecdhCurve.length > 0) {
|
|
615
|
+
requested = opts.ecdhCurve.split(":");
|
|
616
|
+
} else if (opts.groups !== undefined || opts.ecdhCurve !== undefined) {
|
|
617
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
618
|
+
"buildOptions: groups must be string or string[], ecdhCurve must be string");
|
|
619
|
+
}
|
|
620
|
+
var preferred = STATE.tlsKeyShares.length > 0
|
|
621
|
+
? STATE.tlsKeyShares.slice()
|
|
622
|
+
: DEFAULT_PQC_KEY_SHARES.slice();
|
|
623
|
+
var resolved;
|
|
624
|
+
if (requested === null) {
|
|
625
|
+
resolved = preferred;
|
|
626
|
+
} else {
|
|
627
|
+
if (requested.length === 0) {
|
|
628
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
629
|
+
"buildOptions: groups/ecdhCurve must list at least one named group");
|
|
630
|
+
}
|
|
631
|
+
for (var rgi = 0; rgi < requested.length; rgi += 1) {
|
|
632
|
+
if (typeof requested[rgi] !== "string" || requested[rgi].length === 0) {
|
|
633
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
634
|
+
"buildOptions: groups[" + rgi + "] must be a non-empty string");
|
|
635
|
+
}
|
|
636
|
+
if (preferred.indexOf(requested[rgi]) === -1) {
|
|
637
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
638
|
+
"buildOptions: group '" + requested[rgi] + "' is not in the " +
|
|
639
|
+
"framework preferred list (" + preferred.join(":") + "); " +
|
|
640
|
+
"construct tls.connect opts directly to negotiate weaker groups.");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
resolved = requested;
|
|
644
|
+
}
|
|
645
|
+
var resolvedStr = resolved.join(":");
|
|
646
|
+
out.ecdhCurve = resolvedStr;
|
|
647
|
+
out.groups = resolvedStr;
|
|
648
|
+
|
|
649
|
+
// cert / key — pass-through with light shape check. Both are
|
|
650
|
+
// typically PEM strings or Buffers; arrays are valid for cert
|
|
651
|
+
// bundles per Node's tls API, so allow array<string|Buffer>.
|
|
652
|
+
if (opts.cert !== undefined) {
|
|
653
|
+
if (!(typeof opts.cert === "string" || Buffer.isBuffer(opts.cert) ||
|
|
654
|
+
Array.isArray(opts.cert))) {
|
|
655
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
656
|
+
"buildOptions: cert must be a string, Buffer, or array thereof");
|
|
657
|
+
}
|
|
658
|
+
out.cert = opts.cert;
|
|
659
|
+
}
|
|
660
|
+
if (opts.key !== undefined) {
|
|
661
|
+
if (!(typeof opts.key === "string" || Buffer.isBuffer(opts.key) ||
|
|
662
|
+
Array.isArray(opts.key))) {
|
|
663
|
+
throw new NetworkTlsError("network-tls/bad-tls-options",
|
|
664
|
+
"buildOptions: key must be a string, Buffer, or array thereof");
|
|
665
|
+
}
|
|
666
|
+
out.key = opts.key;
|
|
667
|
+
}
|
|
668
|
+
if (opts.ca !== undefined) out.ca = _normalizeCaInput(opts.ca);
|
|
669
|
+
|
|
670
|
+
// SNI override — Node spells this `servername`.
|
|
671
|
+
if (opts.sni !== undefined) {
|
|
672
|
+
validateOpts.requireNonEmptyString(opts.sni, "buildOptions: sni",
|
|
673
|
+
NetworkTlsError, "network-tls/bad-tls-options");
|
|
674
|
+
out.servername = opts.sni;
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function _emitAuditAdd(metaList, opts) {
|
|
680
|
+
if (opts.audit === false) return;
|
|
681
|
+
var sink;
|
|
682
|
+
try { sink = audit(); } catch (_e) { sink = null; }
|
|
683
|
+
if (!sink || typeof sink.safeEmit !== "function") return;
|
|
684
|
+
for (var i = 0; i < metaList.length; i++) {
|
|
685
|
+
var m = metaList[i];
|
|
686
|
+
try {
|
|
687
|
+
sink.safeEmit({
|
|
688
|
+
action: "network.tls.ca.added",
|
|
689
|
+
outcome: "success",
|
|
690
|
+
metadata: {
|
|
691
|
+
subject: m.subject,
|
|
692
|
+
issuer: m.issuer,
|
|
693
|
+
fingerprint256: m.fingerprint256,
|
|
694
|
+
validFrom: m.validFrom,
|
|
695
|
+
validTo: m.validTo,
|
|
696
|
+
isSelfSigned: m.isSelfSigned,
|
|
697
|
+
label: opts.label || null,
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function _emitObs(name, fields) {
|
|
705
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function _resetForTest() {
|
|
709
|
+
STATE.cas = [];
|
|
710
|
+
STATE.systemTrust = false;
|
|
711
|
+
STATE.baselineFingerprints = null;
|
|
712
|
+
STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ---- OCSP / OCSP-stapling wrappers around node:tls ----------------
|
|
716
|
+
//
|
|
717
|
+
// node:tls exposes two OCSP affordances:
|
|
718
|
+
// - tls.connect({ requestOCSP: true }) → emits 'OCSPResponse' event
|
|
719
|
+
// - https.createServer({ ... requestOCSP }) → server-side stapling
|
|
720
|
+
//
|
|
721
|
+
// b.network.tls.ocsp wraps these. The names reflect what the wrapper
|
|
722
|
+
// actually does at this stage:
|
|
723
|
+
//
|
|
724
|
+
// - ocsp.connect(opts) — connect with requestOCSP:true; resolve
|
|
725
|
+
// with { authorized, ocspBytes, peerCert }.
|
|
726
|
+
// - ocsp.requireStapled(opts) — refuse if peer doesn't staple an
|
|
727
|
+
// OCSP response (presence + non-empty
|
|
728
|
+
// byte check). DOES NOT verify the OCSP
|
|
729
|
+
// response signature against the issuer
|
|
730
|
+
// cert — that requires DER OCSPResponse
|
|
731
|
+
// parsing which lands in the next patch
|
|
732
|
+
// alongside the ASN.1 DER helper. The
|
|
733
|
+
// honest name keeps the surface from
|
|
734
|
+
// claiming "good" while only checking
|
|
735
|
+
// stapling.
|
|
736
|
+
//
|
|
737
|
+
// node:tls validates the cert chain itself; OCSP staple validation is
|
|
738
|
+
// the application's job once the response bytes are received.
|
|
739
|
+
|
|
740
|
+
function _connectAndCheckOcsp(opts, requireStapled) {
|
|
741
|
+
return new Promise(function (resolve, reject) {
|
|
742
|
+
var connectOpts = Object.assign({}, opts, { requestOCSP: true });
|
|
743
|
+
var sock;
|
|
744
|
+
try {
|
|
745
|
+
sock = nodeTls.connect(connectOpts);
|
|
746
|
+
} catch (e) {
|
|
747
|
+
reject(new TlsTrustError("tls/connect-failed",
|
|
748
|
+
"tls.connect threw: " + ((e && e.message) || String(e))));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
var ocspResponseSeen = false;
|
|
752
|
+
sock.on("OCSPResponse", function (response) {
|
|
753
|
+
ocspResponseSeen = true;
|
|
754
|
+
if (!response || response.length === 0) {
|
|
755
|
+
if (requireStapled) {
|
|
756
|
+
sock.destroy();
|
|
757
|
+
reject(new TlsTrustError("tls/ocsp-empty",
|
|
758
|
+
"OCSP response was empty and requireStapled is set"));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Operator can post-process the DER OCSPResponse via the resolved
|
|
763
|
+
// callback; the framework doesn't parse the ASN.1 itself.
|
|
764
|
+
sock.once("secureConnect", function () {
|
|
765
|
+
var rv = {
|
|
766
|
+
authorized: sock.authorized,
|
|
767
|
+
ocspBytes: response || null,
|
|
768
|
+
peerCert: sock.getPeerCertificate(true),
|
|
769
|
+
};
|
|
770
|
+
sock.destroy();
|
|
771
|
+
resolve(rv);
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
sock.on("secureConnect", function () {
|
|
775
|
+
// 'OCSPResponse' fires BEFORE 'secureConnect' when the server
|
|
776
|
+
// replied with stapled OCSP. If we got here without seeing an
|
|
777
|
+
// OCSPResponse event AND requireStapled is set, refuse.
|
|
778
|
+
if (!ocspResponseSeen) {
|
|
779
|
+
if (requireStapled) {
|
|
780
|
+
sock.destroy();
|
|
781
|
+
reject(new TlsTrustError("tls/ocsp-not-stapled",
|
|
782
|
+
"TLS peer did not staple an OCSP response and requireStapled is set"));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
var rv = {
|
|
786
|
+
authorized: sock.authorized,
|
|
787
|
+
ocspBytes: null,
|
|
788
|
+
peerCert: sock.getPeerCertificate(true),
|
|
789
|
+
};
|
|
790
|
+
sock.destroy();
|
|
791
|
+
resolve(rv);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
sock.on("error", function (e) { reject(e); });
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// ---- OCSP response parser (RFC 6960) ----
|
|
799
|
+
//
|
|
800
|
+
// Decodes a DER OCSPResponse into:
|
|
801
|
+
// {
|
|
802
|
+
// status: "successful" | "malformedRequest" | "internalError" |
|
|
803
|
+
// "tryLater" | "sigRequired" | "unauthorized",
|
|
804
|
+
// basic: { // present when status === "successful"
|
|
805
|
+
// tbsResponseDataDer: Buffer, // the bytes signed
|
|
806
|
+
// signatureAlgorithmOid: string,
|
|
807
|
+
// signature: Buffer,
|
|
808
|
+
// responses: [{ certIdSerialHex, certStatus, thisUpdate, nextUpdate }, ...],
|
|
809
|
+
// }
|
|
810
|
+
// }
|
|
811
|
+
//
|
|
812
|
+
// Cherry-picks the fields the framework needs to verify the response —
|
|
813
|
+
// the signed bytes (tbsResponseData) + the signature + each response
|
|
814
|
+
// entry's status. Out of scope: ResponderID / extensions / nonce
|
|
815
|
+
// validation (operators relying on those wire their own parser).
|
|
816
|
+
|
|
817
|
+
var OID_BASIC_OCSP_RESPONSE = "1.3.6.1.5.5.7.48.1.1";
|
|
818
|
+
// OCSP nonce extension — id-pkix-ocsp-nonce.
|
|
819
|
+
var OID_OCSP_NONCE = "1.3.6.1.5.5.7.48.1.2";
|
|
820
|
+
var OID_SHA1 = "1.3.14.3.2.26"; // allow:raw-byte-literal — SHA-1 algorithm OID arc
|
|
821
|
+
var OID_RSA_SHA256 = "1.2.840.113549.1.1.11";
|
|
822
|
+
var OID_RSA_SHA384 = "1.2.840.113549.1.1.12";
|
|
823
|
+
var OID_RSA_SHA512 = "1.2.840.113549.1.1.13";
|
|
824
|
+
var OID_ECDSA_SHA256 = "1.2.840.10045.4.3.2";
|
|
825
|
+
var OID_ECDSA_SHA384 = "1.2.840.10045.4.3.3";
|
|
826
|
+
var OID_ECDSA_SHA512 = "1.2.840.10045.4.3.4";
|
|
827
|
+
|
|
828
|
+
function _parseTime(node) {
|
|
829
|
+
// Parse UTCTime ("YYMMDDhhmmssZ") or GeneralizedTime
|
|
830
|
+
// ("YYYYMMDDhhmmssZ") into ms-since-epoch.
|
|
831
|
+
var s = node.value.toString("ascii");
|
|
832
|
+
var year, month, day, hour, min, sec;
|
|
833
|
+
if (s.length === 13 && s.charAt(12) === "Z") { // allow:raw-byte-literal — UTCTime length per X.690
|
|
834
|
+
// UTCTime YYMMDDhhmmssZ — 50+ → 19xx, else 20xx (RFC 5280 §4.1.2.5).
|
|
835
|
+
year = parseInt(s.slice(0, 2), 10);
|
|
836
|
+
year += year >= 50 ? 1900 : 2000; // allow:raw-byte-literal allow:raw-time-literal — RFC 5280 century pivot, calendar years
|
|
837
|
+
month = parseInt(s.slice(2, 4), 10);
|
|
838
|
+
day = parseInt(s.slice(4, 6), 10);
|
|
839
|
+
hour = parseInt(s.slice(6, 8), 10); // allow:raw-byte-literal — UTCTime hour-byte offsets
|
|
840
|
+
min = parseInt(s.slice(8, 10), 10); // allow:raw-byte-literal — UTCTime minute-byte offsets
|
|
841
|
+
sec = parseInt(s.slice(10, 12), 10);
|
|
842
|
+
} else if (s.length >= 15 && s.charAt(s.length - 1) === "Z") { // allow:raw-byte-literal — GeneralizedTime length per X.690
|
|
843
|
+
// GeneralizedTime YYYYMMDDhhmmssZ.
|
|
844
|
+
year = parseInt(s.slice(0, 4), 10);
|
|
845
|
+
month = parseInt(s.slice(4, 6), 10);
|
|
846
|
+
day = parseInt(s.slice(6, 8), 10); // allow:raw-byte-literal — GeneralizedTime day-byte offsets
|
|
847
|
+
hour = parseInt(s.slice(8, 10), 10); // allow:raw-byte-literal — GeneralizedTime hour-byte offsets
|
|
848
|
+
min = parseInt(s.slice(10, 12), 10);
|
|
849
|
+
sec = parseInt(s.slice(12, 14), 10);
|
|
850
|
+
} else {
|
|
851
|
+
throw new TlsTrustError("tls/ocsp-bad-time",
|
|
852
|
+
"OCSP time field is not UTCTime or GeneralizedTime: " + JSON.stringify(s));
|
|
853
|
+
}
|
|
854
|
+
return Date.UTC(year, month - 1, day, hour, min, sec);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
var OCSP_RESPONSE_STATUS = {
|
|
858
|
+
0: "successful",
|
|
859
|
+
1: "malformedRequest",
|
|
860
|
+
2: "internalError",
|
|
861
|
+
3: "tryLater",
|
|
862
|
+
// 4 reserved
|
|
863
|
+
5: "sigRequired",
|
|
864
|
+
6: "unauthorized",
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
function parseOcspResponse(der) {
|
|
868
|
+
if (!Buffer.isBuffer(der) || der.length === 0) {
|
|
869
|
+
throw new TlsTrustError("tls/ocsp-bad-input",
|
|
870
|
+
"parseOcspResponse: expected non-empty Buffer");
|
|
871
|
+
}
|
|
872
|
+
var top = asn1.readNode(der); // OCSPResponse SEQUENCE
|
|
873
|
+
if (top.tag !== asn1.TAG.SEQUENCE) {
|
|
874
|
+
throw new TlsTrustError("tls/ocsp-bad-shape", "OCSPResponse is not a SEQUENCE");
|
|
875
|
+
}
|
|
876
|
+
var topChildren = asn1.readSequence(top.value);
|
|
877
|
+
if (topChildren.length === 0) {
|
|
878
|
+
throw new TlsTrustError("tls/ocsp-bad-shape", "OCSPResponse has no responseStatus");
|
|
879
|
+
}
|
|
880
|
+
var statusInt = asn1.readUnsignedInt(topChildren[0]);
|
|
881
|
+
var status = OCSP_RESPONSE_STATUS[statusInt] || ("unknown:" + statusInt);
|
|
882
|
+
if (status !== "successful") {
|
|
883
|
+
return { status: status };
|
|
884
|
+
}
|
|
885
|
+
// responseBytes [0] EXPLICIT ResponseBytes
|
|
886
|
+
if (topChildren.length < 2) {
|
|
887
|
+
throw new TlsTrustError("tls/ocsp-bad-shape",
|
|
888
|
+
"successful OCSP response missing responseBytes");
|
|
889
|
+
}
|
|
890
|
+
var responseBytes = asn1.unwrapExplicit(topChildren[1], 0); // [0] EXPLICIT
|
|
891
|
+
if (responseBytes.tag !== asn1.TAG.SEQUENCE) {
|
|
892
|
+
throw new TlsTrustError("tls/ocsp-bad-shape", "responseBytes is not a SEQUENCE");
|
|
893
|
+
}
|
|
894
|
+
var rbChildren = asn1.readSequence(responseBytes.value);
|
|
895
|
+
if (rbChildren.length < 2) {
|
|
896
|
+
throw new TlsTrustError("tls/ocsp-bad-shape",
|
|
897
|
+
"responseBytes missing responseType or response");
|
|
898
|
+
}
|
|
899
|
+
var responseTypeOid = asn1.readOid(rbChildren[0]);
|
|
900
|
+
if (responseTypeOid !== OID_BASIC_OCSP_RESPONSE) {
|
|
901
|
+
throw new TlsTrustError("tls/ocsp-unsupported-response-type",
|
|
902
|
+
"OCSP responseType is not id-pkix-ocsp-basic: " + responseTypeOid);
|
|
903
|
+
}
|
|
904
|
+
// The OCTET STRING wraps a DER BasicOCSPResponse.
|
|
905
|
+
var basicDer = asn1.readOctetString(rbChildren[1]);
|
|
906
|
+
var basic = asn1.readNode(basicDer);
|
|
907
|
+
if (basic.tag !== asn1.TAG.SEQUENCE) {
|
|
908
|
+
throw new TlsTrustError("tls/ocsp-bad-shape",
|
|
909
|
+
"BasicOCSPResponse is not a SEQUENCE");
|
|
910
|
+
}
|
|
911
|
+
var basicChildren = asn1.readSequence(basic.value);
|
|
912
|
+
if (basicChildren.length < 3) { // allow:raw-byte-literal — minimum BasicOCSPResponse fields (tbs + alg + sig)
|
|
913
|
+
throw new TlsTrustError("tls/ocsp-bad-shape",
|
|
914
|
+
"BasicOCSPResponse needs tbsResponseData + signatureAlgorithm + signature");
|
|
915
|
+
}
|
|
916
|
+
var tbsNode = basicChildren[0];
|
|
917
|
+
var sigAlgChildren = asn1.readSequence(basicChildren[1].value);
|
|
918
|
+
var sigAlgOid = asn1.readOid(sigAlgChildren[0]);
|
|
919
|
+
var signatureBytes = asn1.readBitString(basicChildren[2]);
|
|
920
|
+
|
|
921
|
+
// Slice the tbsResponseData bytes (header + value) — that's what the
|
|
922
|
+
// signature covers per RFC 6960 §4.2.1. tbsResponseData is the FIRST
|
|
923
|
+
// child of BasicOCSPResponse; its bytes start at basic.valueStart
|
|
924
|
+
// within the raw basicDer buffer (offset 0).
|
|
925
|
+
var basicValueStart = basicDer.length - basic.value.length;
|
|
926
|
+
var tbsDer = basicDer.slice(basicValueStart, basicValueStart + tbsNode.totalLength);
|
|
927
|
+
|
|
928
|
+
// Walk responseData (SEQUENCE) for the per-cert responses.
|
|
929
|
+
var rdChildren = asn1.readSequence(tbsNode.value);
|
|
930
|
+
// Find the SEQUENCE of SingleResponse — it's the LAST SEQUENCE before
|
|
931
|
+
// optional [1] EXPLICIT extensions. Per RFC 6960:
|
|
932
|
+
// ResponseData ::= SEQUENCE {
|
|
933
|
+
// version [0] EXPLICIT Version DEFAULT v1,
|
|
934
|
+
// responderID ResponderID,
|
|
935
|
+
// producedAt GeneralizedTime,
|
|
936
|
+
// responses SEQUENCE OF SingleResponse,
|
|
937
|
+
// responseExtensions [1] EXPLICIT Extensions OPTIONAL
|
|
938
|
+
// }
|
|
939
|
+
// ResponderID is itself a CHOICE (byName [1] / byKey [2]), then a
|
|
940
|
+
// GeneralizedTime, then the responses SEQUENCE-OF.
|
|
941
|
+
var responsesNode = null;
|
|
942
|
+
var responseExtensionsNode = null;
|
|
943
|
+
for (var rdi = rdChildren.length - 1; rdi >= 0; rdi -= 1) {
|
|
944
|
+
var ch = rdChildren[rdi];
|
|
945
|
+
if (ch.tag === asn1.TAG.SEQUENCE && ch.tagClass === asn1.TAG_CLASS.UNIVERSAL) {
|
|
946
|
+
responsesNode = ch;
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
if (ch.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && ch.tag === 1) { // [1] EXPLICIT responseExtensions
|
|
950
|
+
responseExtensionsNode = asn1.readNode(ch.value, 0);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (!responsesNode) {
|
|
954
|
+
throw new TlsTrustError("tls/ocsp-bad-shape",
|
|
955
|
+
"ResponseData missing responses SEQUENCE OF");
|
|
956
|
+
}
|
|
957
|
+
// Walk responseExtensions for the OCSP nonce (RFC 8954 / RFC 6960
|
|
958
|
+
// §4.4.1). Returns the raw nonce bytes when present, or null.
|
|
959
|
+
var responseNonce = null;
|
|
960
|
+
if (responseExtensionsNode && responseExtensionsNode.tag === asn1.TAG.SEQUENCE) {
|
|
961
|
+
var extKids = asn1.readSequence(responseExtensionsNode.value);
|
|
962
|
+
for (var ei = 0; ei < extKids.length; ei += 1) {
|
|
963
|
+
var ext = extKids[ei];
|
|
964
|
+
if (ext.tag !== asn1.TAG.SEQUENCE) continue;
|
|
965
|
+
var extChildren = asn1.readSequence(ext.value);
|
|
966
|
+
if (extChildren.length === 0) continue;
|
|
967
|
+
var extOid;
|
|
968
|
+
try { extOid = asn1.readOid(extChildren[0]); }
|
|
969
|
+
catch (_e3) { continue; }
|
|
970
|
+
if (extOid !== OID_OCSP_NONCE) continue;
|
|
971
|
+
var extnValue = asn1.readOctetString(extChildren[extChildren.length - 1]);
|
|
972
|
+
// RFC 8954 §2.1 — the nonce extension value is the raw bytes
|
|
973
|
+
// wrapped in an OCTET STRING (the value here). RFC 6960 §4.4.1
|
|
974
|
+
// historically wrapped the nonce in another OCTET STRING; tolerate
|
|
975
|
+
// both shapes.
|
|
976
|
+
try {
|
|
977
|
+
var inner = asn1.readNode(extnValue);
|
|
978
|
+
if (inner.tag === asn1.TAG.OCTET_STRING) {
|
|
979
|
+
responseNonce = inner.value;
|
|
980
|
+
} else {
|
|
981
|
+
responseNonce = extnValue;
|
|
982
|
+
}
|
|
983
|
+
} catch (_e4) {
|
|
984
|
+
responseNonce = extnValue;
|
|
985
|
+
}
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
var singleResponses = asn1.readSequence(responsesNode.value);
|
|
990
|
+
var responses = [];
|
|
991
|
+
for (var sri = 0; sri < singleResponses.length; sri += 1) {
|
|
992
|
+
var sr = asn1.readSequence(singleResponses[sri].value);
|
|
993
|
+
if (sr.length < 3) continue; // allow:raw-byte-literal — minimum SingleResponse fields
|
|
994
|
+
// sr[0] = certID SEQUENCE, sr[1] = certStatus CHOICE, sr[2] = thisUpdate.
|
|
995
|
+
var certIdChildren = asn1.readSequence(sr[0].value);
|
|
996
|
+
// certID = SEQUENCE { hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber }
|
|
997
|
+
var serialHex = certIdChildren.length >= 4
|
|
998
|
+
? certIdChildren[3].value.toString("hex")
|
|
999
|
+
: null;
|
|
1000
|
+
var certStatus;
|
|
1001
|
+
var statusNode = sr[1];
|
|
1002
|
+
if (statusNode.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC) {
|
|
1003
|
+
certStatus = statusNode.tag === 0 ? "good" :
|
|
1004
|
+
statusNode.tag === 1 ? "revoked" :
|
|
1005
|
+
statusNode.tag === 2 ? "unknown" : "unknown";
|
|
1006
|
+
} else if (statusNode.tag === asn1.TAG.NULL) {
|
|
1007
|
+
certStatus = "good";
|
|
1008
|
+
} else {
|
|
1009
|
+
certStatus = "unknown";
|
|
1010
|
+
}
|
|
1011
|
+
var thisUpdate = _parseTime(sr[2]);
|
|
1012
|
+
var nextUpdate = null;
|
|
1013
|
+
if (sr.length >= 4 && sr[3].tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && sr[3].tag === 0) {
|
|
1014
|
+
nextUpdate = _parseTime(asn1.readNode(sr[3].value, 0));
|
|
1015
|
+
}
|
|
1016
|
+
responses.push({
|
|
1017
|
+
certIdSerialHex: serialHex,
|
|
1018
|
+
certStatus: certStatus,
|
|
1019
|
+
thisUpdate: thisUpdate,
|
|
1020
|
+
nextUpdate: nextUpdate,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return {
|
|
1025
|
+
status: status,
|
|
1026
|
+
basic: {
|
|
1027
|
+
tbsResponseDataDer: tbsDer,
|
|
1028
|
+
signatureAlgorithmOid: sigAlgOid,
|
|
1029
|
+
signature: signatureBytes,
|
|
1030
|
+
responses: responses,
|
|
1031
|
+
nonce: responseNonce,
|
|
1032
|
+
},
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function _verifyOcspSignature(parsed, issuerPem) {
|
|
1037
|
+
if (!parsed || !parsed.basic) {
|
|
1038
|
+
throw new TlsTrustError("tls/ocsp-not-successful",
|
|
1039
|
+
"OCSP response status is not 'successful' (got " +
|
|
1040
|
+
(parsed && parsed.status) + ")");
|
|
1041
|
+
}
|
|
1042
|
+
var algOid = parsed.basic.signatureAlgorithmOid;
|
|
1043
|
+
var nodeAlgo = algOid === OID_RSA_SHA256 ? "sha256" :
|
|
1044
|
+
algOid === OID_RSA_SHA384 ? "sha384" :
|
|
1045
|
+
algOid === OID_RSA_SHA512 ? "sha512" :
|
|
1046
|
+
algOid === OID_ECDSA_SHA256 ? "sha256" :
|
|
1047
|
+
algOid === OID_ECDSA_SHA384 ? "sha384" :
|
|
1048
|
+
algOid === OID_ECDSA_SHA512 ? "sha512" : null;
|
|
1049
|
+
if (nodeAlgo === null) {
|
|
1050
|
+
throw new TlsTrustError("tls/ocsp-unsupported-sig-alg",
|
|
1051
|
+
"OCSP signatureAlgorithm OID '" + algOid + "' is not supported by the verifier");
|
|
1052
|
+
}
|
|
1053
|
+
var keyObj;
|
|
1054
|
+
try { keyObj = nodeCrypto.createPublicKey(issuerPem); }
|
|
1055
|
+
catch (e) {
|
|
1056
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-key",
|
|
1057
|
+
"issuer public key parse failed: " + ((e && e.message) || String(e)));
|
|
1058
|
+
}
|
|
1059
|
+
// ECDSA OCSP signatures use DER-encoded ECDSA-Sig-Value (the ASN.1
|
|
1060
|
+
// shape that node:crypto.verify accepts by default — no dsaEncoding
|
|
1061
|
+
// option needed).
|
|
1062
|
+
var verified;
|
|
1063
|
+
try {
|
|
1064
|
+
verified = nodeCrypto.verify(nodeAlgo, parsed.basic.tbsResponseDataDer, keyObj,
|
|
1065
|
+
parsed.basic.signature);
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
throw new TlsTrustError("tls/ocsp-verify-threw",
|
|
1068
|
+
"OCSP signature verify threw: " + ((e && e.message) || String(e)));
|
|
1069
|
+
}
|
|
1070
|
+
return verified;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Operator-side OCSP response evaluator. Takes the DER bytes (from
|
|
1074
|
+
// `ocsp.requireStapled` or any other source) plus the issuer cert PEM
|
|
1075
|
+
// and returns a structured outcome:
|
|
1076
|
+
// { ok, status, certStatus, thisUpdate, nextUpdate, signatureValid, errors }
|
|
1077
|
+
function evaluateOcspResponse(ocspDer, opts) {
|
|
1078
|
+
opts = opts || {};
|
|
1079
|
+
var issuerPem = opts.issuerPem;
|
|
1080
|
+
if (!issuerPem) {
|
|
1081
|
+
throw new TlsTrustError("tls/ocsp-missing-issuer",
|
|
1082
|
+
"evaluateOcspResponse requires opts.issuerPem (PEM of the cert that signed the OCSP response — typically the leaf's CA OR a delegated id-kp-OCSPSigning responder cert)");
|
|
1083
|
+
}
|
|
1084
|
+
var parsed;
|
|
1085
|
+
try { parsed = parseOcspResponse(ocspDer); }
|
|
1086
|
+
catch (e) {
|
|
1087
|
+
return { ok: false, status: "parse-error",
|
|
1088
|
+
errors: [(e && e.message) || String(e)] };
|
|
1089
|
+
}
|
|
1090
|
+
if (parsed.status !== "successful") {
|
|
1091
|
+
return { ok: false, status: parsed.status, errors: ["responseStatus=" + parsed.status] };
|
|
1092
|
+
}
|
|
1093
|
+
var sigOk = false;
|
|
1094
|
+
try { sigOk = _verifyOcspSignature(parsed, issuerPem); }
|
|
1095
|
+
catch (e) {
|
|
1096
|
+
return { ok: false, status: parsed.status,
|
|
1097
|
+
signatureValid: false,
|
|
1098
|
+
errors: [(e && e.message) || String(e)] };
|
|
1099
|
+
}
|
|
1100
|
+
if (!sigOk) {
|
|
1101
|
+
return { ok: false, status: parsed.status, signatureValid: false,
|
|
1102
|
+
errors: ["OCSP signature did not verify against the issuer key"] };
|
|
1103
|
+
}
|
|
1104
|
+
// Look up the requested cert serial in the responses; "good" wins.
|
|
1105
|
+
var serial = opts.serialHex || (parsed.basic.responses[0] && parsed.basic.responses[0].certIdSerialHex);
|
|
1106
|
+
var match = null;
|
|
1107
|
+
for (var i = 0; i < parsed.basic.responses.length; i += 1) {
|
|
1108
|
+
var r = parsed.basic.responses[i];
|
|
1109
|
+
if (!serial || r.certIdSerialHex === serial) { match = r; break; }
|
|
1110
|
+
}
|
|
1111
|
+
if (!match) {
|
|
1112
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1113
|
+
errors: ["OCSP response has no entry for the requested cert serial"] };
|
|
1114
|
+
}
|
|
1115
|
+
// Optional nonce echo verification (RFC 8954 / RFC 6960 §4.4.1).
|
|
1116
|
+
// When opts.expectedNonce is supplied, the response MUST carry an
|
|
1117
|
+
// OCSP nonce extension equal to the expected bytes — defends against
|
|
1118
|
+
// replay of a stale "good" response captured before revocation.
|
|
1119
|
+
var nonceCheck = "n/a";
|
|
1120
|
+
if (opts.expectedNonce !== undefined && opts.expectedNonce !== null) {
|
|
1121
|
+
if (!Buffer.isBuffer(opts.expectedNonce)) {
|
|
1122
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1123
|
+
errors: ["evaluateOcspResponse: opts.expectedNonce must be a Buffer when supplied"] };
|
|
1124
|
+
}
|
|
1125
|
+
if (!parsed.basic.nonce) {
|
|
1126
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1127
|
+
errors: ["OCSP response missing nonce extension (expected for replay defense)"] };
|
|
1128
|
+
}
|
|
1129
|
+
// Constant-time compare — module-wide consistency with the
|
|
1130
|
+
// Merkle-root / NTS-cookie / cert-fingerprint paths that already
|
|
1131
|
+
// use timingSafeEqual. Buffer.equals is constant-time on equal-
|
|
1132
|
+
// length inputs but fast-paths on length mismatch; not security-
|
|
1133
|
+
// critical here (the OCSP response is CA-signed and signature
|
|
1134
|
+
// already verified) but matches the project discipline.
|
|
1135
|
+
// (Audit 2026-05-11.)
|
|
1136
|
+
if (!bCrypto.timingSafeEqual(parsed.basic.nonce, opts.expectedNonce)) {
|
|
1137
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1138
|
+
errors: ["OCSP nonce mismatch — possible replay or wrong responder"] };
|
|
1139
|
+
}
|
|
1140
|
+
nonceCheck = "matched";
|
|
1141
|
+
} else if (parsed.basic.nonce) {
|
|
1142
|
+
nonceCheck = "present-not-checked";
|
|
1143
|
+
}
|
|
1144
|
+
// RFC 6960 §4.2.2.1 — time-window enforcement. A "good" response is
|
|
1145
|
+
// valid only between thisUpdate and nextUpdate (with operator-tunable
|
|
1146
|
+
// skew). Without this check a stapled response is replayable forever:
|
|
1147
|
+
// an attacker captures a pre-revocation "good" reply, the cert later
|
|
1148
|
+
// gets revoked, the attacker keeps presenting the cached "good" and
|
|
1149
|
+
// the framework keeps accepting it. requireGood postures depend on
|
|
1150
|
+
// freshness — reject expired or future-dated responses outright.
|
|
1151
|
+
var clockSkewMs = typeof opts.clockSkewMs === "number" && opts.clockSkewMs >= 0 // allow:numeric-opt-Infinity — operator-supplied skew, default 5 min if absent or invalid
|
|
1152
|
+
? opts.clockSkewMs : C.TIME.minutes(5);
|
|
1153
|
+
var now = typeof opts.now === "number" ? opts.now : Date.now();
|
|
1154
|
+
var thisUpdateMs = match.thisUpdate ? Date.parse(match.thisUpdate) : NaN;
|
|
1155
|
+
var nextUpdateMs = match.nextUpdate ? Date.parse(match.nextUpdate) : NaN;
|
|
1156
|
+
if (!isFinite(thisUpdateMs)) {
|
|
1157
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1158
|
+
certStatus: match.certStatus,
|
|
1159
|
+
thisUpdate: match.thisUpdate, nextUpdate: match.nextUpdate,
|
|
1160
|
+
nonce: nonceCheck,
|
|
1161
|
+
errors: ["OCSP response missing thisUpdate (RFC 6960 §4.2.2.1)"] };
|
|
1162
|
+
}
|
|
1163
|
+
if (thisUpdateMs - clockSkewMs > now) {
|
|
1164
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1165
|
+
certStatus: match.certStatus,
|
|
1166
|
+
thisUpdate: match.thisUpdate, nextUpdate: match.nextUpdate,
|
|
1167
|
+
nonce: nonceCheck,
|
|
1168
|
+
errors: ["OCSP thisUpdate is in the future (RFC 6960 §4.2.2.1 — possible clock skew or response replay)"] };
|
|
1169
|
+
}
|
|
1170
|
+
if (isFinite(nextUpdateMs) && nextUpdateMs + clockSkewMs < now) {
|
|
1171
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1172
|
+
certStatus: match.certStatus,
|
|
1173
|
+
thisUpdate: match.thisUpdate, nextUpdate: match.nextUpdate,
|
|
1174
|
+
nonce: nonceCheck,
|
|
1175
|
+
errors: ["OCSP response is past nextUpdate (RFC 6960 §4.2.2.1 — stale response, possible replay)"] };
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
ok: match.certStatus === "good",
|
|
1179
|
+
status: parsed.status,
|
|
1180
|
+
certStatus: match.certStatus,
|
|
1181
|
+
thisUpdate: match.thisUpdate,
|
|
1182
|
+
nextUpdate: match.nextUpdate,
|
|
1183
|
+
signatureValid: true,
|
|
1184
|
+
nonce: nonceCheck,
|
|
1185
|
+
errors: match.certStatus === "good" ? [] :
|
|
1186
|
+
["certStatus=" + match.certStatus],
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// ---- OCSPRequest builder (RFC 6960 §4.1 + RFC 8954 nonce ext) ----
|
|
1191
|
+
//
|
|
1192
|
+
// Constructs a DER-encoded OCSPRequest for a single (leafCertDer,
|
|
1193
|
+
// issuerCertDer) pair, optionally with an RFC 8954 nonce extension.
|
|
1194
|
+
// Operators send the returned `requestDer` to the OCSP responder URL
|
|
1195
|
+
// (e.g. via b.httpClient with `Content-Type: application/ocsp-request`)
|
|
1196
|
+
// and pass `nonce` to `ocsp.evaluate(responseDer, { expectedNonce })`
|
|
1197
|
+
// to defend against replay attacks.
|
|
1198
|
+
//
|
|
1199
|
+
// Nonce DEFAULT ON — defense in depth. RFC 6960 §4.4.1 marks nonce
|
|
1200
|
+
// optional and some public responders (notably Let's Encrypt's) ignore
|
|
1201
|
+
// it; operators explicitly targeting those responders opt out via
|
|
1202
|
+
// `opts.nonce: false`. The framework default is RFC 8954 with 16 random
|
|
1203
|
+
// bytes (RFC 8954 §2.1 floor; ceiling 32).
|
|
1204
|
+
|
|
1205
|
+
function _extractIssuerNameDerAndKeyBitString(certDer) {
|
|
1206
|
+
// From the leaf cert's tbsCertificate, pull the issuer Name (DER) +
|
|
1207
|
+
// the issuer's SubjectPublicKey BIT STRING content. For OCSP CertID,
|
|
1208
|
+
// RFC 6960 §4.1.1 specifies hash(issuerName) and hash(issuerKey).
|
|
1209
|
+
// This helper operates on the ISSUER cert (not the leaf): the issuer's
|
|
1210
|
+
// Name and SubjectPublicKey are what get hashed.
|
|
1211
|
+
var top = asn1.readNode(certDer);
|
|
1212
|
+
if (top.tag !== asn1.TAG.SEQUENCE) {
|
|
1213
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-cert", "issuer cert is not a SEQUENCE");
|
|
1214
|
+
}
|
|
1215
|
+
var children = asn1.readSequence(top.value);
|
|
1216
|
+
if (children.length === 0) {
|
|
1217
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-cert", "issuer cert has no children");
|
|
1218
|
+
}
|
|
1219
|
+
var tbs = children[0];
|
|
1220
|
+
if (tbs.tag !== asn1.TAG.SEQUENCE) {
|
|
1221
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-cert", "tbsCertificate is not a SEQUENCE");
|
|
1222
|
+
}
|
|
1223
|
+
var tbsKids = asn1.readSequence(tbs.value);
|
|
1224
|
+
// Skip optional [0] EXPLICIT version, then serialNumber, signature,
|
|
1225
|
+
// then issuer (the cert's own subject in a self-signed CA, or its
|
|
1226
|
+
// issuer field for a sub-CA — we just want THIS cert's subject).
|
|
1227
|
+
var idx = 0;
|
|
1228
|
+
if (tbsKids.length > 0 &&
|
|
1229
|
+
tbsKids[0].tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC &&
|
|
1230
|
+
tbsKids[0].tag === 0) { // allow:raw-byte-literal — X.509 [0] EXPLICIT version tag
|
|
1231
|
+
idx = 1;
|
|
1232
|
+
}
|
|
1233
|
+
// After version: serialNumber, signature, issuer, validity, subject, SPKI.
|
|
1234
|
+
var subjectIdx = idx + 4; // allow:raw-byte-literal — X.509 TBSCertificate field count
|
|
1235
|
+
var spkiIdx = idx + 5; // allow:raw-byte-literal — X.509 TBSCertificate field count
|
|
1236
|
+
if (spkiIdx >= tbsKids.length) {
|
|
1237
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-cert", "issuer cert lacks SPKI field");
|
|
1238
|
+
}
|
|
1239
|
+
var subject = tbsKids[subjectIdx];
|
|
1240
|
+
var spki = tbsKids[spkiIdx];
|
|
1241
|
+
// Within SPKI: SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
|
|
1242
|
+
var spkiKids = asn1.readSequence(spki.value);
|
|
1243
|
+
if (spkiKids.length < 2) { // allow:raw-byte-literal — minimum SPKI fields
|
|
1244
|
+
throw new TlsTrustError("tls/ocsp-bad-issuer-cert", "SPKI missing subjectPublicKey BIT STRING");
|
|
1245
|
+
}
|
|
1246
|
+
var keyBytes = asn1.readBitString(spkiKids[1]);
|
|
1247
|
+
return {
|
|
1248
|
+
issuerNameDer: subject.raw, // the DER of the Name SEQUENCE (header + value)
|
|
1249
|
+
issuerKey: keyBytes,
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function _extractLeafSerial(leafCertDer) {
|
|
1254
|
+
var top = asn1.readNode(leafCertDer);
|
|
1255
|
+
if (top.tag !== asn1.TAG.SEQUENCE) {
|
|
1256
|
+
throw new TlsTrustError("tls/ocsp-bad-leaf-cert", "leaf cert is not a SEQUENCE");
|
|
1257
|
+
}
|
|
1258
|
+
var children = asn1.readSequence(top.value);
|
|
1259
|
+
var tbs = children[0];
|
|
1260
|
+
var tbsKids = asn1.readSequence(tbs.value);
|
|
1261
|
+
var idx = 0;
|
|
1262
|
+
if (tbsKids.length > 0 &&
|
|
1263
|
+
tbsKids[0].tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC &&
|
|
1264
|
+
tbsKids[0].tag === 0) { // allow:raw-byte-literal — X.509 [0] EXPLICIT version tag
|
|
1265
|
+
idx = 1;
|
|
1266
|
+
}
|
|
1267
|
+
// serialNumber is the next field after the optional version.
|
|
1268
|
+
return tbsKids[idx].value;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function buildOcspRequest(opts) {
|
|
1272
|
+
opts = opts || {};
|
|
1273
|
+
if (!Buffer.isBuffer(opts.leafCertDer)) {
|
|
1274
|
+
throw new TlsTrustError("tls/ocsp-bad-input",
|
|
1275
|
+
"buildRequest: opts.leafCertDer must be a Buffer (peer cert raw DER)");
|
|
1276
|
+
}
|
|
1277
|
+
if (!Buffer.isBuffer(opts.issuerCertDer)) {
|
|
1278
|
+
throw new TlsTrustError("tls/ocsp-bad-input",
|
|
1279
|
+
"buildRequest: opts.issuerCertDer must be a Buffer (issuer cert raw DER)");
|
|
1280
|
+
}
|
|
1281
|
+
var iss = _extractIssuerNameDerAndKeyBitString(opts.issuerCertDer);
|
|
1282
|
+
var serial = _extractLeafSerial(opts.leafCertDer);
|
|
1283
|
+
// CertID hashes — SHA-1 per RFC 6960 §4.1.1 (the only universally
|
|
1284
|
+
// supported algorithm; SHA-256 in OCSP requests is RFC 6960 §4.3
|
|
1285
|
+
// optional and many responders reject). The hash isn't security-
|
|
1286
|
+
// critical here — it's a name/key lookup, not an integrity check —
|
|
1287
|
+
// but operator compliance dashboards alerting on "anywhere in the
|
|
1288
|
+
// framework that touches SHA-1" need a signal. Emit an audit row
|
|
1289
|
+
// on every OCSP request build so the algorithm choice is visible
|
|
1290
|
+
// in the chain.
|
|
1291
|
+
var nameHash = nodeCrypto.createHash("sha1").update(iss.issuerNameDer).digest();
|
|
1292
|
+
var keyHash = nodeCrypto.createHash("sha1").update(iss.issuerKey).digest();
|
|
1293
|
+
setImmediate(function () {
|
|
1294
|
+
try {
|
|
1295
|
+
var auditMod = require("./audit"); // allow:inline-require — circular-load defense (audit imports network-tls)
|
|
1296
|
+
auditMod.safeEmit({
|
|
1297
|
+
action: "network.tls.ocsp.certid_built",
|
|
1298
|
+
outcome: "success",
|
|
1299
|
+
metadata: { hashAlgorithm: "sha1", note: "RFC 6960 §4.1.1 — non-security-critical lookup hash" },
|
|
1300
|
+
});
|
|
1301
|
+
} catch (_e) { /* drop-silent */ }
|
|
1302
|
+
});
|
|
1303
|
+
// hashAlgorithm AlgorithmIdentifier ::= SEQUENCE { algorithm OID, NULL }
|
|
1304
|
+
var algId = asn1.writeSequence([asn1.writeOid(OID_SHA1), asn1.writeNull()]);
|
|
1305
|
+
var certId = asn1.writeSequence([
|
|
1306
|
+
algId,
|
|
1307
|
+
asn1.writeOctetString(nameHash),
|
|
1308
|
+
asn1.writeOctetString(keyHash),
|
|
1309
|
+
asn1.writeInteger(serial),
|
|
1310
|
+
]);
|
|
1311
|
+
var requestNode = asn1.writeSequence([certId]);
|
|
1312
|
+
var requestList = asn1.writeSequence([requestNode]);
|
|
1313
|
+
var nonceBytes = null;
|
|
1314
|
+
var tbsChildren = [requestList];
|
|
1315
|
+
// Default ON per the framework security-defaults-on rule. Operators
|
|
1316
|
+
// talking to a responder that ignores nonces opt out via nonce: false.
|
|
1317
|
+
var includeNonce = opts.nonce !== false;
|
|
1318
|
+
if (includeNonce) {
|
|
1319
|
+
var nonceLen = typeof opts.nonceLen === "number" ? opts.nonceLen : 16; // allow:raw-byte-literal — RFC 8954 §2.1 nonce length floor
|
|
1320
|
+
if (nonceLen < 1 || nonceLen > 32) { // allow:raw-byte-literal — RFC 8954 §2.1 nonce length ceiling
|
|
1321
|
+
throw new TlsTrustError("tls/ocsp-bad-nonce-len",
|
|
1322
|
+
"nonce length out of RFC 8954 range (1..32)");
|
|
1323
|
+
}
|
|
1324
|
+
nonceBytes = nodeCrypto.randomBytes(nonceLen);
|
|
1325
|
+
// Extension ::= SEQUENCE { extnID OID, critical BOOL DEFAULT FALSE, extnValue OCTET STRING }
|
|
1326
|
+
// For nonce, extnValue is OCTET STRING wrapping the raw nonce bytes (RFC 8954 §2.1 — outer OCTET STRING only).
|
|
1327
|
+
var nonceExt = asn1.writeSequence([
|
|
1328
|
+
asn1.writeOid(OID_OCSP_NONCE),
|
|
1329
|
+
asn1.writeOctetString(nonceBytes),
|
|
1330
|
+
]);
|
|
1331
|
+
var extensions = asn1.writeSequence([nonceExt]);
|
|
1332
|
+
tbsChildren.push(asn1.writeContextExplicit(2, extensions)); // [2] EXPLICIT requestExtensions
|
|
1333
|
+
}
|
|
1334
|
+
var tbs = asn1.writeSequence(tbsChildren);
|
|
1335
|
+
var requestDer = asn1.writeSequence([tbs]);
|
|
1336
|
+
return { requestDer: requestDer, nonce: nonceBytes };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
var ocsp = Object.freeze({
|
|
1340
|
+
// Connect with OCSP requested. Returns { authorized, ocspBytes,
|
|
1341
|
+
// peerCert }. requireStapled: true makes empty / not-stapled responses
|
|
1342
|
+
// refuse instead of resolve. NOTE: requireStapled does NOT verify the
|
|
1343
|
+
// OCSP response signature — pair it with evaluateOcspResponse(bytes,
|
|
1344
|
+
// { issuerPem }) for full verification, OR use requireGood below.
|
|
1345
|
+
connect: function (opts) {
|
|
1346
|
+
return _connectAndCheckOcsp(opts || {}, false);
|
|
1347
|
+
},
|
|
1348
|
+
requireStapled: function (opts) {
|
|
1349
|
+
return _connectAndCheckOcsp(opts || {}, true);
|
|
1350
|
+
},
|
|
1351
|
+
// requireGood: connect + parse + verify signature + check certStatus.
|
|
1352
|
+
// Operator passes opts.issuerPem (the cert that signed the OCSP
|
|
1353
|
+
// response — typically the leaf's CA OR a delegated OCSP responder
|
|
1354
|
+
// cert). Throws TlsTrustError on any failure (no-staple, parse error,
|
|
1355
|
+
// signature mismatch, certStatus=revoked/unknown).
|
|
1356
|
+
requireGood: async function (opts) {
|
|
1357
|
+
opts = opts || {};
|
|
1358
|
+
if (!opts.issuerPem) {
|
|
1359
|
+
throw new TlsTrustError("tls/ocsp-missing-issuer",
|
|
1360
|
+
"ocsp.requireGood requires opts.issuerPem (PEM of the OCSP-signing cert)");
|
|
1361
|
+
}
|
|
1362
|
+
var rv = await _connectAndCheckOcsp(opts, true);
|
|
1363
|
+
if (!rv.ocspBytes || rv.ocspBytes.length === 0) {
|
|
1364
|
+
throw new TlsTrustError("tls/ocsp-empty",
|
|
1365
|
+
"OCSP response was empty");
|
|
1366
|
+
}
|
|
1367
|
+
var evald = evaluateOcspResponse(rv.ocspBytes, {
|
|
1368
|
+
issuerPem: opts.issuerPem,
|
|
1369
|
+
serialHex: opts.serialHex || null,
|
|
1370
|
+
});
|
|
1371
|
+
if (!evald.ok) {
|
|
1372
|
+
throw new TlsTrustError("tls/ocsp-not-good",
|
|
1373
|
+
"OCSP evaluation failed: " + evald.errors.join("; "));
|
|
1374
|
+
}
|
|
1375
|
+
return Object.assign({}, rv, { ocspEvaluation: evald });
|
|
1376
|
+
},
|
|
1377
|
+
parseResponse: parseOcspResponse,
|
|
1378
|
+
evaluate: evaluateOcspResponse,
|
|
1379
|
+
// buildRequest — construct a DER-encoded OCSPRequest for a single
|
|
1380
|
+
// (leafCertDer, issuerCertDer) pair. RFC 8954 nonce extension is ON
|
|
1381
|
+
// by default (16 random bytes; opts.nonceLen overrides within RFC
|
|
1382
|
+
// 8954's 1..32 range; opts.nonce: false opts out for responders that
|
|
1383
|
+
// ignore nonces). Returns { requestDer, nonce }; pass `nonce` to
|
|
1384
|
+
// evaluate({ expectedNonce }).
|
|
1385
|
+
buildRequest: buildOcspRequest,
|
|
1386
|
+
// inspectMustStaple — read the RFC 7633 TLS Feature extension on a
|
|
1387
|
+
// peer cert. Returns { mustStaple, features }. mustStaple === true
|
|
1388
|
+
// when status_request (5) is in the feature list; the cert is then
|
|
1389
|
+
// contractually required to ship an OCSP staple on every connection.
|
|
1390
|
+
inspectMustStaple: function (rawDer) {
|
|
1391
|
+
if (!Buffer.isBuffer(rawDer)) {
|
|
1392
|
+
throw new TlsTrustError("tls/ocsp-bad-input",
|
|
1393
|
+
"ocsp.inspectMustStaple: rawDer must be a Buffer (cert.raw)");
|
|
1394
|
+
}
|
|
1395
|
+
return _extractTlsFeatureExtensionFromCert(rawDer);
|
|
1396
|
+
},
|
|
1397
|
+
// requireMustStaple(peerCert, opts) — operator predicate. Refuses
|
|
1398
|
+
// when the cert advertises must-staple but no OCSP staple was
|
|
1399
|
+
// delivered (opts.ocspBytes empty/missing). When the cert does NOT
|
|
1400
|
+
// advertise must-staple, the predicate returns null (operator opted
|
|
1401
|
+
// in by setting opts.enforceUnconditional to also require staples
|
|
1402
|
+
// on certs that don't carry the extension).
|
|
1403
|
+
requireMustStaple: function (opts) {
|
|
1404
|
+
opts = opts || {};
|
|
1405
|
+
var enforceUnconditional = opts.enforceUnconditional === true;
|
|
1406
|
+
return function (peerCert, ctx) {
|
|
1407
|
+
if (!peerCert || !peerCert.raw) {
|
|
1408
|
+
return new TlsTrustError("tls/ocsp-no-cert",
|
|
1409
|
+
"requireMustStaple: peer cert.raw missing");
|
|
1410
|
+
}
|
|
1411
|
+
var feat = _extractTlsFeatureExtensionFromCert(peerCert.raw);
|
|
1412
|
+
var stapled = ctx && Buffer.isBuffer(ctx.ocspBytes) && ctx.ocspBytes.length > 0;
|
|
1413
|
+
if (feat.mustStaple && !stapled) {
|
|
1414
|
+
return new TlsTrustError("tls/ocsp-must-staple-violated",
|
|
1415
|
+
"cert advertises must-staple (RFC 7633) but no OCSP staple was delivered");
|
|
1416
|
+
}
|
|
1417
|
+
if (!feat.mustStaple && enforceUnconditional && !stapled) {
|
|
1418
|
+
return new TlsTrustError("tls/ocsp-staple-required",
|
|
1419
|
+
"operator policy requires OCSP staple but server did not provide one");
|
|
1420
|
+
}
|
|
1421
|
+
return null;
|
|
1422
|
+
};
|
|
1423
|
+
},
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// ---- Certificate Transparency (RFC 6962 + RFC 9162) SCT verifier --
|
|
1427
|
+
//
|
|
1428
|
+
// CT requires every TLS server certificate to carry at least 2 Signed
|
|
1429
|
+
// Certificate Timestamps (SCTs) from approved logs. Modern browsers
|
|
1430
|
+
// (Chrome / Safari) refuse certificates without sufficient SCTs.
|
|
1431
|
+
//
|
|
1432
|
+
// node:tls surfaces SCTs via TLSSocket.getPeerX509Certificate() →
|
|
1433
|
+
// X509Certificate.raw (the DER cert). The SCTs sit inside the cert as
|
|
1434
|
+
// the OCSP-aware extension OID 1.3.6.1.4.1.11129.2.4.2.
|
|
1435
|
+
//
|
|
1436
|
+
// b.network.tls.ct.verify(cert, opts) checks that the cert has at
|
|
1437
|
+
// least `minScts` SCTs and that each SCT references a log in
|
|
1438
|
+
// `approvedLogs`. Full SCT-signature verification against the log's
|
|
1439
|
+
// pubkey is OUT of scope for this patch — that requires log-pubkey
|
|
1440
|
+
// distribution + ASN.1 SCT parsing. The framework provides the
|
|
1441
|
+
// SCT-presence + log-id check; signature verification is a follow-up
|
|
1442
|
+
// when the ASN.1 dependency lands.
|
|
1443
|
+
|
|
1444
|
+
// SCT extension OID per RFC 6962 §3.3.
|
|
1445
|
+
var OID_CT_SCT_LIST = "1.3.6.1.4.1.11129.2.4.2";
|
|
1446
|
+
|
|
1447
|
+
// Walk a DER X.509 cert and locate the SCT extension's OCTET STRING
|
|
1448
|
+
// content. Returns { sctListRaw } or { sctListRaw: null } when no SCT
|
|
1449
|
+
// extension is present.
|
|
1450
|
+
function _extractSctExtensionFromCert(certDer) {
|
|
1451
|
+
// Tolerant of malformed cert buffers — return null sctListRaw when
|
|
1452
|
+
// the ASN.1 walk fails. Callers (parseScts / verifyScts) treat that
|
|
1453
|
+
// as "no SCT extension" rather than throwing on broken input.
|
|
1454
|
+
var top;
|
|
1455
|
+
try { top = asn1.readNode(certDer); }
|
|
1456
|
+
catch (_e) { return { sctListRaw: null }; }
|
|
1457
|
+
if (top.tag !== asn1.TAG.SEQUENCE) return { sctListRaw: null };
|
|
1458
|
+
var children;
|
|
1459
|
+
try { children = asn1.readSequence(top.value); }
|
|
1460
|
+
catch (_e) { return { sctListRaw: null }; }
|
|
1461
|
+
if (children.length === 0) return { sctListRaw: null };
|
|
1462
|
+
// Cert ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signature }
|
|
1463
|
+
var tbs = children[0];
|
|
1464
|
+
if (tbs.tag !== asn1.TAG.SEQUENCE) return { sctListRaw: null };
|
|
1465
|
+
// tbsCertificate ::= SEQUENCE { ..., extensions [3] EXPLICIT ... }
|
|
1466
|
+
var tbsChildren;
|
|
1467
|
+
try { tbsChildren = asn1.readSequence(tbs.value); }
|
|
1468
|
+
catch (_e) { return { sctListRaw: null }; }
|
|
1469
|
+
var extensionsNode = null;
|
|
1470
|
+
for (var i = 0; i < tbsChildren.length; i += 1) {
|
|
1471
|
+
var ch = tbsChildren[i];
|
|
1472
|
+
if (ch.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && ch.tag === 3) { // allow:raw-byte-literal — X.509 [3] EXPLICIT extensions tag
|
|
1473
|
+
extensionsNode = asn1.readNode(ch.value, 0);
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (!extensionsNode || extensionsNode.tag !== asn1.TAG.SEQUENCE) {
|
|
1478
|
+
return { sctListRaw: null };
|
|
1479
|
+
}
|
|
1480
|
+
var extensions = asn1.readSequence(extensionsNode.value);
|
|
1481
|
+
for (var e = 0; e < extensions.length; e += 1) {
|
|
1482
|
+
var ext = extensions[e]; // Extension ::= SEQUENCE { extnID OID, critical BOOL OPTIONAL, extnValue OCTET STRING }
|
|
1483
|
+
if (ext.tag !== asn1.TAG.SEQUENCE) continue;
|
|
1484
|
+
var extChildren = asn1.readSequence(ext.value);
|
|
1485
|
+
if (extChildren.length === 0) continue;
|
|
1486
|
+
var extOid = asn1.readOid(extChildren[0]);
|
|
1487
|
+
if (extOid !== OID_CT_SCT_LIST) continue;
|
|
1488
|
+
// The last child is the OCTET STRING extnValue. Per RFC 6962 §3.3
|
|
1489
|
+
// that OCTET STRING wraps a SECOND OCTET STRING which contains the
|
|
1490
|
+
// raw SignedCertificateTimestampList (TLS-encoded).
|
|
1491
|
+
var extnValueOuter = asn1.readOctetString(extChildren[extChildren.length - 1]);
|
|
1492
|
+
var inner = asn1.readNode(extnValueOuter);
|
|
1493
|
+
if (inner.tag !== asn1.TAG.OCTET_STRING) {
|
|
1494
|
+
throw new TlsTrustError("tls/ct-bad-extension",
|
|
1495
|
+
"SCT extension extnValue does not wrap a second OCTET STRING");
|
|
1496
|
+
}
|
|
1497
|
+
return { sctListRaw: inner.value };
|
|
1498
|
+
}
|
|
1499
|
+
return { sctListRaw: null };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// TLS Feature extension OID per RFC 7633 §6. The extension value is
|
|
1503
|
+
// SEQUENCE OF INTEGER; the integer 5 == status_request == "must-staple".
|
|
1504
|
+
var OID_TLS_FEATURE = "1.3.6.1.5.5.7.1.24";
|
|
1505
|
+
var TLS_FEATURE_STATUS_REQUEST = 5;
|
|
1506
|
+
|
|
1507
|
+
// Walk a DER X.509 cert and return the TLS Feature extension's
|
|
1508
|
+
// integer list. Returns { mustStaple, features }. Tolerant of
|
|
1509
|
+
// malformed cert input — mirrors _extractSctExtensionFromCert's
|
|
1510
|
+
// try/catch tolerance.
|
|
1511
|
+
function _extractTlsFeatureExtensionFromCert(certDer) {
|
|
1512
|
+
var none = { mustStaple: false, features: [] };
|
|
1513
|
+
var top;
|
|
1514
|
+
try { top = asn1.readNode(certDer); }
|
|
1515
|
+
catch (_e) { return none; }
|
|
1516
|
+
if (top.tag !== asn1.TAG.SEQUENCE) return none;
|
|
1517
|
+
var children;
|
|
1518
|
+
try { children = asn1.readSequence(top.value); }
|
|
1519
|
+
catch (_e) { return none; }
|
|
1520
|
+
if (children.length === 0) return none;
|
|
1521
|
+
var tbs = children[0];
|
|
1522
|
+
if (tbs.tag !== asn1.TAG.SEQUENCE) return none;
|
|
1523
|
+
var tbsChildren;
|
|
1524
|
+
try { tbsChildren = asn1.readSequence(tbs.value); }
|
|
1525
|
+
catch (_e) { return none; }
|
|
1526
|
+
var extensionsNode = null;
|
|
1527
|
+
for (var i = 0; i < tbsChildren.length; i += 1) {
|
|
1528
|
+
var ch = tbsChildren[i];
|
|
1529
|
+
if (ch.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && ch.tag === 3) { // allow:raw-byte-literal — X.509 [3] EXPLICIT extensions tag
|
|
1530
|
+
extensionsNode = asn1.readNode(ch.value, 0);
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (!extensionsNode || extensionsNode.tag !== asn1.TAG.SEQUENCE) return none;
|
|
1535
|
+
var extensions = asn1.readSequence(extensionsNode.value);
|
|
1536
|
+
for (var e = 0; e < extensions.length; e += 1) {
|
|
1537
|
+
var ext = extensions[e];
|
|
1538
|
+
if (ext.tag !== asn1.TAG.SEQUENCE) continue;
|
|
1539
|
+
var extChildren = asn1.readSequence(ext.value);
|
|
1540
|
+
if (extChildren.length === 0) continue;
|
|
1541
|
+
var extOid;
|
|
1542
|
+
try { extOid = asn1.readOid(extChildren[0]); }
|
|
1543
|
+
catch (_e2) { continue; }
|
|
1544
|
+
if (extOid !== OID_TLS_FEATURE) continue;
|
|
1545
|
+
var extnValue = asn1.readOctetString(extChildren[extChildren.length - 1]);
|
|
1546
|
+
// extnValue wraps SEQUENCE OF INTEGER.
|
|
1547
|
+
var seq;
|
|
1548
|
+
try { seq = asn1.readNode(extnValue); }
|
|
1549
|
+
catch (_e3) { return none; }
|
|
1550
|
+
if (seq.tag !== asn1.TAG.SEQUENCE) return none;
|
|
1551
|
+
var feats = asn1.readSequence(seq.value);
|
|
1552
|
+
var ints = [];
|
|
1553
|
+
var mustStaple = false;
|
|
1554
|
+
for (var f = 0; f < feats.length; f += 1) {
|
|
1555
|
+
try {
|
|
1556
|
+
var n = asn1.readUnsignedInt(feats[f]);
|
|
1557
|
+
ints.push(n);
|
|
1558
|
+
if (n === TLS_FEATURE_STATUS_REQUEST) mustStaple = true;
|
|
1559
|
+
} catch (_e4) { /* ignore non-integer entries */ }
|
|
1560
|
+
}
|
|
1561
|
+
return { mustStaple: mustStaple, features: ints };
|
|
1562
|
+
}
|
|
1563
|
+
return none;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// Parse the TLS-encoded SignedCertificateTimestampList (RFC 6962 §3.3).
|
|
1567
|
+
// Format: 2-byte length + concatenation of individual SCTs, each
|
|
1568
|
+
// itself prefixed by a 2-byte length.
|
|
1569
|
+
function _parseSctList(sctListRaw) {
|
|
1570
|
+
if (!Buffer.isBuffer(sctListRaw) || sctListRaw.length < 2) { // allow:raw-byte-literal — outer 2-byte length prefix
|
|
1571
|
+
throw new TlsTrustError("tls/ct-bad-list",
|
|
1572
|
+
"SCT list shorter than the outer length prefix");
|
|
1573
|
+
}
|
|
1574
|
+
var totalLen = sctListRaw.readUInt16BE(0);
|
|
1575
|
+
if (totalLen + 2 !== sctListRaw.length) { // allow:raw-byte-literal — outer length prefix
|
|
1576
|
+
throw new TlsTrustError("tls/ct-bad-list",
|
|
1577
|
+
"SCT list outer length " + totalLen + " does not match buffer " +
|
|
1578
|
+
(sctListRaw.length - 2));
|
|
1579
|
+
}
|
|
1580
|
+
var pos = 2; // allow:raw-byte-literal — past the outer prefix
|
|
1581
|
+
var scts = [];
|
|
1582
|
+
while (pos < sctListRaw.length) {
|
|
1583
|
+
var sctLen = sctListRaw.readUInt16BE(pos);
|
|
1584
|
+
pos += 2;
|
|
1585
|
+
if (pos + sctLen > sctListRaw.length) {
|
|
1586
|
+
throw new TlsTrustError("tls/ct-bad-list",
|
|
1587
|
+
"SCT[" + scts.length + "] declared length " + sctLen +
|
|
1588
|
+
" extends past the list buffer");
|
|
1589
|
+
}
|
|
1590
|
+
var sctBytes = sctListRaw.slice(pos, pos + sctLen);
|
|
1591
|
+
scts.push(_parseSct(sctBytes));
|
|
1592
|
+
pos += sctLen;
|
|
1593
|
+
}
|
|
1594
|
+
return scts;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Per RFC 6962 §3.2 — a single SCT:
|
|
1598
|
+
// sct_version (1 byte) — 0 = v1
|
|
1599
|
+
// id (LogID) (32 bytes) — SHA-256 of log's pubkey
|
|
1600
|
+
// timestamp (8 bytes) — uint64 ms since epoch
|
|
1601
|
+
// ct_extensions (2-byte len + N) — usually empty
|
|
1602
|
+
// signature DigitallySigned (hash + sig algo + 2-byte len + N)
|
|
1603
|
+
function _parseSct(sctBuf) {
|
|
1604
|
+
if (sctBuf.length < 1 + 32 + 8 + 2 + 4) { // allow:raw-byte-literal — minimum SCT v1 byte total
|
|
1605
|
+
throw new TlsTrustError("tls/ct-sct-too-short",
|
|
1606
|
+
"SCT is shorter than the minimum v1 layout (" + sctBuf.length + " bytes)");
|
|
1607
|
+
}
|
|
1608
|
+
var version = sctBuf[0];
|
|
1609
|
+
if (version !== 0) {
|
|
1610
|
+
throw new TlsTrustError("tls/ct-sct-bad-version",
|
|
1611
|
+
"SCT version is not 0 (v1): got " + version);
|
|
1612
|
+
}
|
|
1613
|
+
var logId = sctBuf.slice(1, 1 + 32); // allow:raw-byte-literal — RFC 6962 32-byte LogID
|
|
1614
|
+
var timestamp = Number(sctBuf.readBigUInt64BE(1 + 32)); // allow:raw-byte-literal — past LogID
|
|
1615
|
+
var extLen = sctBuf.readUInt16BE(1 + 32 + 8); // allow:raw-byte-literal — past LogID + timestamp
|
|
1616
|
+
var pos = 1 + 32 + 8 + 2; // allow:raw-byte-literal — past extLen field
|
|
1617
|
+
var extensions = sctBuf.slice(pos, pos + extLen);
|
|
1618
|
+
pos += extLen;
|
|
1619
|
+
if (pos + 4 > sctBuf.length) { // allow:raw-byte-literal — DigitallySigned header (hash + alg + len)
|
|
1620
|
+
throw new TlsTrustError("tls/ct-sct-truncated",
|
|
1621
|
+
"SCT truncated before DigitallySigned");
|
|
1622
|
+
}
|
|
1623
|
+
var hashAlgo = sctBuf[pos];
|
|
1624
|
+
var sigAlgo = sctBuf[pos + 1];
|
|
1625
|
+
pos += 2; // allow:raw-byte-literal — past hash+alg pair
|
|
1626
|
+
var sigLen = sctBuf.readUInt16BE(pos);
|
|
1627
|
+
pos += 2; // allow:raw-byte-literal — past sig length
|
|
1628
|
+
if (pos + sigLen !== sctBuf.length) {
|
|
1629
|
+
throw new TlsTrustError("tls/ct-sct-truncated",
|
|
1630
|
+
"SCT signature length " + sigLen + " does not match remaining bytes " +
|
|
1631
|
+
(sctBuf.length - pos));
|
|
1632
|
+
}
|
|
1633
|
+
var signature = sctBuf.slice(pos, pos + sigLen);
|
|
1634
|
+
return {
|
|
1635
|
+
version: version,
|
|
1636
|
+
logId: logId,
|
|
1637
|
+
logIdHex: logId.toString("hex"),
|
|
1638
|
+
timestamp: timestamp,
|
|
1639
|
+
extensions: extensions,
|
|
1640
|
+
hashAlgo: hashAlgo, // RFC 5246 HashAlgorithm enum (4=sha256, 5=sha384, 6=sha512)
|
|
1641
|
+
sigAlgo: sigAlgo, // RFC 5246 SignatureAlgorithm enum (1=rsa, 3=ecdsa)
|
|
1642
|
+
signature: signature,
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// Build the canonical signed-entry per RFC 6962 §3.2 for X.509
|
|
1647
|
+
// pre-cert-free chains (issued cert path):
|
|
1648
|
+
// sct_version (1) || signature_type (1=certificate_timestamp) ||
|
|
1649
|
+
// timestamp (8) || entry_type (0=x509_entry) ||
|
|
1650
|
+
// signed_entry (3-byte length || ASN.1 cert without SCT extension) ||
|
|
1651
|
+
// ct_extensions (2-byte length || N)
|
|
1652
|
+
function _buildSctSignedEntry(certWithoutSctDer, sct) {
|
|
1653
|
+
var head = Buffer.alloc(1 + 1 + 8 + 2); // allow:raw-byte-literal — fixed-shape header bytes
|
|
1654
|
+
head[0] = sct.version;
|
|
1655
|
+
head[1] = 0; // signature_type = certificate_timestamp
|
|
1656
|
+
head.writeBigUInt64BE(BigInt(sct.timestamp), 2); // allow:raw-byte-literal — past version+sig-type
|
|
1657
|
+
head.writeUInt16BE(0, 10); // allow:raw-byte-literal — entry_type = x509_entry (2 bytes; high byte = 0, low byte = 0)
|
|
1658
|
+
// signed_entry: 3-byte length prefix + cert DER.
|
|
1659
|
+
var lenBytes = Buffer.alloc(3); // allow:raw-byte-literal — RFC 6962 24-bit length prefix
|
|
1660
|
+
lenBytes[0] = (certWithoutSctDer.length >> 16) & 0xff; // allow:raw-byte-literal — base-256 length high byte
|
|
1661
|
+
lenBytes[1] = (certWithoutSctDer.length >> 8) & 0xff; // allow:raw-byte-literal — base-256 length mid byte
|
|
1662
|
+
lenBytes[2] = certWithoutSctDer.length & 0xff; // allow:raw-byte-literal — base-256 length low byte
|
|
1663
|
+
// ct_extensions: 2-byte length + bytes.
|
|
1664
|
+
var extHead = Buffer.alloc(2); // allow:raw-byte-literal — RFC 6962 2-byte ct_extensions length prefix
|
|
1665
|
+
extHead.writeUInt16BE(sct.extensions.length, 0);
|
|
1666
|
+
return Buffer.concat([head, lenBytes, certWithoutSctDer, extHead, sct.extensions]);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Strip the SCT extension from a DER cert + return the rebuilt cert
|
|
1670
|
+
// bytes for SCT signing per RFC 6962 §3.2. The strip is byte-precise:
|
|
1671
|
+
// walk the TBSCertificate extensions list, drop the SCT extension,
|
|
1672
|
+
// and re-encode just enough of the chain to reproduce the original
|
|
1673
|
+
// shape minus that one extension. This is non-trivial because the
|
|
1674
|
+
// tbsCertificate length, certificate length, and signature-bytes
|
|
1675
|
+
// boundaries all shift.
|
|
1676
|
+
//
|
|
1677
|
+
// Simpler: rebuild only the tbsCertificate extensions SEQUENCE without
|
|
1678
|
+
// the SCT entry, recompute lengths above it, and replace the cert's
|
|
1679
|
+
// SignedCertificate (BIT STRING) with the original's signature too —
|
|
1680
|
+
// but that's incorrect since the original signature was computed over
|
|
1681
|
+
// the WITH-SCT TBS. The CT log signed an entry built from the
|
|
1682
|
+
// without-SCT pre-issuance shape, NOT the issued cert's tbs.
|
|
1683
|
+
//
|
|
1684
|
+
// Per RFC 6962 §3.1, log servers receive a "TBSCertificate" minus the
|
|
1685
|
+
// SCT extension from the CA. The signed_entry the framework
|
|
1686
|
+
// reconstructs is that pre-extension TBSCertificate. We compute it by
|
|
1687
|
+
// removing the SCT extension at the byte level and rebuilding all
|
|
1688
|
+
// outer length prefixes.
|
|
1689
|
+
function _stripSctExtensionFromCert(certDer) {
|
|
1690
|
+
var top = asn1.readNode(certDer);
|
|
1691
|
+
if (top.tag !== asn1.TAG.SEQUENCE) {
|
|
1692
|
+
throw new TlsTrustError("tls/ct-bad-cert", "Certificate is not a SEQUENCE");
|
|
1693
|
+
}
|
|
1694
|
+
var topChildren = asn1.readSequence(top.value);
|
|
1695
|
+
var tbs = topChildren[0];
|
|
1696
|
+
if (tbs.tag !== asn1.TAG.SEQUENCE) {
|
|
1697
|
+
throw new TlsTrustError("tls/ct-bad-cert", "tbsCertificate is not a SEQUENCE");
|
|
1698
|
+
}
|
|
1699
|
+
// Walk tbsCertificate to find the [3] EXPLICIT extensions wrapper.
|
|
1700
|
+
var tbsChildren = asn1.readSequence(tbs.value);
|
|
1701
|
+
var newTbsChildrenBytes = [];
|
|
1702
|
+
var foundExtensions = false;
|
|
1703
|
+
for (var i = 0; i < tbsChildren.length; i += 1) {
|
|
1704
|
+
var ch = tbsChildren[i];
|
|
1705
|
+
if (ch.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && ch.tag === 3) { // allow:raw-byte-literal — [3] EXPLICIT extensions tag
|
|
1706
|
+
foundExtensions = true;
|
|
1707
|
+
// Inner SEQUENCE OF Extensions.
|
|
1708
|
+
var inner = asn1.readNode(ch.value, 0);
|
|
1709
|
+
var extList = asn1.readSequence(inner.value);
|
|
1710
|
+
var keptExtBytes = [];
|
|
1711
|
+
for (var j = 0; j < extList.length; j += 1) {
|
|
1712
|
+
var ext = extList[j];
|
|
1713
|
+
var extBytes = ext.value;
|
|
1714
|
+
var extDescChildren = asn1.readSequence(ext.value);
|
|
1715
|
+
if (extDescChildren.length > 0) {
|
|
1716
|
+
try {
|
|
1717
|
+
var oid = asn1.readOid(extDescChildren[0]);
|
|
1718
|
+
if (oid === OID_CT_SCT_LIST) continue; // drop the SCT extension
|
|
1719
|
+
} catch (_e) { /* not an OID — keep the extension as-is */ }
|
|
1720
|
+
}
|
|
1721
|
+
// Re-encode this extension verbatim (we have the original bytes).
|
|
1722
|
+
var origExt = certDer.slice(0, 0); // placeholder; we rebuild from the parsed node below
|
|
1723
|
+
void origExt;
|
|
1724
|
+
keptExtBytes.push(_encodeAsn1(asn1.TAG.SEQUENCE, true, extBytes));
|
|
1725
|
+
void extBytes;
|
|
1726
|
+
}
|
|
1727
|
+
var newExtSeq = _encodeAsn1(asn1.TAG.SEQUENCE, true, Buffer.concat(keptExtBytes));
|
|
1728
|
+
var newExplicit3 = _encodeContextExplicit(3, newExtSeq);
|
|
1729
|
+
newTbsChildrenBytes.push(newExplicit3);
|
|
1730
|
+
} else {
|
|
1731
|
+
// Re-encode the original child verbatim by slicing its bytes from
|
|
1732
|
+
// the parent's value buffer.
|
|
1733
|
+
var childDer = _encodeAsn1FromNode(ch);
|
|
1734
|
+
newTbsChildrenBytes.push(childDer);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (!foundExtensions) {
|
|
1738
|
+
// Cert has no extensions at all — caller's SCT lookup would have
|
|
1739
|
+
// returned no SCT bytes, so this path shouldn't run. Surface anyway.
|
|
1740
|
+
throw new TlsTrustError("tls/ct-no-extensions",
|
|
1741
|
+
"cert has no extensions to strip from");
|
|
1742
|
+
}
|
|
1743
|
+
var newTbsValue = Buffer.concat(newTbsChildrenBytes);
|
|
1744
|
+
var newTbs = _encodeAsn1(asn1.TAG.SEQUENCE, true, newTbsValue);
|
|
1745
|
+
return newTbs;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// Minimal DER encoder helpers — enough to rebuild a TBS without the
|
|
1749
|
+
// SCT extension. Tag class is universal for SEQUENCE; constructed
|
|
1750
|
+
// flag wired explicitly.
|
|
1751
|
+
function _encodeLength(len) {
|
|
1752
|
+
if (len < 0x80) return Buffer.from([len]); // allow:raw-byte-literal — DER short-form length threshold
|
|
1753
|
+
var tmp = [];
|
|
1754
|
+
var n = len;
|
|
1755
|
+
while (n > 0) {
|
|
1756
|
+
tmp.unshift(n & 0xff); // allow:raw-byte-literal — base-256 byte
|
|
1757
|
+
n = n >>> 8; // allow:raw-byte-literal — byte shift
|
|
1758
|
+
}
|
|
1759
|
+
return Buffer.concat([Buffer.from([0x80 | tmp.length]), Buffer.from(tmp)]); // allow:raw-byte-literal — DER long-form length flag
|
|
1760
|
+
}
|
|
1761
|
+
function _encodeAsn1(tag, constructed, value) {
|
|
1762
|
+
var tagByte = (constructed ? 0x20 : 0x00) | tag; // allow:raw-byte-literal — DER constructed bit + universal tag
|
|
1763
|
+
return Buffer.concat([Buffer.from([tagByte]), _encodeLength(value.length), value]);
|
|
1764
|
+
}
|
|
1765
|
+
function _encodeContextExplicit(num, value) {
|
|
1766
|
+
// Context-specific class (10) + constructed (20) | tag.
|
|
1767
|
+
var tagByte = 0xa0 | num; // allow:raw-byte-literal — DER context-specific + constructed
|
|
1768
|
+
return Buffer.concat([Buffer.from([tagByte]), _encodeLength(value.length), value]);
|
|
1769
|
+
}
|
|
1770
|
+
function _encodeAsn1FromNode(node) {
|
|
1771
|
+
// Re-encode a parsed node verbatim by replaying the tag + length +
|
|
1772
|
+
// value. Universal-class shortcut: if class is universal, set the
|
|
1773
|
+
// tag byte from the universal table; if constructed, set the bit.
|
|
1774
|
+
// Context-specific / application / private classes get their bytes
|
|
1775
|
+
// restored directly. This works for the simple shapes we walk.
|
|
1776
|
+
var tagByte;
|
|
1777
|
+
if (node.tagClass === asn1.TAG_CLASS.UNIVERSAL) {
|
|
1778
|
+
tagByte = (node.constructed ? 0x20 : 0x00) | (node.tag & 0x1f); // allow:raw-byte-literal — DER constructed bit + universal tag
|
|
1779
|
+
} else {
|
|
1780
|
+
var classBits = (node.tagClass & 0x03) << 6; // allow:raw-byte-literal — DER tag-class bits
|
|
1781
|
+
tagByte = classBits | (node.constructed ? 0x20 : 0x00) | (node.tag & 0x1f); // allow:raw-byte-literal — DER constructed bit + low-tag
|
|
1782
|
+
}
|
|
1783
|
+
return Buffer.concat([Buffer.from([tagByte]), _encodeLength(node.value.length), node.value]);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// SCT signature verification per RFC 6962 §3.2. opts.logKeys maps
|
|
1787
|
+
// log_id (hex) → PEM public key. Operators populate from the Chrome
|
|
1788
|
+
// CT log list (https://www.gstatic.com/ct/log_list/v3/log_list.json
|
|
1789
|
+
// or equivalent) — log keys rotate, so the framework does NOT bake
|
|
1790
|
+
// them in; that drift is the operator's to manage.
|
|
1791
|
+
function verifyScts(certDer, opts) {
|
|
1792
|
+
opts = opts || {};
|
|
1793
|
+
if (!Buffer.isBuffer(certDer)) {
|
|
1794
|
+
throw new TlsTrustError("tls/ct-bad-input",
|
|
1795
|
+
"verifyScts: certDer must be a Buffer");
|
|
1796
|
+
}
|
|
1797
|
+
var logKeys = opts.logKeys || {};
|
|
1798
|
+
var minScts = typeof opts.minScts === "number" ? opts.minScts : 2; // allow:raw-byte-literal — Chrome CT policy min-2-SCTs
|
|
1799
|
+
var ext = _extractSctExtensionFromCert(certDer);
|
|
1800
|
+
if (!ext.sctListRaw) {
|
|
1801
|
+
return { ok: false, reason: "no-sct-extension", scts: [] };
|
|
1802
|
+
}
|
|
1803
|
+
var scts;
|
|
1804
|
+
try { scts = _parseSctList(ext.sctListRaw); }
|
|
1805
|
+
catch (e) {
|
|
1806
|
+
return { ok: false, reason: "parse-error",
|
|
1807
|
+
error: (e && e.message) || String(e), scts: [] };
|
|
1808
|
+
}
|
|
1809
|
+
// Strip the SCT extension to compute the signed-entry per §3.2.
|
|
1810
|
+
var stripped;
|
|
1811
|
+
try { stripped = _stripSctExtensionFromCert(certDer); }
|
|
1812
|
+
catch (e) {
|
|
1813
|
+
return { ok: false, reason: "strip-failed",
|
|
1814
|
+
error: (e && e.message) || String(e), scts: scts };
|
|
1815
|
+
}
|
|
1816
|
+
var verifiedCount = 0;
|
|
1817
|
+
var perSctResults = [];
|
|
1818
|
+
for (var s = 0; s < scts.length; s += 1) {
|
|
1819
|
+
var sct = scts[s];
|
|
1820
|
+
var pem = logKeys[sct.logIdHex];
|
|
1821
|
+
if (!pem) {
|
|
1822
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1823
|
+
reason: "log-key-missing" });
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
var signedEntry;
|
|
1827
|
+
try { signedEntry = _buildSctSignedEntry(stripped, sct); }
|
|
1828
|
+
catch (e) {
|
|
1829
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1830
|
+
reason: "build-entry-failed",
|
|
1831
|
+
error: (e && e.message) || String(e) });
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
var nodeAlgo = sct.hashAlgo === 4 ? "sha256" : // allow:raw-byte-literal — TLS 1.2 HashAlgorithm enum sha256
|
|
1835
|
+
sct.hashAlgo === 5 ? "sha384" : // allow:raw-byte-literal — TLS 1.2 HashAlgorithm enum sha384
|
|
1836
|
+
sct.hashAlgo === 6 ? "sha512" : // allow:raw-byte-literal — TLS 1.2 HashAlgorithm enum sha512
|
|
1837
|
+
null;
|
|
1838
|
+
if (nodeAlgo === null) {
|
|
1839
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1840
|
+
reason: "unsupported-hash-algo", hashAlgo: sct.hashAlgo });
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
var keyObj;
|
|
1844
|
+
try { keyObj = nodeCrypto.createPublicKey(pem); }
|
|
1845
|
+
catch (e) {
|
|
1846
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1847
|
+
reason: "log-key-parse-failed",
|
|
1848
|
+
error: (e && e.message) || String(e) });
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
// RFC 6962 §2.1.4 — log-key SignatureAndHashAlgorithm pair must
|
|
1852
|
+
// match the SCT's signatureAlgorithm. signatureAlgo enum 1=RSA,
|
|
1853
|
+
// 3=ECDSA. Cross-check against the actual log-key type so a
|
|
1854
|
+
// malformed log-keys map can't silently accept SCTs signed
|
|
1855
|
+
// under one algorithm against a key registered under another.
|
|
1856
|
+
var keyType = keyObj.asymmetricKeyType;
|
|
1857
|
+
var sctSigAlgo = sct.signatureAlgo;
|
|
1858
|
+
var algoOk = (sctSigAlgo === 1 && keyType === "rsa") || // allow:raw-byte-literal — TLS 1.2 SignatureAlgorithm rsa
|
|
1859
|
+
(sctSigAlgo === 3 && (keyType === "ec" || keyType === "ecdsa")); // allow:raw-byte-literal — TLS 1.2 SignatureAlgorithm ecdsa
|
|
1860
|
+
if (!algoOk) {
|
|
1861
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1862
|
+
reason: "log-key-algo-mismatch",
|
|
1863
|
+
sctSignatureAlgo: sctSigAlgo, logKeyType: keyType });
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
var verified;
|
|
1867
|
+
try { verified = nodeCrypto.verify(nodeAlgo, signedEntry, keyObj, sct.signature); }
|
|
1868
|
+
catch (e) {
|
|
1869
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
1870
|
+
reason: "verify-threw",
|
|
1871
|
+
error: (e && e.message) || String(e) });
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: verified });
|
|
1875
|
+
if (verified) verifiedCount += 1;
|
|
1876
|
+
}
|
|
1877
|
+
return {
|
|
1878
|
+
ok: verifiedCount >= minScts,
|
|
1879
|
+
reason: verifiedCount >= minScts ? null : "insufficient-verified",
|
|
1880
|
+
minScts: minScts,
|
|
1881
|
+
verifiedCount: verifiedCount,
|
|
1882
|
+
totalScts: scts.length,
|
|
1883
|
+
scts: perSctResults,
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// ---- RFC 9162 §2.1 Merkle tree primitives ----
|
|
1888
|
+
//
|
|
1889
|
+
// CT v2 (RFC 9162) inclusion + consistency proofs operate on a binary
|
|
1890
|
+
// Merkle tree with the following node hashes (RFC 9162 §2.1.1):
|
|
1891
|
+
//
|
|
1892
|
+
// MTH(empty) = SHA-256("") — empty tree
|
|
1893
|
+
// MTH({d}) = SHA-256(0x00 || d) — leaf
|
|
1894
|
+
// MTH(D) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n])) — internal
|
|
1895
|
+
//
|
|
1896
|
+
// SHA-256 is the algorithm RFC 9162 mandates; the framework's PQC-first
|
|
1897
|
+
// posture does not apply here because the algorithm is wire-defined
|
|
1898
|
+
// by the CT log itself and changing it would break interop with every
|
|
1899
|
+
// public log. A future SHA3-flavoured CT (no draft as of writing) ships
|
|
1900
|
+
// alongside, not in place.
|
|
1901
|
+
//
|
|
1902
|
+
// LEAF_HASH_PREFIX = 0x00
|
|
1903
|
+
// INNER_HASH_PREFIX = 0x01
|
|
1904
|
+
// k = largest power of 2 < n (RFC 9162 §2.1.1)
|
|
1905
|
+
|
|
1906
|
+
var CT_LEAF_HASH_PREFIX = 0x00;
|
|
1907
|
+
var CT_INNER_HASH_PREFIX = 0x01;
|
|
1908
|
+
|
|
1909
|
+
function _ctSha256(buf) {
|
|
1910
|
+
return nodeCrypto.createHash("sha256").update(buf).digest();
|
|
1911
|
+
}
|
|
1912
|
+
function _ctLeafHash(leafBytes) {
|
|
1913
|
+
return _ctSha256(Buffer.concat([Buffer.from([CT_LEAF_HASH_PREFIX]), leafBytes]));
|
|
1914
|
+
}
|
|
1915
|
+
function _ctInnerHash(left, right) {
|
|
1916
|
+
return Buffer.concat([Buffer.from([CT_INNER_HASH_PREFIX]), left, right]);
|
|
1917
|
+
}
|
|
1918
|
+
function _ctInnerHashFinal(left, right) {
|
|
1919
|
+
return _ctSha256(_ctInnerHash(left, right));
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// _ctLargestPowerOf2LessThan — k from RFC 9162 §2.1.1: the largest
|
|
1923
|
+
// power of 2 that is strictly less than n. n must be > 1.
|
|
1924
|
+
function _ctLargestPowerOf2LessThan(n) {
|
|
1925
|
+
if (n < 2) {
|
|
1926
|
+
throw new TlsTrustError("tls/ct-bad-tree-size",
|
|
1927
|
+
"ct: largest-power-of-2-less-than requires n >= 2 (got " + n + ")");
|
|
1928
|
+
}
|
|
1929
|
+
var k = 1;
|
|
1930
|
+
while ((k << 1) < n) k = k << 1;
|
|
1931
|
+
return k;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// _ctVerifyInclusion — RFC 9162 §2.1.3 algorithm. Walks the audit path
|
|
1935
|
+
// from the leaf hash up to the tree's expected root using the supplied
|
|
1936
|
+
// audit path siblings. The leafIndex (0-based) selects which side at
|
|
1937
|
+
// each level the leaf sits on; the audit path provides the sibling
|
|
1938
|
+
// hash for that level.
|
|
1939
|
+
//
|
|
1940
|
+
// args:
|
|
1941
|
+
// leafHash: Buffer (32 bytes) — MTH({d}) of the leaf
|
|
1942
|
+
// leafIndex: integer 0 <= idx < treeSize
|
|
1943
|
+
// treeSize: integer >= 1
|
|
1944
|
+
// auditPath: Array of Buffer (each 32 bytes) — siblings bottom-up
|
|
1945
|
+
//
|
|
1946
|
+
// returns: Buffer (32 bytes) — computed root hash to compare
|
|
1947
|
+
// throws: TlsTrustError on shape errors
|
|
1948
|
+
function _ctVerifyInclusionPath(leafHash, leafIndex, treeSize, auditPath) {
|
|
1949
|
+
if (!Buffer.isBuffer(leafHash) || leafHash.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
1950
|
+
throw new TlsTrustError("tls/ct-bad-leaf-hash",
|
|
1951
|
+
"ct.verifyInclusion: leafHash must be a 32-byte Buffer");
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof leafIndex !== "number" || leafIndex < 0 || leafIndex >= treeSize ||
|
|
1954
|
+
Math.floor(leafIndex) !== leafIndex) {
|
|
1955
|
+
throw new TlsTrustError("tls/ct-bad-index",
|
|
1956
|
+
"ct.verifyInclusion: leafIndex must be an integer 0..treeSize-1");
|
|
1957
|
+
}
|
|
1958
|
+
if (typeof treeSize !== "number" || treeSize < 1 || Math.floor(treeSize) !== treeSize) {
|
|
1959
|
+
throw new TlsTrustError("tls/ct-bad-tree-size",
|
|
1960
|
+
"ct.verifyInclusion: treeSize must be a positive integer");
|
|
1961
|
+
}
|
|
1962
|
+
if (!Array.isArray(auditPath)) {
|
|
1963
|
+
throw new TlsTrustError("tls/ct-bad-audit-path",
|
|
1964
|
+
"ct.verifyInclusion: auditPath must be an array of 32-byte Buffers");
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// Per RFC 9162 §2.1.3 — climb the tree using the audit path. fn=leafIndex,
|
|
1968
|
+
// sn=treeSize-1 (last index in the tree at this level). Pop one
|
|
1969
|
+
// sibling from the audit path per level.
|
|
1970
|
+
var fn = leafIndex;
|
|
1971
|
+
var sn = treeSize - 1;
|
|
1972
|
+
var r = leafHash;
|
|
1973
|
+
var pathPos = 0;
|
|
1974
|
+
while (sn > 0) {
|
|
1975
|
+
if (pathPos >= auditPath.length) {
|
|
1976
|
+
throw new TlsTrustError("tls/ct-audit-path-short",
|
|
1977
|
+
"ct.verifyInclusion: audit path exhausted before tree root reached");
|
|
1978
|
+
}
|
|
1979
|
+
var sibling = auditPath[pathPos++];
|
|
1980
|
+
if (!Buffer.isBuffer(sibling) || sibling.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
1981
|
+
throw new TlsTrustError("tls/ct-bad-audit-path",
|
|
1982
|
+
"ct.verifyInclusion: audit path entry " + (pathPos - 1) + " is not a 32-byte Buffer");
|
|
1983
|
+
}
|
|
1984
|
+
if ((fn & 1) === 1 || fn === sn) {
|
|
1985
|
+
r = _ctInnerHashFinal(sibling, r);
|
|
1986
|
+
// Right-side leaf — climb until we hit a left-side ancestor.
|
|
1987
|
+
while ((fn & 1) === 0 && fn !== 0) { fn >>>= 1; sn >>>= 1; }
|
|
1988
|
+
} else {
|
|
1989
|
+
r = _ctInnerHashFinal(r, sibling);
|
|
1990
|
+
}
|
|
1991
|
+
fn >>>= 1;
|
|
1992
|
+
sn >>>= 1;
|
|
1993
|
+
}
|
|
1994
|
+
if (pathPos !== auditPath.length) {
|
|
1995
|
+
throw new TlsTrustError("tls/ct-audit-path-long",
|
|
1996
|
+
"ct.verifyInclusion: audit path has " + (auditPath.length - pathPos) +
|
|
1997
|
+
" trailing entries beyond the root");
|
|
1998
|
+
}
|
|
1999
|
+
return r;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// _ctVerifyConsistency — RFC 9162 §2.1.4 consistency proof verification.
|
|
2003
|
+
// Given a first STH (size m) and a second STH (size n, n >= m), the
|
|
2004
|
+
// consistency proof shows the second tree contains the first tree as a
|
|
2005
|
+
// prefix. Returns the computed roots (oldRoot, newRoot) so the caller
|
|
2006
|
+
// can compare against the operator-supplied STHs.
|
|
2007
|
+
function _ctVerifyConsistencyPath(m, n, consistencyProof, firstHash) {
|
|
2008
|
+
if (typeof m !== "number" || m < 1 || Math.floor(m) !== m) {
|
|
2009
|
+
throw new TlsTrustError("tls/ct-bad-first-size",
|
|
2010
|
+
"ct.verifyConsistency: m (first tree size) must be a positive integer");
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof n !== "number" || n < m || Math.floor(n) !== n) {
|
|
2013
|
+
throw new TlsTrustError("tls/ct-bad-second-size",
|
|
2014
|
+
"ct.verifyConsistency: n (second tree size) must be an integer >= m");
|
|
2015
|
+
}
|
|
2016
|
+
if (!Buffer.isBuffer(firstHash) || firstHash.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
2017
|
+
throw new TlsTrustError("tls/ct-bad-first-hash",
|
|
2018
|
+
"ct.verifyConsistency: firstHash must be a 32-byte Buffer");
|
|
2019
|
+
}
|
|
2020
|
+
if (!Array.isArray(consistencyProof)) {
|
|
2021
|
+
throw new TlsTrustError("tls/ct-bad-consistency-proof",
|
|
2022
|
+
"ct.verifyConsistency: consistencyProof must be an array of Buffers");
|
|
2023
|
+
}
|
|
2024
|
+
// RFC 9162 §2.1.4.2 — algorithm is the same as the inclusion-proof
|
|
2025
|
+
// walk, with the leaf-index seeded at the first-tree size minus 1 and
|
|
2026
|
+
// the special case for m being a complete subtree.
|
|
2027
|
+
var path = consistencyProof.slice();
|
|
2028
|
+
var node;
|
|
2029
|
+
var fn = m - 1;
|
|
2030
|
+
var sn = n - 1;
|
|
2031
|
+
// Walk past the right-side bits — the consistency proof omits the
|
|
2032
|
+
// path while the first tree is a complete subtree of the second.
|
|
2033
|
+
while ((fn & 1) === 1) { fn >>>= 1; sn >>>= 1; }
|
|
2034
|
+
|
|
2035
|
+
if (fn === 0) {
|
|
2036
|
+
// m was a complete subtree — its root is the firstHash itself.
|
|
2037
|
+
node = firstHash;
|
|
2038
|
+
} else {
|
|
2039
|
+
if (path.length === 0) {
|
|
2040
|
+
throw new TlsTrustError("tls/ct-consistency-empty",
|
|
2041
|
+
"ct.verifyConsistency: consistency proof empty but first tree is not a complete subtree");
|
|
2042
|
+
}
|
|
2043
|
+
node = path.shift();
|
|
2044
|
+
}
|
|
2045
|
+
while (sn > 0) {
|
|
2046
|
+
if (path.length === 0) {
|
|
2047
|
+
throw new TlsTrustError("tls/ct-consistency-short",
|
|
2048
|
+
"ct.verifyConsistency: consistency proof exhausted before second-tree root");
|
|
2049
|
+
}
|
|
2050
|
+
var sibling = path.shift();
|
|
2051
|
+
if (!Buffer.isBuffer(sibling) || sibling.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
2052
|
+
throw new TlsTrustError("tls/ct-bad-consistency-entry",
|
|
2053
|
+
"ct.verifyConsistency: consistency-proof entry is not a 32-byte Buffer");
|
|
2054
|
+
}
|
|
2055
|
+
if ((fn & 1) === 1 || fn === sn) {
|
|
2056
|
+
node = _ctInnerHashFinal(sibling, node);
|
|
2057
|
+
while ((fn & 1) === 0 && fn !== 0) { fn >>>= 1; sn >>>= 1; }
|
|
2058
|
+
} else {
|
|
2059
|
+
node = _ctInnerHashFinal(node, sibling);
|
|
2060
|
+
}
|
|
2061
|
+
fn >>>= 1;
|
|
2062
|
+
sn >>>= 1;
|
|
2063
|
+
}
|
|
2064
|
+
return node;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function _findSctOid(rawDer) {
|
|
2068
|
+
// Cheap presence check — used by inspect() before ASN.1 walking.
|
|
2069
|
+
// OID 1.3.6.1.4.1.11129.2.4.2 = 06 0A 2B 06 01 04 01 D6 79 02 04 02.
|
|
2070
|
+
var oidBytes = Buffer.from([
|
|
2071
|
+
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02,
|
|
2072
|
+
]);
|
|
2073
|
+
return rawDer.indexOf(oidBytes) !== -1;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
var ct = Object.freeze({
|
|
2077
|
+
// inspect — quick presence check for the SCT extension.
|
|
2078
|
+
inspect: function (rawDer) {
|
|
2079
|
+
if (!Buffer.isBuffer(rawDer)) {
|
|
2080
|
+
throw new TlsTrustError("tls/ct-bad-input",
|
|
2081
|
+
"ct.inspect: rawDer must be a Buffer (cert.raw)");
|
|
2082
|
+
}
|
|
2083
|
+
return {
|
|
2084
|
+
hasSctExtension: _findSctOid(rawDer),
|
|
2085
|
+
rawLength: rawDer.length,
|
|
2086
|
+
};
|
|
2087
|
+
},
|
|
2088
|
+
// parseScts — full ASN.1 walk + SCT-list parse. Returns
|
|
2089
|
+
// [{ version, logIdHex, timestamp, signature, ... }, ...] or [] when
|
|
2090
|
+
// no SCT extension is present.
|
|
2091
|
+
parseScts: function (rawDer) {
|
|
2092
|
+
if (!Buffer.isBuffer(rawDer)) {
|
|
2093
|
+
throw new TlsTrustError("tls/ct-bad-input",
|
|
2094
|
+
"ct.parseScts: rawDer must be a Buffer");
|
|
2095
|
+
}
|
|
2096
|
+
var ext = _extractSctExtensionFromCert(rawDer);
|
|
2097
|
+
if (!ext.sctListRaw) return [];
|
|
2098
|
+
return _parseSctList(ext.sctListRaw);
|
|
2099
|
+
},
|
|
2100
|
+
// verifyScts — full RFC 6962 verification. opts.logKeys maps
|
|
2101
|
+
// log_id (hex SHA-256 of the log's pubkey) → PEM public key.
|
|
2102
|
+
// Operators populate from the Chrome CT log list. Returns
|
|
2103
|
+
// { ok, verifiedCount, totalScts, scts: [{ logIdHex, verified, ... }] }.
|
|
2104
|
+
verifyScts: verifyScts,
|
|
2105
|
+
// Operator middleware predicate: refuse a peer cert lacking SCT
|
|
2106
|
+
// verification. Composes verifyScts under the hood.
|
|
2107
|
+
// verifyInclusion — RFC 9162 §4.5/§5.1 inclusion-proof verifier.
|
|
2108
|
+
// Composes with inspect() / parseScts() / verifyScts() for the
|
|
2109
|
+
// signature side: an SCT proves a log promised to include the cert,
|
|
2110
|
+
// and verifyInclusion proves that promise was kept (the leaf actually
|
|
2111
|
+
// sits in the published tree).
|
|
2112
|
+
//
|
|
2113
|
+
// opts: {
|
|
2114
|
+
// sct: { logIdHex, timestamp, signedEntryDer? } — from parseScts
|
|
2115
|
+
// leafCertificate: Buffer — leaf cert DER (the entry hashed at the leaf)
|
|
2116
|
+
// leafIndex: integer — position in the tree (from RFC 9162 §6.7 get-proof-by-hash)
|
|
2117
|
+
// auditPath: [Buffer] — the inclusion-proof siblings, bottom-up
|
|
2118
|
+
// sthFromLog: { treeSize, rootHash[, sha256RootHash] }
|
|
2119
|
+
// — operator fetched the signed tree head from the log
|
|
2120
|
+
// (RFC 9162 §6.4 get-sth) and supplies treeSize +
|
|
2121
|
+
// rootHash (32-byte Buffer or hex string)
|
|
2122
|
+
// consistency: { firstSize, firstRoot, proof } — optional
|
|
2123
|
+
// — when provided, also verifies that the supplied
|
|
2124
|
+
// STH is consistent with an earlier STH the operator
|
|
2125
|
+
// pinned (RFC 9162 §6.5 get-sth-consistency)
|
|
2126
|
+
// }
|
|
2127
|
+
//
|
|
2128
|
+
// returns: { valid: bool, reason?: string, computedRoot?: hex,
|
|
2129
|
+
// consistency?: { ok, computedSecondRoot? } }
|
|
2130
|
+
verifyInclusion: function (opts) {
|
|
2131
|
+
if (!opts || typeof opts !== "object") {
|
|
2132
|
+
return { valid: false, reason: "missing-opts" };
|
|
2133
|
+
}
|
|
2134
|
+
if (!opts.sct || typeof opts.sct !== "object") {
|
|
2135
|
+
return { valid: false, reason: "missing-sct" };
|
|
2136
|
+
}
|
|
2137
|
+
if (!Buffer.isBuffer(opts.leafCertificate)) {
|
|
2138
|
+
return { valid: false, reason: "missing-leaf-certificate" };
|
|
2139
|
+
}
|
|
2140
|
+
if (!opts.sthFromLog || typeof opts.sthFromLog !== "object") {
|
|
2141
|
+
return { valid: false, reason: "missing-sth" };
|
|
2142
|
+
}
|
|
2143
|
+
if (typeof opts.leafIndex !== "number" || !isFinite(opts.leafIndex) ||
|
|
2144
|
+
opts.leafIndex < 0 || Math.floor(opts.leafIndex) !== opts.leafIndex) {
|
|
2145
|
+
return { valid: false, reason: "bad-leaf-index" };
|
|
2146
|
+
}
|
|
2147
|
+
if (!Array.isArray(opts.auditPath)) {
|
|
2148
|
+
return { valid: false, reason: "bad-audit-path" };
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// Build the leaf bytes per RFC 9162 §4.6 — TimestampedEntry.
|
|
2152
|
+
// entry_type = x509_entry (0); signed_entry = strip-SCT-extension(cert).
|
|
2153
|
+
// Operators may pass a pre-built signedEntryDer when the SCT was
|
|
2154
|
+
// already extracted via parseScts() + the framework has the
|
|
2155
|
+
// pre-issuance cert; otherwise we strip the SCT extension here.
|
|
2156
|
+
var signedEntryDer = opts.sct.signedEntryDer;
|
|
2157
|
+
if (!Buffer.isBuffer(signedEntryDer)) {
|
|
2158
|
+
try { signedEntryDer = _stripSctExtensionFromCert(opts.leafCertificate); }
|
|
2159
|
+
catch (e) {
|
|
2160
|
+
return { valid: false, reason: "strip-failed",
|
|
2161
|
+
error: (e && e.message) || String(e) };
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// RFC 9162 §4.6 MerkleTreeLeaf — version (1) + leaf_type (0) +
|
|
2166
|
+
// timestamp (uint64) + entry_type (uint16) + signed_entry (variable-
|
|
2167
|
+
// length cert DER with 24-bit length prefix) + extensions (variable-
|
|
2168
|
+
// length, 16-bit length prefix, empty for x509_entry).
|
|
2169
|
+
var ts = opts.sct.timestamp;
|
|
2170
|
+
if (typeof ts !== "number" && typeof ts !== "bigint") {
|
|
2171
|
+
return { valid: false, reason: "bad-sct-timestamp" };
|
|
2172
|
+
}
|
|
2173
|
+
var tsBuf = Buffer.alloc(8); // allow:raw-byte-literal — TLS uint64 width
|
|
2174
|
+
var tsBig = typeof ts === "bigint" ? ts : BigInt(Math.floor(ts));
|
|
2175
|
+
tsBuf.writeBigUInt64BE(tsBig);
|
|
2176
|
+
var entryTypeBuf = Buffer.from([0x00, 0x00]);
|
|
2177
|
+
var lenBuf = Buffer.alloc(3); // allow:raw-byte-literal — TLS uint24 length prefix
|
|
2178
|
+
lenBuf.writeUIntBE(signedEntryDer.length, 0, 3);
|
|
2179
|
+
var extensionsBuf = Buffer.from([0x00, 0x00]); // allow:raw-byte-literal — empty extensions vector
|
|
2180
|
+
var leafBytes = Buffer.concat([
|
|
2181
|
+
Buffer.from([0x00]), // version v1
|
|
2182
|
+
Buffer.from([0x00]), // leaf_type timestamped_entry
|
|
2183
|
+
tsBuf,
|
|
2184
|
+
entryTypeBuf,
|
|
2185
|
+
lenBuf, signedEntryDer,
|
|
2186
|
+
extensionsBuf,
|
|
2187
|
+
]);
|
|
2188
|
+
|
|
2189
|
+
var leafHash = _ctLeafHash(leafBytes);
|
|
2190
|
+
var computedRoot;
|
|
2191
|
+
try {
|
|
2192
|
+
computedRoot = _ctVerifyInclusionPath(leafHash, opts.leafIndex,
|
|
2193
|
+
opts.sthFromLog.treeSize, opts.auditPath);
|
|
2194
|
+
} catch (e) {
|
|
2195
|
+
return { valid: false, reason: "inclusion-walk-failed",
|
|
2196
|
+
error: (e && e.message) || String(e) };
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// sthFromLog.rootHash may be a Buffer or hex string.
|
|
2200
|
+
var sthRoot = opts.sthFromLog.rootHash || opts.sthFromLog.sha256RootHash;
|
|
2201
|
+
if (typeof sthRoot === "string") {
|
|
2202
|
+
try { sthRoot = Buffer.from(sthRoot, "hex"); }
|
|
2203
|
+
catch (_e) { return { valid: false, reason: "bad-sth-root-encoding" }; }
|
|
2204
|
+
}
|
|
2205
|
+
if (!Buffer.isBuffer(sthRoot) || sthRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
2206
|
+
return { valid: false, reason: "bad-sth-root" };
|
|
2207
|
+
}
|
|
2208
|
+
if (!bCrypto.timingSafeEqual(computedRoot, sthRoot)) {
|
|
2209
|
+
return { valid: false, reason: "root-mismatch",
|
|
2210
|
+
computedRoot: computedRoot.toString("hex") };
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
// Optional consistency proof — RFC 9162 §2.1.4.
|
|
2214
|
+
var consistencyResult = null;
|
|
2215
|
+
if (opts.consistency && typeof opts.consistency === "object") {
|
|
2216
|
+
var firstRoot = opts.consistency.firstRoot;
|
|
2217
|
+
if (typeof firstRoot === "string") {
|
|
2218
|
+
try { firstRoot = Buffer.from(firstRoot, "hex"); }
|
|
2219
|
+
catch (_e) {
|
|
2220
|
+
return { valid: false, reason: "bad-consistency-first-root-encoding" };
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
try {
|
|
2224
|
+
var computedSecond = _ctVerifyConsistencyPath(
|
|
2225
|
+
opts.consistency.firstSize, opts.sthFromLog.treeSize,
|
|
2226
|
+
opts.consistency.proof || [], firstRoot);
|
|
2227
|
+
var ok = bCrypto.timingSafeEqual(computedSecond, sthRoot);
|
|
2228
|
+
consistencyResult = {
|
|
2229
|
+
ok: ok,
|
|
2230
|
+
computedSecondRoot: computedSecond.toString("hex"),
|
|
2231
|
+
};
|
|
2232
|
+
if (!ok) {
|
|
2233
|
+
return { valid: false, reason: "consistency-mismatch",
|
|
2234
|
+
computedRoot: computedRoot.toString("hex"),
|
|
2235
|
+
consistency: consistencyResult };
|
|
2236
|
+
}
|
|
2237
|
+
} catch (e) {
|
|
2238
|
+
return { valid: false, reason: "consistency-walk-failed",
|
|
2239
|
+
error: (e && e.message) || String(e) };
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
return {
|
|
2244
|
+
valid: true,
|
|
2245
|
+
computedRoot: computedRoot.toString("hex"),
|
|
2246
|
+
leafHash: leafHash.toString("hex"),
|
|
2247
|
+
consistency: consistencyResult,
|
|
2248
|
+
};
|
|
2249
|
+
},
|
|
2250
|
+
// verifyConsistency — standalone RFC 9162 §2.1.4 consistency-proof
|
|
2251
|
+
// verifier. Operators pinning historical tree-head fingerprints call
|
|
2252
|
+
// this whenever they fetch a fresh STH to confirm the log hasn't
|
|
2253
|
+
// forked. Returns { valid, computedRoot } / { valid:false, reason }.
|
|
2254
|
+
verifyConsistency: function (opts) {
|
|
2255
|
+
if (!opts || typeof opts !== "object") {
|
|
2256
|
+
return { valid: false, reason: "missing-opts" };
|
|
2257
|
+
}
|
|
2258
|
+
var firstRoot = opts.firstRoot;
|
|
2259
|
+
if (typeof firstRoot === "string") {
|
|
2260
|
+
try { firstRoot = Buffer.from(firstRoot, "hex"); }
|
|
2261
|
+
catch (_e) { return { valid: false, reason: "bad-first-root-encoding" }; }
|
|
2262
|
+
}
|
|
2263
|
+
var secondRoot = opts.secondRoot;
|
|
2264
|
+
if (typeof secondRoot === "string") {
|
|
2265
|
+
try { secondRoot = Buffer.from(secondRoot, "hex"); }
|
|
2266
|
+
catch (_e) { return { valid: false, reason: "bad-second-root-encoding" }; }
|
|
2267
|
+
}
|
|
2268
|
+
if (!Buffer.isBuffer(firstRoot) || firstRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
2269
|
+
return { valid: false, reason: "bad-first-root" };
|
|
2270
|
+
}
|
|
2271
|
+
if (!Buffer.isBuffer(secondRoot) || secondRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
|
|
2272
|
+
return { valid: false, reason: "bad-second-root" };
|
|
2273
|
+
}
|
|
2274
|
+
var computed;
|
|
2275
|
+
try {
|
|
2276
|
+
computed = _ctVerifyConsistencyPath(opts.firstSize, opts.secondSize,
|
|
2277
|
+
opts.proof || [], firstRoot);
|
|
2278
|
+
} catch (e) {
|
|
2279
|
+
return { valid: false, reason: "consistency-walk-failed",
|
|
2280
|
+
error: (e && e.message) || String(e) };
|
|
2281
|
+
}
|
|
2282
|
+
if (!bCrypto.timingSafeEqual(computed, secondRoot)) {
|
|
2283
|
+
return { valid: false, reason: "root-mismatch",
|
|
2284
|
+
computedRoot: computed.toString("hex") };
|
|
2285
|
+
}
|
|
2286
|
+
return { valid: true, computedRoot: computed.toString("hex") };
|
|
2287
|
+
},
|
|
2288
|
+
requireScts: function (opts) {
|
|
2289
|
+
opts = opts || {};
|
|
2290
|
+
return function (peerCert) {
|
|
2291
|
+
if (!peerCert || !peerCert.raw) {
|
|
2292
|
+
return new TlsTrustError("tls/ct-no-cert",
|
|
2293
|
+
"requireScts: peer cert.raw missing");
|
|
2294
|
+
}
|
|
2295
|
+
var rv = verifyScts(peerCert.raw, opts);
|
|
2296
|
+
if (!rv.ok) {
|
|
2297
|
+
// Map verifier reason → operator-facing error code so call
|
|
2298
|
+
// sites can distinguish "no SCT extension at all" from
|
|
2299
|
+
// "extension present but verification short of minScts".
|
|
2300
|
+
var code = "tls/ct-not-verified";
|
|
2301
|
+
if (rv.reason === "no-sct-extension") code = "tls/ct-no-sct-extension";
|
|
2302
|
+
else if (rv.reason === "insufficient-verified") code = "tls/ct-insufficient-verified";
|
|
2303
|
+
return new TlsTrustError(code,
|
|
2304
|
+
"SCT verification failed: " + (rv.reason || "unknown") +
|
|
2305
|
+
" (" + rv.verifiedCount + "/" + rv.totalScts + " verified)");
|
|
2306
|
+
}
|
|
2307
|
+
return null;
|
|
2308
|
+
};
|
|
2309
|
+
},
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
// ---- ECH (Encrypted Client Hello) — RFC 9460 SVCB ech= SvcParam +
|
|
2313
|
+
// draft-ietf-tls-esni-22 §4 ECHConfigList -----------------------
|
|
2314
|
+
//
|
|
2315
|
+
// ECH is a TLS 1.3 extension that encrypts the Client Hello Inner
|
|
2316
|
+
// (SNI, ALPN, etc.) under a public key the server publishes via DNS
|
|
2317
|
+
// SVCB/HTTPS records. A passive observer sees only the public_name
|
|
2318
|
+
// SNI in the outer hello — the real virtual host stays confidential.
|
|
2319
|
+
//
|
|
2320
|
+
// Wire format reminder (uint16 lengths are big-endian throughout):
|
|
2321
|
+
//
|
|
2322
|
+
// ECHConfigList = uint16 total_length || ECHConfig[]
|
|
2323
|
+
// ECHConfig = uint16 version || uint16 length || contents
|
|
2324
|
+
// contents (v=0xfe0d) =
|
|
2325
|
+
// HpkeKeyConfig key_config
|
|
2326
|
+
// uint8 maximum_name_length
|
|
2327
|
+
// opaque<1..255> public_name (with uint8 length prefix)
|
|
2328
|
+
// Extension extensions<0..2^16-1> (uint16 length prefix +
|
|
2329
|
+
// list of (uint16 ext_type,
|
|
2330
|
+
// opaque<0..2^16-1> ext_data))
|
|
2331
|
+
// HpkeKeyConfig =
|
|
2332
|
+
// uint8 config_id
|
|
2333
|
+
// uint16 kem_id
|
|
2334
|
+
// opaque public_key<1..2^16-1> (uint16 length prefix)
|
|
2335
|
+
// HpkeSymmetricCipherSuite cipher_suites<4..2^16-1>
|
|
2336
|
+
// (uint16 length prefix; entries
|
|
2337
|
+
// each (uint16 kdf_id, uint16
|
|
2338
|
+
// aead_id) — 4 bytes apiece)
|
|
2339
|
+
|
|
2340
|
+
var ECH_CONFIG_VERSION_DRAFT_22 = 0xfe0d; // allow:raw-byte-literal — draft-ietf-tls-esni-22 ECH version codepoint
|
|
2341
|
+
|
|
2342
|
+
function _echReadU8(buf, off) {
|
|
2343
|
+
if (off + 1 > buf.length) {
|
|
2344
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2345
|
+
"ECHConfigList: truncated reading uint8 at offset " + off);
|
|
2346
|
+
}
|
|
2347
|
+
return buf[off];
|
|
2348
|
+
}
|
|
2349
|
+
function _echReadU16(buf, off) {
|
|
2350
|
+
if (off + 2 > buf.length) { // allow:raw-byte-literal — uint16 width
|
|
2351
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2352
|
+
"ECHConfigList: truncated reading uint16 at offset " + off);
|
|
2353
|
+
}
|
|
2354
|
+
return buf.readUInt16BE(off);
|
|
2355
|
+
}
|
|
2356
|
+
function _echReadVarOpaqueU16(buf, off) {
|
|
2357
|
+
var len = _echReadU16(buf, off);
|
|
2358
|
+
off += 2; // allow:raw-byte-literal — uint16 length-prefix width
|
|
2359
|
+
if (off + len > buf.length) {
|
|
2360
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2361
|
+
"ECHConfigList: opaque vector overflows buffer (declared " + len +
|
|
2362
|
+
" bytes at offset " + (off - 2) + ", " + (buf.length - off) + " available)");
|
|
2363
|
+
}
|
|
2364
|
+
return { value: buf.slice(off, off + len), nextOff: off + len };
|
|
2365
|
+
}
|
|
2366
|
+
function _echReadVarOpaqueU8(buf, off) {
|
|
2367
|
+
var len = _echReadU8(buf, off);
|
|
2368
|
+
off += 1;
|
|
2369
|
+
if (off + len > buf.length) {
|
|
2370
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2371
|
+
"ECHConfigList: u8-prefixed opaque overflows buffer");
|
|
2372
|
+
}
|
|
2373
|
+
return { value: buf.slice(off, off + len), nextOff: off + len };
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
/**
|
|
2377
|
+
* @primitive b.network.tls.parseEchConfigList
|
|
2378
|
+
* @signature b.network.tls.parseEchConfigList(raw)
|
|
2379
|
+
* @since 0.8.53
|
|
2380
|
+
* @status stable
|
|
2381
|
+
* @related b.network.tls.connectWithEch, b.network.dns.queryHttps
|
|
2382
|
+
*
|
|
2383
|
+
* Parse a draft-ietf-tls-esni-22 ECHConfigList byte string (the value
|
|
2384
|
+
* of the `ech=` SvcParam in an SVCB or HTTPS DNS record per RFC 9460
|
|
2385
|
+
* paragraph 7.4.2). Accepts a `Buffer` or a strict-base64 string. Returns
|
|
2386
|
+
* `{ rawLength, configs: [{ version, length, keyConfig, ... }] }`.
|
|
2387
|
+
*
|
|
2388
|
+
* For each ECHConfig at the published draft-22 version (`0xfe0d`) the
|
|
2389
|
+
* decoded `keyConfig` carries `configId`, `kemId`, `publicKey`
|
|
2390
|
+
* (Buffer), and `cipherSuites` (each `{ kdfId, aeadId }`); the entry
|
|
2391
|
+
* also exposes `maximumNameLength`, `publicName`, and `extensions`.
|
|
2392
|
+
* Unknown future ECH versions surface their raw `body` Buffer so the
|
|
2393
|
+
* caller can forward them to a Node build that supports them.
|
|
2394
|
+
*
|
|
2395
|
+
* Throws `NetworkTlsError("tls/ech-config-malformed")` on any framing
|
|
2396
|
+
* violation (truncated length prefix, vector overflow, bad
|
|
2397
|
+
* cipher_suites stride, etc.).
|
|
2398
|
+
*
|
|
2399
|
+
* @example
|
|
2400
|
+
* var b = require("@blamejs/core");
|
|
2401
|
+
* var rrs = await b.network.dns.queryHttps("example.com");
|
|
2402
|
+
* var rec = rrs.find(function (r) { return r.params && r.params.ech; });
|
|
2403
|
+
* var parsed = b.network.tls.parseEchConfigList(rec.params.ech);
|
|
2404
|
+
* // parsed.configs[0].keyConfig.kemId === 0x0020 (X25519)
|
|
2405
|
+
*/
|
|
2406
|
+
function parseEchConfigList(raw) {
|
|
2407
|
+
if (typeof raw === "string") {
|
|
2408
|
+
// Operators sometimes hold the SvcParam as a base64 string; accept
|
|
2409
|
+
// both. Reject anything that doesn't round-trip cleanly through
|
|
2410
|
+
// strict-base64 — Node's Buffer.from(b64, "base64") is lenient
|
|
2411
|
+
// (silently ignores stray bytes), so we re-encode and compare.
|
|
2412
|
+
var stripped = raw.replace(/\s+/g, "");
|
|
2413
|
+
var decoded = Buffer.from(stripped, "base64");
|
|
2414
|
+
if (decoded.length === 0 || decoded.toString("base64") !== stripped) {
|
|
2415
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2416
|
+
"parseEchConfigList: input string is not strict base64");
|
|
2417
|
+
}
|
|
2418
|
+
raw = decoded;
|
|
2419
|
+
}
|
|
2420
|
+
if (!Buffer.isBuffer(raw) || raw.length === 0) {
|
|
2421
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2422
|
+
"parseEchConfigList: input must be a non-empty Buffer or base64 string");
|
|
2423
|
+
}
|
|
2424
|
+
if (raw.length < 2) { // allow:raw-byte-literal — uint16 outer length prefix
|
|
2425
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2426
|
+
"ECHConfigList: too short for outer length prefix");
|
|
2427
|
+
}
|
|
2428
|
+
var totalLen = raw.readUInt16BE(0);
|
|
2429
|
+
if (2 + totalLen !== raw.length) { // allow:raw-byte-literal — uint16 prefix width
|
|
2430
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2431
|
+
"ECHConfigList: outer length " + totalLen + " does not match buffer " +
|
|
2432
|
+
"tail length " + (raw.length - 2));
|
|
2433
|
+
}
|
|
2434
|
+
var off = 2; // allow:raw-byte-literal — uint16 prefix width
|
|
2435
|
+
var configs = [];
|
|
2436
|
+
while (off < raw.length) {
|
|
2437
|
+
if (off + 4 > raw.length) { // allow:raw-byte-literal — uint16 ver + uint16 len
|
|
2438
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2439
|
+
"ECHConfig: truncated header at offset " + off);
|
|
2440
|
+
}
|
|
2441
|
+
var version = raw.readUInt16BE(off);
|
|
2442
|
+
var length = raw.readUInt16BE(off + 2);
|
|
2443
|
+
var bodyOff = off + 4;
|
|
2444
|
+
var bodyEnd = bodyOff + length;
|
|
2445
|
+
if (bodyEnd > raw.length) {
|
|
2446
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2447
|
+
"ECHConfig: declared length " + length + " overflows ECHConfigList");
|
|
2448
|
+
}
|
|
2449
|
+
var entry = { version: version, length: length };
|
|
2450
|
+
if (version === ECH_CONFIG_VERSION_DRAFT_22) {
|
|
2451
|
+
var p = bodyOff;
|
|
2452
|
+
// HpkeKeyConfig
|
|
2453
|
+
var configId = _echReadU8(raw, p); p += 1;
|
|
2454
|
+
var kemId = _echReadU16(raw, p); p += 2; // allow:raw-byte-literal — uint16 KEM id width
|
|
2455
|
+
var pkOpaque = _echReadVarOpaqueU16(raw, p); p = pkOpaque.nextOff;
|
|
2456
|
+
var suitesLen = _echReadU16(raw, p); p += 2; // allow:raw-byte-literal — uint16 length prefix width
|
|
2457
|
+
if (p + suitesLen > bodyEnd) {
|
|
2458
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2459
|
+
"ECHConfig: cipher_suites vector overflows config body");
|
|
2460
|
+
}
|
|
2461
|
+
if (suitesLen % 4 !== 0 || suitesLen < 4) { // allow:raw-byte-literal — kdf+aead = 4 bytes per suite
|
|
2462
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2463
|
+
"ECHConfig: cipher_suites length must be a positive multiple of 4");
|
|
2464
|
+
}
|
|
2465
|
+
var suites = [];
|
|
2466
|
+
for (var sp = p; sp < p + suitesLen; sp += 4) { // allow:raw-byte-literal — 4-byte cipher suite stride
|
|
2467
|
+
suites.push({
|
|
2468
|
+
kdfId: raw.readUInt16BE(sp),
|
|
2469
|
+
aeadId: raw.readUInt16BE(sp + 2),
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
p += suitesLen;
|
|
2473
|
+
// remainder of contents
|
|
2474
|
+
var maxNameLen = _echReadU8(raw, p); p += 1;
|
|
2475
|
+
var publicName = _echReadVarOpaqueU8(raw, p); p = publicName.nextOff;
|
|
2476
|
+
var extLen = _echReadU16(raw, p); p += 2; // allow:raw-byte-literal — uint16 length prefix width
|
|
2477
|
+
if (p + extLen !== bodyEnd) {
|
|
2478
|
+
throw new NetworkTlsError("tls/ech-config-malformed",
|
|
2479
|
+
"ECHConfig: extensions vector does not consume remaining body " +
|
|
2480
|
+
"(extLen=" + extLen + ", remaining=" + (bodyEnd - p) + ")");
|
|
2481
|
+
}
|
|
2482
|
+
var extensions = [];
|
|
2483
|
+
var extEnd = p + extLen;
|
|
2484
|
+
while (p < extEnd) {
|
|
2485
|
+
var extType = _echReadU16(raw, p); p += 2; // allow:raw-byte-literal — uint16 ext type
|
|
2486
|
+
var extData = _echReadVarOpaqueU16(raw, p); p = extData.nextOff;
|
|
2487
|
+
extensions.push({ type: extType, data: extData.value });
|
|
2488
|
+
}
|
|
2489
|
+
entry.keyConfig = {
|
|
2490
|
+
configId: configId,
|
|
2491
|
+
kemId: kemId,
|
|
2492
|
+
publicKey: pkOpaque.value,
|
|
2493
|
+
cipherSuites: suites,
|
|
2494
|
+
};
|
|
2495
|
+
entry.maximumNameLength = maxNameLen;
|
|
2496
|
+
entry.publicName = publicName.value.toString("ascii");
|
|
2497
|
+
entry.extensions = extensions;
|
|
2498
|
+
} else {
|
|
2499
|
+
// Unknown future version — surface raw bytes so the caller can
|
|
2500
|
+
// forward them to a Node build that does support that version.
|
|
2501
|
+
entry.body = Buffer.from(raw.slice(bodyOff, bodyEnd));
|
|
2502
|
+
}
|
|
2503
|
+
configs.push(entry);
|
|
2504
|
+
off = bodyEnd;
|
|
2505
|
+
}
|
|
2506
|
+
return { rawLength: raw.length, configs: configs };
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// Feature-detect: probe whether tls.connect accepts the `ech` option.
|
|
2510
|
+
// Cached so repeated connect calls don't re-test on every connection.
|
|
2511
|
+
// Strategy: tls.connect throws synchronously on a port=0 socket attempt
|
|
2512
|
+
// when the option shape is rejected at the C++ layer with
|
|
2513
|
+
// ERR_INVALID_ARG_TYPE / ERR_TLS_INVALID_OPTION; if it makes it past
|
|
2514
|
+
// option-validation we destroy the half-built socket. We never actually
|
|
2515
|
+
// open a socket — the probe runs entirely in option-parsing.
|
|
2516
|
+
var _echFeatureProbe = null;
|
|
2517
|
+
function _isEchSupported() {
|
|
2518
|
+
if (_echFeatureProbe !== null) return _echFeatureProbe;
|
|
2519
|
+
// The cleanest probe is to read tls.connect.toString() — but Node
|
|
2520
|
+
// hides option parsing in C++. Instead we attempt to construct the
|
|
2521
|
+
// options object via tls.checkServerIdentity-adjacent surface: call
|
|
2522
|
+
// tls.connect with a sentinel `ech: Buffer.alloc(0)` and an
|
|
2523
|
+
// immediately-destroyed socket. Any non-throwing path = supported.
|
|
2524
|
+
var supported = false;
|
|
2525
|
+
try {
|
|
2526
|
+
var probe = nodeTls.connect({
|
|
2527
|
+
host: "127.0.0.1",
|
|
2528
|
+
port: 1,
|
|
2529
|
+
ech: Buffer.alloc(0),
|
|
2530
|
+
lookup: function (_h, _o, cb) { cb(new Error("probe-abort")); },
|
|
2531
|
+
});
|
|
2532
|
+
supported = true;
|
|
2533
|
+
try { probe.destroy(); } catch (_e) { /* probe socket */ }
|
|
2534
|
+
} catch (e) {
|
|
2535
|
+
var msg = (e && (e.code || e.message)) || "";
|
|
2536
|
+
// ERR_INVALID_ARG_TYPE or ERR_TLS_* on `ech` = unsupported.
|
|
2537
|
+
if (/ech/i.test(msg) || /unknown option/i.test(msg)) supported = false;
|
|
2538
|
+
else supported = true; // unrelated throw (e.g. lookup): option accepted
|
|
2539
|
+
}
|
|
2540
|
+
_echFeatureProbe = supported;
|
|
2541
|
+
return supported;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
/**
|
|
2545
|
+
* @primitive b.network.tls.connectWithEch
|
|
2546
|
+
* @signature b.network.tls.connectWithEch(opts)
|
|
2547
|
+
* @since 0.8.53
|
|
2548
|
+
* @status stable
|
|
2549
|
+
* @related b.network.tls.parseEchConfigList, b.network.dns.queryHttps,
|
|
2550
|
+
* b.network.tls.checkServerIdentity9525
|
|
2551
|
+
*
|
|
2552
|
+
* Open a TLS-1.3 outbound connection with Encrypted Client Hello (ECH,
|
|
2553
|
+
* draft-ietf-tls-esni-22) when the destination publishes an `ech=`
|
|
2554
|
+
* SvcParam via SVCB/HTTPS records (RFC 9460 paragraph 2.4 / paragraph 9). The flow:
|
|
2555
|
+
*
|
|
2556
|
+
* 1. `b.network.dns.queryHttps(host)` to discover ECH config.
|
|
2557
|
+
* 2. If any record carries `ech=`, the parsed ECHConfigList is
|
|
2558
|
+
* attached to `tls.connect({ ech })` so the outer ClientHello
|
|
2559
|
+
* uses the published `public_name` SNI and the inner ClientHello
|
|
2560
|
+
* (real SNI, ALPN, etc.) is HPKE-encrypted under the published
|
|
2561
|
+
* public key.
|
|
2562
|
+
* 3. If no record carries `ech=`, or DNS fails, the function falls
|
|
2563
|
+
* back to a normal TLS connect (still TLSv1.3-floor + framework
|
|
2564
|
+
* PQC group preference). Operators get an `observability.event`
|
|
2565
|
+
* so the degradation is visible.
|
|
2566
|
+
* 4. If the running Node build does not support the `ech` connect
|
|
2567
|
+
* option, the function emits a one-shot warn and connects
|
|
2568
|
+
* without ECH — never throws on missing Node-side support.
|
|
2569
|
+
*
|
|
2570
|
+
* Returns the connected `tls.TLSSocket` once `secureConnect` fires.
|
|
2571
|
+
* `b.httpClient` will compose this in a follow-up release; this
|
|
2572
|
+
* primitive is the operator escape hatch for raw outbound TLS over
|
|
2573
|
+
* ECH (custom protocol clients, mTLS testing, ECH validation tools).
|
|
2574
|
+
*
|
|
2575
|
+
* @opts
|
|
2576
|
+
* {
|
|
2577
|
+
* host: string,
|
|
2578
|
+
* port: number,
|
|
2579
|
+
* alpn: string[],
|
|
2580
|
+
* ipFamily: 4 | 6,
|
|
2581
|
+
* timeoutMs: number,
|
|
2582
|
+
* servername: string,
|
|
2583
|
+
* ca: string|Buffer|Array,
|
|
2584
|
+
* checkServerIdentity: function,
|
|
2585
|
+
* echOverride: Buffer|string,
|
|
2586
|
+
* rejectUnauthorized: boolean,
|
|
2587
|
+
* }
|
|
2588
|
+
*
|
|
2589
|
+
* @example
|
|
2590
|
+
* var b = require("@blamejs/core");
|
|
2591
|
+
* var sock = await b.network.tls.connectWithEch({
|
|
2592
|
+
* host: "ech-target.example.com",
|
|
2593
|
+
* alpn: ["h2", "http/1.1"],
|
|
2594
|
+
* });
|
|
2595
|
+
* sock.write("GET / HTTP/1.1\r\nHost: ech-target.example.com\r\n\r\n");
|
|
2596
|
+
*/
|
|
2597
|
+
function connectWithEch(opts) {
|
|
2598
|
+
opts = opts || {};
|
|
2599
|
+
if (typeof opts !== "object" || Array.isArray(opts)) {
|
|
2600
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2601
|
+
"connectWithEch: opts must be a plain object");
|
|
2602
|
+
}
|
|
2603
|
+
validateOpts(opts,
|
|
2604
|
+
["host", "port", "alpn", "ipFamily", "timeoutMs", "servername", "ca",
|
|
2605
|
+
"checkServerIdentity", "echOverride", "rejectUnauthorized"],
|
|
2606
|
+
"network.tls.connectWithEch");
|
|
2607
|
+
validateOpts.requireNonEmptyString(opts.host, "connectWithEch: host",
|
|
2608
|
+
NetworkTlsError, "tls/ech-bad-opts");
|
|
2609
|
+
var port = opts.port === undefined ? 443 : opts.port; // allow:raw-byte-literal — HTTPS default port
|
|
2610
|
+
if (typeof port !== "number" || !isFinite(port) ||
|
|
2611
|
+
port <= 0 || port > 65535 || Math.floor(port) !== port) { // allow:raw-byte-literal — TCP port range
|
|
2612
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2613
|
+
"connectWithEch: port must be an integer in 1..65535");
|
|
2614
|
+
}
|
|
2615
|
+
if (opts.alpn !== undefined && !Array.isArray(opts.alpn)) {
|
|
2616
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2617
|
+
"connectWithEch: alpn must be an array of strings");
|
|
2618
|
+
}
|
|
2619
|
+
if (opts.ipFamily !== undefined && opts.ipFamily !== 4 && opts.ipFamily !== 6) {
|
|
2620
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2621
|
+
"connectWithEch: ipFamily must be 4 | 6 | undefined");
|
|
2622
|
+
}
|
|
2623
|
+
var timeoutMs = opts.timeoutMs === undefined
|
|
2624
|
+
? C.TIME.seconds(30) : opts.timeoutMs;
|
|
2625
|
+
if (typeof timeoutMs !== "number" || !isFinite(timeoutMs) || timeoutMs < 0) {
|
|
2626
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2627
|
+
"connectWithEch: timeoutMs must be a non-negative finite number");
|
|
2628
|
+
}
|
|
2629
|
+
if (opts.echOverride !== undefined &&
|
|
2630
|
+
!Buffer.isBuffer(opts.echOverride) &&
|
|
2631
|
+
typeof opts.echOverride !== "string") {
|
|
2632
|
+
throw new NetworkTlsError("tls/ech-bad-opts",
|
|
2633
|
+
"connectWithEch: echOverride must be a Buffer or base64 string");
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
return new Promise(function (resolve, reject) {
|
|
2637
|
+
function _doConnect(echConfigBuf, sourceLabel) {
|
|
2638
|
+
var nodeSupportsEch = _isEchSupported();
|
|
2639
|
+
var connectOpts = {
|
|
2640
|
+
host: opts.host,
|
|
2641
|
+
port: port,
|
|
2642
|
+
servername: opts.servername || opts.host,
|
|
2643
|
+
minVersion: "TLSv1.3",
|
|
2644
|
+
};
|
|
2645
|
+
if (Array.isArray(opts.alpn)) connectOpts.ALPNProtocols = opts.alpn.slice();
|
|
2646
|
+
if (opts.ipFamily !== undefined) connectOpts.family = opts.ipFamily;
|
|
2647
|
+
if (opts.ca !== undefined) connectOpts.ca = _normalizeCaInput(opts.ca);
|
|
2648
|
+
if (typeof opts.checkServerIdentity === "function") {
|
|
2649
|
+
connectOpts.checkServerIdentity = opts.checkServerIdentity;
|
|
2650
|
+
}
|
|
2651
|
+
if (opts.rejectUnauthorized === false) {
|
|
2652
|
+
connectOpts.rejectUnauthorized = false;
|
|
2653
|
+
}
|
|
2654
|
+
var echAttached = false;
|
|
2655
|
+
if (echConfigBuf && nodeSupportsEch) {
|
|
2656
|
+
connectOpts.ech = echConfigBuf;
|
|
2657
|
+
echAttached = true;
|
|
2658
|
+
} else if (echConfigBuf && !nodeSupportsEch) {
|
|
2659
|
+
// ECHConfig present but Node build can't honor it — degrade
|
|
2660
|
+
// gracefully with a one-shot warn so operators know they're
|
|
2661
|
+
// sending an outer-only ClientHello.
|
|
2662
|
+
try {
|
|
2663
|
+
observability().emit("network.tls.ech.unsupported", {
|
|
2664
|
+
host: opts.host, source: sourceLabel,
|
|
2665
|
+
});
|
|
2666
|
+
} catch (_e) { /* drop-silent */ }
|
|
2667
|
+
try {
|
|
2668
|
+
audit().safeEmit({
|
|
2669
|
+
action: "network.tls.ech.unsupported",
|
|
2670
|
+
outcome: "success", // Node lacks `ech` opt — degraded to non-ECH
|
|
2671
|
+
metadata: { host: opts.host, source: sourceLabel },
|
|
2672
|
+
});
|
|
2673
|
+
} catch (_e) { /* drop-silent */ }
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
var sock;
|
|
2677
|
+
try { sock = nodeTls.connect(connectOpts); }
|
|
2678
|
+
catch (e) {
|
|
2679
|
+
reject(new NetworkTlsError("tls/ech-connect-failed",
|
|
2680
|
+
"connectWithEch: tls.connect threw: " + ((e && e.message) || String(e))));
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
var settled = false;
|
|
2684
|
+
var to = null;
|
|
2685
|
+
if (timeoutMs > 0) {
|
|
2686
|
+
to = setTimeout(function () {
|
|
2687
|
+
if (settled) return;
|
|
2688
|
+
settled = true;
|
|
2689
|
+
try { sock.destroy(); } catch (_e) { /* destroy best-effort */ }
|
|
2690
|
+
reject(new NetworkTlsError("tls/ech-timeout",
|
|
2691
|
+
"connectWithEch: handshake timed out after " + timeoutMs + "ms"));
|
|
2692
|
+
}, timeoutMs);
|
|
2693
|
+
if (typeof to.unref === "function") to.unref();
|
|
2694
|
+
}
|
|
2695
|
+
sock.once("secureConnect", function () {
|
|
2696
|
+
if (settled) return;
|
|
2697
|
+
settled = true;
|
|
2698
|
+
if (to) clearTimeout(to);
|
|
2699
|
+
try {
|
|
2700
|
+
observability().emit("network.tls.ech.connected", {
|
|
2701
|
+
host: opts.host, echAttached: echAttached, source: sourceLabel,
|
|
2702
|
+
});
|
|
2703
|
+
} catch (_e) { /* drop-silent */ }
|
|
2704
|
+
resolve(sock);
|
|
2705
|
+
});
|
|
2706
|
+
sock.once("error", function (e) {
|
|
2707
|
+
if (settled) return;
|
|
2708
|
+
settled = true;
|
|
2709
|
+
if (to) clearTimeout(to);
|
|
2710
|
+
reject(e);
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
if (Buffer.isBuffer(opts.echOverride) || typeof opts.echOverride === "string") {
|
|
2715
|
+
// Operator-provided ECHConfigList — skip the SVCB lookup, validate
|
|
2716
|
+
// shape, then connect.
|
|
2717
|
+
var override;
|
|
2718
|
+
try {
|
|
2719
|
+
var bufOverride = Buffer.isBuffer(opts.echOverride)
|
|
2720
|
+
? opts.echOverride
|
|
2721
|
+
: Buffer.from(opts.echOverride, "base64");
|
|
2722
|
+
parseEchConfigList(bufOverride); // validate-only
|
|
2723
|
+
override = bufOverride;
|
|
2724
|
+
} catch (e) {
|
|
2725
|
+
reject(e);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
_doConnect(override, "override");
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// Default: SVCB/HTTPS lookup. Per RFC 9460 §2.4 the prefix `_https.`
|
|
2733
|
+
// is the SVCB owner-name for an HTTPS origin; modern Node honors a
|
|
2734
|
+
// bare HTTPS QTYPE on the apex name though, which is what
|
|
2735
|
+
// queryHttps does. We use queryHttps directly.
|
|
2736
|
+
var dnsMod;
|
|
2737
|
+
try { dnsMod = networkDns(); }
|
|
2738
|
+
catch (e) {
|
|
2739
|
+
reject(new NetworkTlsError("tls/ech-dns-unavailable",
|
|
2740
|
+
"connectWithEch: network-dns module unavailable: " +
|
|
2741
|
+
((e && e.message) || String(e))));
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
dnsMod.queryHttps(opts.host).then(function (records) {
|
|
2745
|
+
var echBuf = null;
|
|
2746
|
+
for (var i = 0; i < records.length; i += 1) {
|
|
2747
|
+
var rec = records[i];
|
|
2748
|
+
if (rec && rec.params && Buffer.isBuffer(rec.params.ech) &&
|
|
2749
|
+
rec.params.ech.length > 0) {
|
|
2750
|
+
echBuf = rec.params.ech;
|
|
2751
|
+
break;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
_doConnect(echBuf, echBuf ? "svcb" : "no-ech-record");
|
|
2755
|
+
}).catch(function (e) {
|
|
2756
|
+
// DNS failure is not fatal — fall back to non-ECH connect so the
|
|
2757
|
+
// operator still gets a working TLS session. Emit obs so the
|
|
2758
|
+
// operator sees the degradation.
|
|
2759
|
+
try {
|
|
2760
|
+
observability().emit("network.tls.ech.dns_failed", {
|
|
2761
|
+
host: opts.host, error: (e && e.message) || String(e),
|
|
2762
|
+
});
|
|
2763
|
+
} catch (_e) { /* drop-silent */ }
|
|
2764
|
+
_doConnect(null, "dns-failed");
|
|
2765
|
+
});
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// ---- RFC 9525 strict server identity verification ----------------
|
|
2770
|
+
//
|
|
2771
|
+
// RFC 9525 §6 — PKIX name validation:
|
|
2772
|
+
// §6.1 The certificate's subjectAltName extension is the
|
|
2773
|
+
// authoritative source of identifiers. CN-fallback is
|
|
2774
|
+
// forbidden when SAN is present. RFC 9525 §6.4.4 explicitly
|
|
2775
|
+
// deprecates CN matching outright; legacy CN-only certs
|
|
2776
|
+
// (no SAN) are refused under strict mode.
|
|
2777
|
+
// §6.4.3 Wildcard `*.example.com` matches `foo.example.com` (one
|
|
2778
|
+
// left-most label) but NOT `foo.bar.example.com` (deeper
|
|
2779
|
+
// subdomain) and NOT `example.com` (the wildcard owner
|
|
2780
|
+
// itself). Wildcards in the middle (`foo.*.example.com`) or
|
|
2781
|
+
// partial wildcards (`f*o.example.com`) are refused.
|
|
2782
|
+
// §6.5 IP addresses match against iPAddress entries in SAN, never
|
|
2783
|
+
// dNSName entries. Textual IP literals do not get DNS-style
|
|
2784
|
+
// wildcard treatment.
|
|
2785
|
+
//
|
|
2786
|
+
// Operators pass `b.network.tls.checkServerIdentity9525` to
|
|
2787
|
+
// `tls.connect({ checkServerIdentity })` to swap Node's permissive
|
|
2788
|
+
// default for the strict policy.
|
|
2789
|
+
|
|
2790
|
+
function _normalizeAsciiHost(host) {
|
|
2791
|
+
// RFC 9525 §6.4 — comparisons are ASCII case-insensitive on the
|
|
2792
|
+
// A-label form. We don't perform IDNA conversion (operators that
|
|
2793
|
+
// need U-label hosts must pre-convert via punycode); raw non-ASCII
|
|
2794
|
+
// input is refused so we never silently match across encodings.
|
|
2795
|
+
if (typeof host !== "string" || host.length === 0) return null;
|
|
2796
|
+
for (var i = 0; i < host.length; i += 1) {
|
|
2797
|
+
var cc = host.charCodeAt(i);
|
|
2798
|
+
if (cc > 0x7f) return null; // allow:raw-byte-literal — ASCII upper bound codepoint
|
|
2799
|
+
}
|
|
2800
|
+
// Strip a trailing dot (FQDN absolute form) for matching.
|
|
2801
|
+
var h = host.toLowerCase();
|
|
2802
|
+
if (h.length > 1 && h.charAt(h.length - 1) === ".") h = h.slice(0, -1);
|
|
2803
|
+
return h;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
function _matchDnsNamePattern(pattern, host) {
|
|
2807
|
+
// Both inputs must be ASCII-normalized. `pattern` is from the SAN;
|
|
2808
|
+
// `host` is the operator-supplied target host.
|
|
2809
|
+
pattern = _normalizeAsciiHost(pattern);
|
|
2810
|
+
if (!pattern || !host) return false;
|
|
2811
|
+
if (pattern.indexOf("*") === -1) {
|
|
2812
|
+
return pattern === host;
|
|
2813
|
+
}
|
|
2814
|
+
// Wildcards permitted only as the entire left-most label.
|
|
2815
|
+
var pLabels = pattern.split(".");
|
|
2816
|
+
var hLabels = host.split(".");
|
|
2817
|
+
if (pLabels.length !== hLabels.length) return false;
|
|
2818
|
+
if (pLabels.length < 3) return false; // refuse `*.tld` — too broad
|
|
2819
|
+
// Only the FIRST label may contain the wildcard, and it must be `*`
|
|
2820
|
+
// exactly (no partial like `f*o`).
|
|
2821
|
+
if (pLabels[0] !== "*") return false;
|
|
2822
|
+
for (var li = 1; li < pLabels.length; li += 1) {
|
|
2823
|
+
if (pLabels[li].indexOf("*") !== -1) return false;
|
|
2824
|
+
if (pLabels[li] !== hLabels[li]) return false;
|
|
2825
|
+
}
|
|
2826
|
+
// Left-most host label must be non-empty (no `*` matching empty).
|
|
2827
|
+
if (hLabels[0].length === 0) return false;
|
|
2828
|
+
return true;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
function _parseSanString(rawSubjectAltName) {
|
|
2832
|
+
// Node exposes the SAN as a comma-separated string of typed entries:
|
|
2833
|
+
// "DNS:foo.example.com, DNS:*.example.com, IP Address:198.51.100.1,
|
|
2834
|
+
// IP Address:2001:db8::1"
|
|
2835
|
+
// The RFC 9525 verifier only consumes DNS / IP entries.
|
|
2836
|
+
var dns = [];
|
|
2837
|
+
var ips = [];
|
|
2838
|
+
if (typeof rawSubjectAltName !== "string" || rawSubjectAltName.length === 0) {
|
|
2839
|
+
return { dns: dns, ips: ips };
|
|
2840
|
+
}
|
|
2841
|
+
var entries = rawSubjectAltName.split(",");
|
|
2842
|
+
for (var i = 0; i < entries.length; i += 1) {
|
|
2843
|
+
var raw = entries[i].trim();
|
|
2844
|
+
var colon = raw.indexOf(":");
|
|
2845
|
+
if (colon === -1) continue;
|
|
2846
|
+
var kind = raw.slice(0, colon).trim();
|
|
2847
|
+
var val = raw.slice(colon + 1).trim();
|
|
2848
|
+
if (kind === "DNS") {
|
|
2849
|
+
dns.push(val);
|
|
2850
|
+
} else if (kind === "IP Address" || kind === "IP") {
|
|
2851
|
+
ips.push(val);
|
|
2852
|
+
}
|
|
2853
|
+
// Other GeneralName types (URI / email / dirName / OID-based) are
|
|
2854
|
+
// outside RFC 9525's HTTPS scope.
|
|
2855
|
+
}
|
|
2856
|
+
return { dns: dns, ips: ips };
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
function _normalizeIpForCompare(ip) {
|
|
2860
|
+
// Lower-case + strip embedded brackets so "[::1]" / "::1" / "::0001"
|
|
2861
|
+
// all compare equal.
|
|
2862
|
+
if (typeof ip !== "string") return null;
|
|
2863
|
+
var s = ip.trim();
|
|
2864
|
+
if (s.length >= 2 && s.charAt(0) === "[" && s.charAt(s.length - 1) === "]") {
|
|
2865
|
+
s = s.slice(1, -1);
|
|
2866
|
+
}
|
|
2867
|
+
// For IPv6 the canonical form is what `net.isIP` accepts; we
|
|
2868
|
+
// round-trip through Buffer comparison via net.isIPv4 / isIPv6.
|
|
2869
|
+
if (net.isIPv4(s)) return { family: 4, text: s };
|
|
2870
|
+
if (net.isIPv6(s)) {
|
|
2871
|
+
// Canonicalize by expanding to bytes and re-emitting lower-case.
|
|
2872
|
+
var parts = s.split("%"); // strip zone id
|
|
2873
|
+
var addr = parts[0];
|
|
2874
|
+
// Expand "::" then re-collapse via toString won't work in pure JS;
|
|
2875
|
+
// instead produce a 16-byte buffer for byte-equal comparison.
|
|
2876
|
+
var bytes = _ipv6ToBytes(addr);
|
|
2877
|
+
if (!bytes) return null;
|
|
2878
|
+
return { family: 6, text: addr.toLowerCase(), bytes: bytes };
|
|
2879
|
+
}
|
|
2880
|
+
return null;
|
|
2881
|
+
}
|
|
2882
|
+
function _ipv6ToBytes(addr) {
|
|
2883
|
+
// Minimal IPv6 → 16-byte parser. Splits on "::" once, parses each
|
|
2884
|
+
// hextet as base-16 uint16. Returns null on malformed input.
|
|
2885
|
+
if (typeof addr !== "string") return null;
|
|
2886
|
+
var halves;
|
|
2887
|
+
var doubleIdx = addr.indexOf("::");
|
|
2888
|
+
if (doubleIdx === -1) {
|
|
2889
|
+
halves = [addr.split(":"), []];
|
|
2890
|
+
} else {
|
|
2891
|
+
var leftStr = addr.slice(0, doubleIdx);
|
|
2892
|
+
var rightStr = addr.slice(doubleIdx + 2);
|
|
2893
|
+
halves = [
|
|
2894
|
+
leftStr.length ? leftStr.split(":") : [],
|
|
2895
|
+
rightStr.length ? rightStr.split(":") : [],
|
|
2896
|
+
];
|
|
2897
|
+
}
|
|
2898
|
+
var left = halves[0], right = halves[1];
|
|
2899
|
+
var fillCount = 8 - (left.length + right.length); // allow:raw-byte-literal — IPv6 has 8 hextets
|
|
2900
|
+
if (fillCount < 0) return null;
|
|
2901
|
+
var hextets = left.concat(new Array(fillCount).fill("0")).concat(right);
|
|
2902
|
+
if (hextets.length !== 8) return null; // allow:raw-byte-literal — IPv6 hextet count
|
|
2903
|
+
var bytes = Buffer.alloc(16); // allow:raw-byte-literal — IPv6 = 16 bytes
|
|
2904
|
+
for (var i = 0; i < 8; i += 1) { // allow:raw-byte-literal — IPv6 hextet count
|
|
2905
|
+
var h = hextets[i];
|
|
2906
|
+
if (!safeBuffer.IPV6_HEXTET_RE.test(h)) return null;
|
|
2907
|
+
var v = parseInt(h, 16); // allow:raw-byte-literal — hex radix
|
|
2908
|
+
bytes[i * 2] = (v >> 8) & 0xff; // allow:raw-byte-literal — uint8 mask + uint16-half shift
|
|
2909
|
+
bytes[i * 2 + 1] = v & 0xff; // allow:raw-byte-literal — uint8 mask
|
|
2910
|
+
}
|
|
2911
|
+
return bytes;
|
|
2912
|
+
}
|
|
2913
|
+
function _ipsEqual(sanIp, hostIp) {
|
|
2914
|
+
var a = _normalizeIpForCompare(sanIp);
|
|
2915
|
+
var b = _normalizeIpForCompare(hostIp);
|
|
2916
|
+
if (!a || !b) return false;
|
|
2917
|
+
if (a.family !== b.family) return false;
|
|
2918
|
+
if (a.family === 4) return a.text === b.text;
|
|
2919
|
+
// family === 6 — byte compare.
|
|
2920
|
+
if (!a.bytes || !b.bytes) return false;
|
|
2921
|
+
if (a.bytes.length !== b.bytes.length) return false;
|
|
2922
|
+
for (var i = 0; i < a.bytes.length; i += 1) {
|
|
2923
|
+
if (a.bytes[i] !== b.bytes[i]) return false;
|
|
2924
|
+
}
|
|
2925
|
+
return true;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
/**
|
|
2929
|
+
* @primitive b.network.tls.checkServerIdentity9525
|
|
2930
|
+
* @signature b.network.tls.checkServerIdentity9525(host, cert)
|
|
2931
|
+
* @since 0.8.53
|
|
2932
|
+
* @status stable
|
|
2933
|
+
* @related b.network.tls.connectWithEch
|
|
2934
|
+
*
|
|
2935
|
+
* Drop-in replacement for Node's `tls.checkServerIdentity` that
|
|
2936
|
+
* implements RFC 9525 paragraph 6 strictly. Operators pass it to
|
|
2937
|
+
* `tls.connect({ checkServerIdentity })` (or to any framework primitive
|
|
2938
|
+
* that exposes `pkixStrict: true`).
|
|
2939
|
+
*
|
|
2940
|
+
* Differences vs Node's default matcher:
|
|
2941
|
+
*
|
|
2942
|
+
* - SAN-required when present is mandatory: a peer cert lacking
|
|
2943
|
+
* `subjectAltName` refuses with `tls/pkix-san-required` (RFC 9525
|
|
2944
|
+
* paragraph 6.4.4 forbids Common Name fallback).
|
|
2945
|
+
* - CN-only legacy certs surface a distinct
|
|
2946
|
+
* `tls/pkix-cn-fallback-refused` code so audit logs distinguish
|
|
2947
|
+
* "missing SAN" from "ancient CN-only cert still shipping".
|
|
2948
|
+
* - Wildcard matching is restricted to the entire leftmost label.
|
|
2949
|
+
* `*.example.com` matches `foo.example.com` but NOT
|
|
2950
|
+
* `foo.bar.example.com` and NOT `example.com`. Partial wildcards
|
|
2951
|
+
* like `f*o.example.com` and middle wildcards like
|
|
2952
|
+
* `foo.*.example.com` refuse.
|
|
2953
|
+
* - IP literals match `iPAddress` SAN entries only — never DNS
|
|
2954
|
+
* entries, never wildcards. IPv6 comparison is byte-equal after
|
|
2955
|
+
* canonicalization (zone-id stripped, `::` expanded).
|
|
2956
|
+
*
|
|
2957
|
+
* Returns `Error | undefined` — the `Error` shape Node expects; when
|
|
2958
|
+
* undefined, the connection is permitted to proceed.
|
|
2959
|
+
*
|
|
2960
|
+
* @example
|
|
2961
|
+
* var tls = require("node:tls");
|
|
2962
|
+
* var b = require("@blamejs/core");
|
|
2963
|
+
* var sock = tls.connect({
|
|
2964
|
+
* host: "internal.example.com",
|
|
2965
|
+
* port: 443,
|
|
2966
|
+
* checkServerIdentity: b.network.tls.checkServerIdentity9525,
|
|
2967
|
+
* });
|
|
2968
|
+
*/
|
|
2969
|
+
function checkServerIdentity9525(host, cert) {
|
|
2970
|
+
// Drop-in for tls.checkServerIdentity. Returns Error|undefined.
|
|
2971
|
+
// Node calls this with the post-handshake `cert` shape: subject,
|
|
2972
|
+
// subjectaltname, etc.
|
|
2973
|
+
if (typeof host !== "string" || host.length === 0) {
|
|
2974
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
2975
|
+
"checkServerIdentity9525: host must be a non-empty string");
|
|
2976
|
+
}
|
|
2977
|
+
if (!cert || typeof cert !== "object") {
|
|
2978
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
2979
|
+
"checkServerIdentity9525: peer cert object missing");
|
|
2980
|
+
}
|
|
2981
|
+
var hostIsIp = net.isIP(host) > 0;
|
|
2982
|
+
var hostNorm = hostIsIp ? host : _normalizeAsciiHost(host);
|
|
2983
|
+
if (!hostIsIp && !hostNorm) {
|
|
2984
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
2985
|
+
"checkServerIdentity9525: host '" + host + "' is not a valid ASCII " +
|
|
2986
|
+
"DNS name (pre-convert U-labels to A-labels with punycode)");
|
|
2987
|
+
}
|
|
2988
|
+
var rawSan = cert.subjectaltname;
|
|
2989
|
+
if (typeof rawSan !== "string" || rawSan.length === 0) {
|
|
2990
|
+
// RFC 9525 §6.4.4 forbids CN fallback. If there's no SAN we refuse,
|
|
2991
|
+
// never inspect cert.subject.CN — a CN-only cert violates the
|
|
2992
|
+
// modern PKIX baseline and the operator chose the strict checker.
|
|
2993
|
+
return new NetworkTlsError("tls/pkix-san-required",
|
|
2994
|
+
"checkServerIdentity9525: certificate has no subjectAltName " +
|
|
2995
|
+
"extension (RFC 9525 §6.4.4 forbids Common Name fallback)");
|
|
2996
|
+
}
|
|
2997
|
+
var san = _parseSanString(rawSan);
|
|
2998
|
+
if (hostIsIp) {
|
|
2999
|
+
if (san.ips.length === 0) {
|
|
3000
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
3001
|
+
"checkServerIdentity9525: host '" + host + "' is an IP literal " +
|
|
3002
|
+
"but the certificate's SAN contains no iPAddress entries");
|
|
3003
|
+
}
|
|
3004
|
+
for (var ii = 0; ii < san.ips.length; ii += 1) {
|
|
3005
|
+
if (_ipsEqual(san.ips[ii], host)) return undefined;
|
|
3006
|
+
}
|
|
3007
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
3008
|
+
"checkServerIdentity9525: host IP '" + host + "' does not match " +
|
|
3009
|
+
"any iPAddress SAN (" + san.ips.join(", ") + ")");
|
|
3010
|
+
}
|
|
3011
|
+
// DNS host — must match a dNSName SAN entry.
|
|
3012
|
+
if (san.dns.length === 0) {
|
|
3013
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
3014
|
+
"checkServerIdentity9525: certificate's SAN contains no dNSName " +
|
|
3015
|
+
"entries (host '" + host + "' cannot match an iPAddress-only cert)");
|
|
3016
|
+
}
|
|
3017
|
+
for (var di = 0; di < san.dns.length; di += 1) {
|
|
3018
|
+
if (_matchDnsNamePattern(san.dns[di], hostNorm)) return undefined;
|
|
3019
|
+
}
|
|
3020
|
+
return new NetworkTlsError("tls/pkix-hostname-mismatch",
|
|
3021
|
+
"checkServerIdentity9525: host '" + host + "' does not match any " +
|
|
3022
|
+
"dNSName SAN (" + san.dns.join(", ") + ")");
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
// Detect: did the caller pass a CN-only legacy cert? Surface a
|
|
3026
|
+
// distinct error code so operators can grep audit logs for the
|
|
3027
|
+
// fallback-refused shape vs a generic mismatch.
|
|
3028
|
+
function _refuseCnFallback(host, cert) {
|
|
3029
|
+
if (cert && cert.subject && typeof cert.subject.CN === "string" &&
|
|
3030
|
+
cert.subject.CN.length > 0 &&
|
|
3031
|
+
(typeof cert.subjectaltname !== "string" || cert.subjectaltname.length === 0)) {
|
|
3032
|
+
return new NetworkTlsError("tls/pkix-cn-fallback-refused",
|
|
3033
|
+
"checkServerIdentity9525: peer cert is CN-only (CN='" +
|
|
3034
|
+
cert.subject.CN + "'); RFC 9525 §6.4.4 refuses CN-fallback. " +
|
|
3035
|
+
"Reissue the certificate with a subjectAltName extension covering " +
|
|
3036
|
+
"host '" + host + "'.");
|
|
3037
|
+
}
|
|
3038
|
+
return null;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// Public combined verifier — applies both the SAN-required check and
|
|
3042
|
+
// the CN-fallback explicit refusal so operators get the more specific
|
|
3043
|
+
// of the two error codes when applicable. checkServerIdentity9525 is
|
|
3044
|
+
// the drop-in name; this internal helper is what `connect` wires in.
|
|
3045
|
+
function _checkServerIdentityStrict(host, cert) {
|
|
3046
|
+
var cnRefusal = _refuseCnFallback(host, cert);
|
|
3047
|
+
if (cnRefusal) return cnRefusal;
|
|
3048
|
+
return checkServerIdentity9525(host, cert);
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// CVE-2026-21637 — Node propagates a synchronous throw from an
|
|
3052
|
+
// operator-supplied SNICallback up through the TLS handshake listener;
|
|
3053
|
+
// the unhandled throw on an unexpected servername crashes the
|
|
3054
|
+
// listener. RFC 6066 §3 expects the server to abort the handshake on a
|
|
3055
|
+
// failed callback, NOT crash the process.
|
|
3056
|
+
//
|
|
3057
|
+
// `wrapSNICallback(operatorCb)` returns a wrapper that:
|
|
3058
|
+
//
|
|
3059
|
+
// - Calls the operator callback in a try/catch.
|
|
3060
|
+
// - Surface a synchronous throw via the async (err, null) callback so
|
|
3061
|
+
// the TLS handshake aborts cleanly. Cb is best-effort: an operator
|
|
3062
|
+
// callback that throws AFTER invoking the callback already (double
|
|
3063
|
+
// invoke) gets the throw caught here without double-invoking again.
|
|
3064
|
+
// - Emit an audit event so a burst of crashes-that-weren't surfaces
|
|
3065
|
+
// in operator review.
|
|
3066
|
+
// - Returns the operator's original callback unchanged if it's not a
|
|
3067
|
+
// function (lets the caller pass undefined through without
|
|
3068
|
+
// special-casing).
|
|
3069
|
+
//
|
|
3070
|
+
// router.js routes its operator-supplied tlsOptions.SNICallback through
|
|
3071
|
+
// this helper before handing the options off to https.createServer.
|
|
3072
|
+
// Any future framework primitive that takes operator SNICallback
|
|
3073
|
+
// values does the same.
|
|
3074
|
+
function wrapSNICallback(operatorCb) {
|
|
3075
|
+
if (typeof operatorCb !== "function") return operatorCb;
|
|
3076
|
+
return function _wrappedSNICallback(servername, cb) {
|
|
3077
|
+
try {
|
|
3078
|
+
operatorCb(servername, cb);
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
try {
|
|
3081
|
+
audit().safeEmit({
|
|
3082
|
+
action: "network.tls.sni_callback_threw",
|
|
3083
|
+
outcome: "failure",
|
|
3084
|
+
metadata: {
|
|
3085
|
+
servername: typeof servername === "string" ? servername : null,
|
|
3086
|
+
reason: (err && err.message) ? err.message : String(err),
|
|
3087
|
+
},
|
|
3088
|
+
});
|
|
3089
|
+
} catch (_auditErr) { /* drop-silent — audit best-effort */ }
|
|
3090
|
+
try { cb(err, null); }
|
|
3091
|
+
catch (_cbErr) { /* cb already invoked or unavailable */ }
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
module.exports = {
|
|
3097
|
+
addCa: addCa,
|
|
3098
|
+
addCaBundle: addCaBundle,
|
|
3099
|
+
removeCa: removeCa,
|
|
3100
|
+
removeCaByLabel: removeCaByLabel,
|
|
3101
|
+
clearAll: clearAll,
|
|
3102
|
+
purgeExpired: purgeExpired,
|
|
3103
|
+
expiringSoon: expiringSoon,
|
|
3104
|
+
expiryMonitor: expiryMonitor,
|
|
3105
|
+
pinsetDriftMonitor: pinsetDriftMonitor,
|
|
3106
|
+
useSystemTrust: useSystemTrust,
|
|
3107
|
+
isSystemTrustEnabled: isSystemTrustEnabled,
|
|
3108
|
+
getTrustStore: getTrustStore,
|
|
3109
|
+
captureBaselineFingerprints: captureBaselineFingerprints,
|
|
3110
|
+
detectBaselineDrift: detectBaselineDrift,
|
|
3111
|
+
applyToContext: applyToContext,
|
|
3112
|
+
buildOptions: buildOptions,
|
|
3113
|
+
getCaPems: getCaPems,
|
|
3114
|
+
ocsp: ocsp,
|
|
3115
|
+
ct: ct,
|
|
3116
|
+
pqc: pqc,
|
|
3117
|
+
preferredGroups: preferredGroups,
|
|
3118
|
+
parseEchConfigList: parseEchConfigList,
|
|
3119
|
+
connectWithEch: connectWithEch,
|
|
3120
|
+
checkServerIdentity9525: checkServerIdentity9525,
|
|
3121
|
+
wrapSNICallback: wrapSNICallback,
|
|
3122
|
+
TlsTrustError: TlsTrustError,
|
|
3123
|
+
NetworkTlsError: NetworkTlsError,
|
|
3124
|
+
_resetForTest: _resetForTest,
|
|
3125
|
+
_checkServerIdentityStrict: _checkServerIdentityStrict,
|
|
3126
|
+
};
|