@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,2109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.auth.saml
|
|
4
|
+
* @nav Identity
|
|
5
|
+
* @title SAML 2.0 SP
|
|
6
|
+
* @order 370
|
|
7
|
+
* @card SAML 2.0 Service Provider primitive — builds AuthnRequests,
|
|
8
|
+
* parses + verifies IdP-signed Responses, validates the
|
|
9
|
+
* assertion's SubjectConfirmation / Conditions, and
|
|
10
|
+
* defends against XML signature-wrapping via
|
|
11
|
+
* `b.xmlC14n.canonicalizeElementById`'s single-match
|
|
12
|
+
* invariant.
|
|
13
|
+
*
|
|
14
|
+
* @intro
|
|
15
|
+
* SAML 2.0 (OASIS) is the federation protocol financial /
|
|
16
|
+
* government / enterprise IdPs still ship — operators can't always
|
|
17
|
+
* require an OIDC IdP. This primitive implements the SP side
|
|
18
|
+
* only:
|
|
19
|
+
*
|
|
20
|
+
* - AuthnRequest builder (HTTP-Redirect + HTTP-POST bindings)
|
|
21
|
+
* - Response parser:
|
|
22
|
+
* * Verify Response or Assertion XMLDSig (whichever the IdP
|
|
23
|
+
* signed) using the IdP's signing certificate
|
|
24
|
+
* * Refuse signature-wrapping by enforcing single-element-
|
|
25
|
+
* match on the Reference URI (via xml-c14n)
|
|
26
|
+
* * Validate `NotOnOrAfter` / `NotBefore` / `Recipient` /
|
|
27
|
+
* `InResponseTo` on SubjectConfirmation
|
|
28
|
+
* * Validate `Conditions/NotBefore`/`NotOnOrAfter`/
|
|
29
|
+
* `AudienceRestriction`
|
|
30
|
+
* - SP metadata XML emitter
|
|
31
|
+
* - MDQ (RFC 8414-style metadata-query) fetch with strict
|
|
32
|
+
* server-identity per RFC 9525
|
|
33
|
+
*
|
|
34
|
+
* Operators wire two routes:
|
|
35
|
+
*
|
|
36
|
+
* /saml/login → returns the AuthnRequest URL (Redirect binding)
|
|
37
|
+
* OR an HTML form (POST binding) the user-agent
|
|
38
|
+
* submits to the IdP's SSO endpoint.
|
|
39
|
+
* /saml/acs → AssertionConsumerService — receives the IdP's
|
|
40
|
+
* SAMLResponse, calls verifyResponse, hydrates
|
|
41
|
+
* the user session.
|
|
42
|
+
*
|
|
43
|
+
* Storage of `InResponseTo` / RelayState pre-image / nonce is
|
|
44
|
+
* operator-side via b.cache or b.session — the framework gives the
|
|
45
|
+
* parsing + verification primitive; operators wire freshness +
|
|
46
|
+
* replay defense.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
var lazyRequire = require("../lazy-require");
|
|
50
|
+
var validateOpts = require("../validate-opts");
|
|
51
|
+
var zlib = require("node:zlib");
|
|
52
|
+
var nodeCrypto = require("node:crypto");
|
|
53
|
+
var pqcSoftware = require("../pqc-software");
|
|
54
|
+
var bCrypto = require("../crypto");
|
|
55
|
+
var { generateToken, timingSafeEqual } = bCrypto;
|
|
56
|
+
var { AuthError } = require("../framework-error");
|
|
57
|
+
|
|
58
|
+
var xmlC14n = lazyRequire(function () { return require("../xml-c14n"); });
|
|
59
|
+
var httpClient = lazyRequire(function () { return require("../http-client"); });
|
|
60
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
61
|
+
var observability = lazyRequire(function () { return require("../observability"); });
|
|
62
|
+
var emit = validateOpts.makeNamespacedEmitters("auth.saml", { audit: audit, observability: observability });
|
|
63
|
+
|
|
64
|
+
var SUPPORTED_DIGEST = { "http://www.w3.org/2001/04/xmlenc#sha256": "sha256",
|
|
65
|
+
"http://www.w3.org/2001/04/xmlenc#sha384": "sha384",
|
|
66
|
+
"http://www.w3.org/2001/04/xmlenc#sha512": "sha512" };
|
|
67
|
+
var SUPPORTED_SIG = { "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": { hash: "sha256", padding: "pkcs1" },
|
|
68
|
+
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384": { hash: "sha384", padding: "pkcs1" },
|
|
69
|
+
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": { hash: "sha512", padding: "pkcs1" },
|
|
70
|
+
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256": { hash: "sha256", ec: true },
|
|
71
|
+
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384": { hash: "sha384", ec: true },
|
|
72
|
+
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512": { hash: "sha512", ec: true } };
|
|
73
|
+
var SAML_NS = {
|
|
74
|
+
protocol: "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
75
|
+
assertion: "urn:oasis:names:tc:SAML:2.0:assertion",
|
|
76
|
+
metadata: "urn:oasis:names:tc:SAML:2.0:metadata",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
var _emitAudit = emit.audit;
|
|
80
|
+
var _emitMetric = emit.metric;
|
|
81
|
+
|
|
82
|
+
function _findChild(node, localName, namespace) {
|
|
83
|
+
if (!node || !node.children) return null;
|
|
84
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
85
|
+
var c = node.children[i];
|
|
86
|
+
if (c.type !== "element") continue;
|
|
87
|
+
var colon = c.name.indexOf(":");
|
|
88
|
+
var local = colon !== -1 ? c.name.substring(colon + 1) : c.name;
|
|
89
|
+
if (local !== localName) continue;
|
|
90
|
+
if (namespace) {
|
|
91
|
+
var prefix = colon !== -1 ? c.name.substring(0, colon) : "";
|
|
92
|
+
var ns = _namespaceForPrefix(c, prefix);
|
|
93
|
+
if (ns !== namespace) continue;
|
|
94
|
+
}
|
|
95
|
+
return c;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _findAllChildren(node, localName, namespace) {
|
|
101
|
+
var out = [];
|
|
102
|
+
if (!node || !node.children) return out;
|
|
103
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
104
|
+
var c = node.children[i];
|
|
105
|
+
if (c.type !== "element") continue;
|
|
106
|
+
var colon = c.name.indexOf(":");
|
|
107
|
+
var local = colon !== -1 ? c.name.substring(colon + 1) : c.name;
|
|
108
|
+
if (local !== localName) continue;
|
|
109
|
+
if (namespace) {
|
|
110
|
+
var prefix = colon !== -1 ? c.name.substring(0, colon) : "";
|
|
111
|
+
var ns = _namespaceForPrefix(c, prefix);
|
|
112
|
+
if (ns !== namespace) continue;
|
|
113
|
+
}
|
|
114
|
+
out.push(c);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function _namespaceForPrefix(node, prefix) {
|
|
120
|
+
var cur = node;
|
|
121
|
+
while (cur) {
|
|
122
|
+
if (cur.attrs) {
|
|
123
|
+
for (var i = 0; i < cur.attrs.length; i++) {
|
|
124
|
+
var a = cur.attrs[i];
|
|
125
|
+
if (prefix === "" && a.name === "xmlns") return a.value;
|
|
126
|
+
if (a.name === "xmlns:" + prefix) return a.value;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
cur = cur.parent;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function _attr(node, name) {
|
|
135
|
+
if (!node || !node.attrs) return null;
|
|
136
|
+
for (var i = 0; i < node.attrs.length; i++) {
|
|
137
|
+
if (node.attrs[i].name === name) return node.attrs[i].value;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _textContent(node) {
|
|
143
|
+
if (!node || !node.children) return "";
|
|
144
|
+
var out = "";
|
|
145
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
146
|
+
var c = node.children[i];
|
|
147
|
+
if (c.type === "text") out += c.text;
|
|
148
|
+
else if (c.type === "element") out += _textContent(c);
|
|
149
|
+
}
|
|
150
|
+
return out.trim();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _verifyXmldsig(envelope, signatureNode, certPem) {
|
|
154
|
+
// Parse SignedInfo + extract canonicalization, signature, and
|
|
155
|
+
// reference algorithms. Then:
|
|
156
|
+
// 1. Locate the referenced element by its ID attribute (single-
|
|
157
|
+
// match invariant from xml-c14n.canonicalizeElementById)
|
|
158
|
+
// 2. C14n + hash that element, compare to Reference DigestValue
|
|
159
|
+
// 3. C14n SignedInfo, verify the signature against the cert pubkey
|
|
160
|
+
var signedInfo = _findChild(signatureNode, "SignedInfo");
|
|
161
|
+
if (!signedInfo) {
|
|
162
|
+
throw new AuthError("auth-saml/no-signed-info", "Signature missing SignedInfo");
|
|
163
|
+
}
|
|
164
|
+
var canonMethodNode = _findChild(signedInfo, "CanonicalizationMethod");
|
|
165
|
+
var canonAlgo = canonMethodNode && _attr(canonMethodNode, "Algorithm");
|
|
166
|
+
if (canonAlgo !== "http://www.w3.org/2001/10/xml-exc-c14n#" &&
|
|
167
|
+
canonAlgo !== "http://www.w3.org/2001/10/xml-exc-c14n#WithComments") {
|
|
168
|
+
throw new AuthError("auth-saml/unsupported-c14n",
|
|
169
|
+
"Unsupported CanonicalizationMethod: " + canonAlgo + " (only xml-exc-c14n supported)");
|
|
170
|
+
}
|
|
171
|
+
var sigMethodNode = _findChild(signedInfo, "SignatureMethod");
|
|
172
|
+
var sigAlgo = sigMethodNode && _attr(sigMethodNode, "Algorithm");
|
|
173
|
+
if (!SUPPORTED_SIG[sigAlgo]) {
|
|
174
|
+
throw new AuthError("auth-saml/unsupported-sig-alg",
|
|
175
|
+
"Unsupported SignatureMethod: " + sigAlgo);
|
|
176
|
+
}
|
|
177
|
+
var refNode = _findChild(signedInfo, "Reference");
|
|
178
|
+
if (!refNode) throw new AuthError("auth-saml/no-reference", "SignedInfo missing Reference");
|
|
179
|
+
var refUri = _attr(refNode, "URI") || "";
|
|
180
|
+
if (refUri.charAt(0) !== "#") {
|
|
181
|
+
throw new AuthError("auth-saml/external-reference",
|
|
182
|
+
"Reference URI must be a same-document fragment (got \"" + refUri + "\")");
|
|
183
|
+
}
|
|
184
|
+
var refId = refUri.substring(1);
|
|
185
|
+
var digestMethodNode = _findChild(refNode, "DigestMethod");
|
|
186
|
+
var digestAlgo = digestMethodNode && _attr(digestMethodNode, "Algorithm");
|
|
187
|
+
if (!SUPPORTED_DIGEST[digestAlgo]) {
|
|
188
|
+
throw new AuthError("auth-saml/unsupported-digest",
|
|
189
|
+
"Unsupported DigestMethod: " + digestAlgo);
|
|
190
|
+
}
|
|
191
|
+
var digestValueNode = _findChild(refNode, "DigestValue");
|
|
192
|
+
var expectedDigestB64 = _textContent(digestValueNode);
|
|
193
|
+
if (!expectedDigestB64) {
|
|
194
|
+
throw new AuthError("auth-saml/no-digest-value", "Reference missing DigestValue");
|
|
195
|
+
}
|
|
196
|
+
var withComments = canonAlgo.indexOf("#WithComments") !== -1;
|
|
197
|
+
|
|
198
|
+
// XMLDSig Reference Transforms — applied in order before the digest.
|
|
199
|
+
// SAML responses commonly use:
|
|
200
|
+
// 1. http://www.w3.org/2000/09/xmldsig#enveloped-signature (strip
|
|
201
|
+
// the <Signature> child of the referenced element)
|
|
202
|
+
// 2. http://www.w3.org/2001/10/xml-exc-c14n# (canonicalize)
|
|
203
|
+
// Without the enveloped-signature transform, the digest is computed
|
|
204
|
+
// over the assertion-including-signature, which never matches the
|
|
205
|
+
// signed-then-signature-injected reality.
|
|
206
|
+
var transformsNode = _findChild(refNode, "Transforms");
|
|
207
|
+
var transformList = transformsNode ? _findAllChildren(transformsNode, "Transform") : [];
|
|
208
|
+
var stripSignature = false;
|
|
209
|
+
var refC14nWithComments = withComments;
|
|
210
|
+
for (var ti = 0; ti < transformList.length; ti++) {
|
|
211
|
+
var algo = _attr(transformList[ti], "Algorithm");
|
|
212
|
+
switch (algo) {
|
|
213
|
+
case "http://www.w3.org/2000/09/xmldsig#enveloped-signature":
|
|
214
|
+
stripSignature = true;
|
|
215
|
+
break;
|
|
216
|
+
case "http://www.w3.org/2001/10/xml-exc-c14n#":
|
|
217
|
+
refC14nWithComments = false;
|
|
218
|
+
break;
|
|
219
|
+
case "http://www.w3.org/2001/10/xml-exc-c14n#WithComments":
|
|
220
|
+
refC14nWithComments = true;
|
|
221
|
+
break;
|
|
222
|
+
default:
|
|
223
|
+
throw new AuthError("auth-saml/unsupported-transform",
|
|
224
|
+
"Unsupported Transform: " + algo + " (supported: enveloped-signature, xml-exc-c14n)");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Locate the referenced element with the single-match invariant
|
|
229
|
+
// (anti-wrapping defense) — if zero or duplicate IDs match, refuse.
|
|
230
|
+
// We then optionally strip its <Signature> child(ren) per the
|
|
231
|
+
// enveloped-signature transform and canonicalize the result.
|
|
232
|
+
var c14n = xmlC14n();
|
|
233
|
+
var rootForRef = c14n.parse(envelope);
|
|
234
|
+
var matches = [];
|
|
235
|
+
(function _walk(node) {
|
|
236
|
+
if (node.type !== "element") return;
|
|
237
|
+
if (node.attrs) {
|
|
238
|
+
for (var ai = 0; ai < node.attrs.length; ai++) {
|
|
239
|
+
if (node.attrs[ai].name === "ID" && node.attrs[ai].value === refId) {
|
|
240
|
+
matches.push(node);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
for (var ci = 0; ci < node.children.length; ci++) _walk(node.children[ci]);
|
|
246
|
+
})(rootForRef);
|
|
247
|
+
if (matches.length === 0) {
|
|
248
|
+
throw new AuthError("auth-saml/no-id-match",
|
|
249
|
+
"Reference URI #" + refId + " resolves to no element");
|
|
250
|
+
}
|
|
251
|
+
if (matches.length > 1) {
|
|
252
|
+
throw new AuthError("auth-saml/duplicate-id",
|
|
253
|
+
"Reference URI #" + refId + " matches " + matches.length +
|
|
254
|
+
" elements — refused (signature-wrapping defense)");
|
|
255
|
+
}
|
|
256
|
+
var refTarget = matches[0];
|
|
257
|
+
if (stripSignature) {
|
|
258
|
+
refTarget.children = refTarget.children.filter(function (c) {
|
|
259
|
+
if (c.type !== "element") return true;
|
|
260
|
+
var colon = c.name.indexOf(":");
|
|
261
|
+
var local = colon !== -1 ? c.name.substring(colon + 1) : c.name;
|
|
262
|
+
return local !== "Signature";
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
var canonical = c14n.canonicalize(refTarget, { withComments: refC14nWithComments });
|
|
266
|
+
var actualDigest = nodeCrypto.createHash(SUPPORTED_DIGEST[digestAlgo]).update(canonical).digest();
|
|
267
|
+
// Constant-time compare — Buffer.compare short-circuits per byte and
|
|
268
|
+
// leaks the matching-prefix length when the operator's audit/log
|
|
269
|
+
// captures verify-failure timing. timingSafeEqual returns false for
|
|
270
|
+
// length-mismatched inputs without leaking length.
|
|
271
|
+
if (!timingSafeEqual(Buffer.from(expectedDigestB64, "base64"), actualDigest)) {
|
|
272
|
+
throw new AuthError("auth-saml/digest-mismatch",
|
|
273
|
+
"Reference DigestValue does not match canonicalized referenced element (signature-wrapping or tampered content)");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// C14n SignedInfo as a parsed-tree node — we need to canonicalize
|
|
277
|
+
// the SignedInfo element ITSELF, not look it up by ID. Slice the
|
|
278
|
+
// serialized SignedInfo from the parsed tree.
|
|
279
|
+
var signedInfoCanonical = xmlC14n().canonicalize(signedInfo, { withComments: withComments });
|
|
280
|
+
|
|
281
|
+
// Resolve signer key from cert
|
|
282
|
+
var cert = nodeCrypto.createPublicKey({ key: certPem, format: "pem" });
|
|
283
|
+
|
|
284
|
+
var sigValueNode = _findChild(signatureNode, "SignatureValue");
|
|
285
|
+
var sigB64 = _textContent(sigValueNode).replace(/\s+/g, "");
|
|
286
|
+
if (!sigB64) throw new AuthError("auth-saml/no-signature-value", "Signature missing SignatureValue");
|
|
287
|
+
var sigBytes = Buffer.from(sigB64, "base64");
|
|
288
|
+
|
|
289
|
+
var sigSpec = SUPPORTED_SIG[sigAlgo];
|
|
290
|
+
var verifyOpts = { key: cert };
|
|
291
|
+
if (sigSpec.padding === "pkcs1") verifyOpts.padding = nodeCrypto.constants.RSA_PKCS1_PADDING;
|
|
292
|
+
if (sigSpec.ec) verifyOpts.dsaEncoding = "der";
|
|
293
|
+
var ok = nodeCrypto.verify(sigSpec.hash, signedInfoCanonical, verifyOpts, sigBytes);
|
|
294
|
+
if (!ok) {
|
|
295
|
+
throw new AuthError("auth-saml/bad-signature", "SAML signature verification failed");
|
|
296
|
+
}
|
|
297
|
+
return { refId: refId };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @primitive b.auth.saml.sp.create
|
|
302
|
+
* @signature b.auth.saml.sp.create(opts)
|
|
303
|
+
* @since 0.8.62
|
|
304
|
+
* @status stable
|
|
305
|
+
* @related b.xmlC14n.canonicalizeElementById, b.network.tls.checkServerIdentity9525
|
|
306
|
+
*
|
|
307
|
+
* Build a SAML 2.0 SP. Operators supply:
|
|
308
|
+
* - the SP entityId (this RP's URL)
|
|
309
|
+
* - assertionConsumerServiceUrl (the /saml/acs route)
|
|
310
|
+
* - idpEntityId + idpSsoUrl + idpCertPem (the trust anchor for
|
|
311
|
+
* this SP — typically rotated quarterly via MDQ)
|
|
312
|
+
*
|
|
313
|
+
* @opts
|
|
314
|
+
* {
|
|
315
|
+
* entityId: string, // this SP's entityID URL
|
|
316
|
+
* assertionConsumerServiceUrl: string, // SP /saml/acs endpoint
|
|
317
|
+
* idpEntityId: string,
|
|
318
|
+
* idpSsoUrl: string, // IdP single-sign-on endpoint
|
|
319
|
+
* idpCertPem: string, // IdP signing cert (PEM)
|
|
320
|
+
* audience?: string, // default = entityId
|
|
321
|
+
* clockSkewSec?: number, // default 60
|
|
322
|
+
* nameIdFormat?: string, // optional NameIDPolicy/Format
|
|
323
|
+
* }
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* var sp = b.auth.saml.sp.create({
|
|
327
|
+
* entityId: "https://sp.example",
|
|
328
|
+
* assertionConsumerServiceUrl: "https://sp.example/saml/acs",
|
|
329
|
+
* idpEntityId: "https://idp.example",
|
|
330
|
+
* idpSsoUrl: "https://idp.example/sso",
|
|
331
|
+
* idpCertPem: process.env.IDP_CERT_PEM,
|
|
332
|
+
* });
|
|
333
|
+
*/
|
|
334
|
+
function create(opts) {
|
|
335
|
+
validateOpts.requireObject(opts, "auth.saml.sp.create", AuthError);
|
|
336
|
+
validateOpts.requireNonEmptyString(opts.entityId, "entityId", AuthError, "auth-saml/no-entity-id");
|
|
337
|
+
validateOpts.requireNonEmptyString(opts.assertionConsumerServiceUrl, "assertionConsumerServiceUrl",
|
|
338
|
+
AuthError, "auth-saml/no-acs");
|
|
339
|
+
validateOpts.requireNonEmptyString(opts.idpEntityId, "idpEntityId", AuthError, "auth-saml/no-idp-entity-id");
|
|
340
|
+
validateOpts.requireNonEmptyString(opts.idpSsoUrl, "idpSsoUrl", AuthError, "auth-saml/no-idp-sso");
|
|
341
|
+
validateOpts.requireNonEmptyString(opts.idpCertPem, "idpCertPem", AuthError, "auth-saml/no-idp-cert");
|
|
342
|
+
|
|
343
|
+
var audience = opts.audience || opts.entityId;
|
|
344
|
+
var clockSkewSec = typeof opts.clockSkewSec === "number" ? opts.clockSkewSec : 60; // allow:raw-time-literal — clock-skew default
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @primitive b.auth.saml.sp.buildAuthnRequest
|
|
348
|
+
* @signature b.auth.saml.sp.buildAuthnRequest(opts)
|
|
349
|
+
* @since 0.8.62
|
|
350
|
+
*
|
|
351
|
+
* Build a SAMLRequest XML + the URL-safe deflate-base64 encoding
|
|
352
|
+
* for the HTTP-Redirect binding. Returns `{ id, redirectUrl, raw }`
|
|
353
|
+
* where `id` is the AuthnRequest ID the SP must remember (binds to
|
|
354
|
+
* the response's `InResponseTo`).
|
|
355
|
+
*
|
|
356
|
+
* @opts
|
|
357
|
+
* { relayState?: string }
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* var ar = sp.buildAuthnRequest({ relayState: "/dashboard" });
|
|
361
|
+
* res.statusCode = 302;
|
|
362
|
+
* res.setHeader("Location", ar.redirectUrl);
|
|
363
|
+
* res.end();
|
|
364
|
+
* // remember ar.id; expect it back in the Response InResponseTo
|
|
365
|
+
*/
|
|
366
|
+
function buildAuthnRequest(bopts) {
|
|
367
|
+
bopts = bopts || {};
|
|
368
|
+
var id = "_" + generateToken(20);
|
|
369
|
+
var issueInstant = new Date().toISOString();
|
|
370
|
+
// RFC 3741 §1.3.2 attribute-value + §1.3.1 element-text escaping
|
|
371
|
+
// for every operator-supplied string interpolated into the
|
|
372
|
+
// AuthnRequest XML. Without escaping, a `"` or `<` in any of the
|
|
373
|
+
// four fields (idpSsoUrl, assertionConsumerServiceUrl, entityId,
|
|
374
|
+
// nameIdFormat) produces malformed XML and can break out of the
|
|
375
|
+
// attribute / element context, injecting unsigned content the IdP
|
|
376
|
+
// canonicalizer would never honor but the consumer's signed XML
|
|
377
|
+
// baseline relies on. (Surfaced by the 2026-05-11 SAML audit.)
|
|
378
|
+
var c14n = xmlC14n();
|
|
379
|
+
var nameIdPolicy = "";
|
|
380
|
+
if (opts.nameIdFormat) {
|
|
381
|
+
nameIdPolicy = "<samlp:NameIDPolicy Format=\"" + c14n.escapeAttrValue(opts.nameIdFormat) +
|
|
382
|
+
"\" AllowCreate=\"true\"/>";
|
|
383
|
+
}
|
|
384
|
+
var xml =
|
|
385
|
+
"<samlp:AuthnRequest xmlns:samlp=\"" + SAML_NS.protocol + "\" " +
|
|
386
|
+
"xmlns:saml=\"" + SAML_NS.assertion + "\" " +
|
|
387
|
+
"ID=\"" + id + "\" " +
|
|
388
|
+
"Version=\"2.0\" " +
|
|
389
|
+
"IssueInstant=\"" + issueInstant + "\" " +
|
|
390
|
+
"Destination=\"" + c14n.escapeAttrValue(opts.idpSsoUrl) + "\" " +
|
|
391
|
+
"AssertionConsumerServiceURL=\"" + c14n.escapeAttrValue(opts.assertionConsumerServiceUrl) + "\" " +
|
|
392
|
+
"ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\">" +
|
|
393
|
+
"<saml:Issuer>" + c14n.escapeText(opts.entityId) + "</saml:Issuer>" +
|
|
394
|
+
nameIdPolicy +
|
|
395
|
+
"</samlp:AuthnRequest>";
|
|
396
|
+
var deflated = zlib.deflateRawSync(Buffer.from(xml, "utf8"));
|
|
397
|
+
var samlRequest = encodeURIComponent(deflated.toString("base64"));
|
|
398
|
+
var url = opts.idpSsoUrl + (opts.idpSsoUrl.indexOf("?") === -1 ? "?" : "&") +
|
|
399
|
+
"SAMLRequest=" + samlRequest;
|
|
400
|
+
if (bopts.relayState) {
|
|
401
|
+
url += "&RelayState=" + encodeURIComponent(bopts.relayState);
|
|
402
|
+
}
|
|
403
|
+
_emitAudit("authnrequest_built", "success", { id: id, idp: opts.idpEntityId });
|
|
404
|
+
_emitMetric("authn-request-built");
|
|
405
|
+
return { id: id, redirectUrl: url, raw: xml };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* @primitive b.auth.saml.sp.verifyResponse
|
|
410
|
+
* @signature b.auth.saml.sp.verifyResponse(samlResponseB64, vopts)
|
|
411
|
+
* @since 0.8.62
|
|
412
|
+
*
|
|
413
|
+
* Parse + verify the IdP's SAMLResponse (the base64-encoded XML
|
|
414
|
+
* the user-agent POSTs to /saml/acs). Validates the XMLDSig
|
|
415
|
+
* (Response-level OR Assertion-level signature), the assertion's
|
|
416
|
+
* SubjectConfirmation Bearer constraints, and Conditions audience
|
|
417
|
+
* + time bounds. Returns `{ nameId, nameIdFormat, sessionIndex,
|
|
418
|
+
* attributes, audience, inResponseTo }`.
|
|
419
|
+
*
|
|
420
|
+
* @opts
|
|
421
|
+
* {
|
|
422
|
+
* expectedInResponseTo?: string, // the AuthnRequest ID this is responding to
|
|
423
|
+
* now?: number, // timestamp override for tests
|
|
424
|
+
* }
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* app.post("/saml/acs", function (req, res) {
|
|
428
|
+
* var info = sp.verifyResponse(req.body.SAMLResponse, {
|
|
429
|
+
* expectedInResponseTo: req.session.samlRequestId,
|
|
430
|
+
* });
|
|
431
|
+
* // → { nameId, nameIdFormat, sessionIndex, attributes, audience, issuer }
|
|
432
|
+
* });
|
|
433
|
+
*/
|
|
434
|
+
function verifyResponse(samlResponseB64, vopts) {
|
|
435
|
+
vopts = vopts || {};
|
|
436
|
+
if (typeof samlResponseB64 !== "string" || samlResponseB64.length === 0) {
|
|
437
|
+
throw new AuthError("auth-saml/no-response", "verifyResponse: SAMLResponse required");
|
|
438
|
+
}
|
|
439
|
+
var xml = Buffer.from(samlResponseB64, "base64").toString("utf8");
|
|
440
|
+
if (!xml || xml.indexOf("<") === -1) {
|
|
441
|
+
throw new AuthError("auth-saml/bad-response-decode",
|
|
442
|
+
"verifyResponse: SAMLResponse base64 decode produced no XML");
|
|
443
|
+
}
|
|
444
|
+
var c14n = xmlC14n();
|
|
445
|
+
var root = c14n.parse(xml);
|
|
446
|
+
// Root must be Response
|
|
447
|
+
var rootColon = root.name.indexOf(":");
|
|
448
|
+
var rootLocal = rootColon !== -1 ? root.name.substring(rootColon + 1) : root.name;
|
|
449
|
+
if (rootLocal !== "Response") {
|
|
450
|
+
throw new AuthError("auth-saml/wrong-root",
|
|
451
|
+
"verifyResponse: root element must be Response, got " + rootLocal);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// XSW defense — refuse duplicate top-level security-critical
|
|
455
|
+
// elements. SAML XML signature wrapping (XSW) attacks shuffle
|
|
456
|
+
// signed elements alongside unsigned siblings; the parser's
|
|
457
|
+
// first-match `_findChild` lookup combined with the signed-
|
|
458
|
+
// element-ID check at L479 was vulnerable to a multi-Assertion
|
|
459
|
+
// payload where the verifier signed one but the consumer read
|
|
460
|
+
// attributes from another. Reject any Response with more than
|
|
461
|
+
// one of these structural children (Audit 2026-05-11).
|
|
462
|
+
var statusChildren = _findAllChildren(root, "Status", SAML_NS.protocol);
|
|
463
|
+
if (statusChildren.length > 1) {
|
|
464
|
+
throw new AuthError("auth-saml/duplicate-status",
|
|
465
|
+
"verifyResponse: Response has multiple <Status> children — XSW shape refused");
|
|
466
|
+
}
|
|
467
|
+
var status = statusChildren[0] || null;
|
|
468
|
+
var statusCodeChildren = status ? _findAllChildren(status, "StatusCode", SAML_NS.protocol) : [];
|
|
469
|
+
if (statusCodeChildren.length > 1) {
|
|
470
|
+
throw new AuthError("auth-saml/duplicate-status-code",
|
|
471
|
+
"verifyResponse: <Status> has multiple <StatusCode> children — XSW shape refused");
|
|
472
|
+
}
|
|
473
|
+
var statusCode = statusCodeChildren[0] || null;
|
|
474
|
+
var statusValue = statusCode && _attr(statusCode, "Value");
|
|
475
|
+
if (statusValue !== "urn:oasis:names:tc:SAML:2.0:status:Success") {
|
|
476
|
+
throw new AuthError("auth-saml/bad-status",
|
|
477
|
+
"verifyResponse: SAML Status is not Success: " + statusValue);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// EncryptedAssertion (SAML 2.0 §2.5) — operator supplies their
|
|
481
|
+
// SP decryption key via vopts.spPrivateKeyPem. Decrypt the
|
|
482
|
+
// EncryptedAssertion → re-parse the cleartext → splice the
|
|
483
|
+
// resulting <Assertion> back into the document tree before
|
|
484
|
+
// signature validation. The signature check then runs against
|
|
485
|
+
// the cleartext element exactly as if the IdP had emitted it
|
|
486
|
+
// unencrypted (the IdP signs the cleartext, then encrypts).
|
|
487
|
+
var encAssertionChildren = _findAllChildren(root, "EncryptedAssertion", SAML_NS.assertion);
|
|
488
|
+
if (encAssertionChildren.length > 1) {
|
|
489
|
+
throw new AuthError("auth-saml/duplicate-encrypted-assertion",
|
|
490
|
+
"verifyResponse: Response has multiple <EncryptedAssertion> children — XSW shape refused");
|
|
491
|
+
}
|
|
492
|
+
var encAssertion = encAssertionChildren[0] || null;
|
|
493
|
+
if (encAssertion) {
|
|
494
|
+
if (typeof vopts.spPrivateKeyPem !== "string" || vopts.spPrivateKeyPem.length === 0) {
|
|
495
|
+
throw new AuthError("auth-saml/encrypted-no-sp-key",
|
|
496
|
+
"verifyResponse: Response carries EncryptedAssertion but " +
|
|
497
|
+
"vopts.spPrivateKeyPem was not supplied");
|
|
498
|
+
}
|
|
499
|
+
var decryptedAssertionXml = _decryptEncryptedAssertion(encAssertion, vopts.spPrivateKeyPem);
|
|
500
|
+
// Re-parse the cleartext + splice into root.children, replacing
|
|
501
|
+
// the EncryptedAssertion node. The cleartext XML may carry its
|
|
502
|
+
// own namespace declarations; we use the c14n parser to handle
|
|
503
|
+
// that uniformly.
|
|
504
|
+
var clearRoot;
|
|
505
|
+
try { clearRoot = c14n.parse(decryptedAssertionXml); }
|
|
506
|
+
catch (e) {
|
|
507
|
+
throw new AuthError("auth-saml/encrypted-bad-cleartext",
|
|
508
|
+
"verifyResponse: decrypted EncryptedAssertion is not parseable XML: " +
|
|
509
|
+
((e && e.message) || String(e)));
|
|
510
|
+
}
|
|
511
|
+
var clearRootLocal = clearRoot.name.split(":").pop();
|
|
512
|
+
if (clearRootLocal !== "Assertion") {
|
|
513
|
+
throw new AuthError("auth-saml/encrypted-not-assertion",
|
|
514
|
+
"verifyResponse: decrypted EncryptedAssertion content is " + clearRootLocal +
|
|
515
|
+
", expected Assertion");
|
|
516
|
+
}
|
|
517
|
+
var encIdx = root.children.indexOf(encAssertion);
|
|
518
|
+
if (encIdx !== -1) {
|
|
519
|
+
root.children.splice(encIdx, 1, clearRoot);
|
|
520
|
+
} else {
|
|
521
|
+
root.children.push(clearRoot);
|
|
522
|
+
}
|
|
523
|
+
// Re-serialize the document with the cleartext Assertion inlined
|
|
524
|
+
// so the downstream XMLDSig verifier (_verifyXmldsig) operates
|
|
525
|
+
// on a coherent envelope. The signature reference still points
|
|
526
|
+
// at the Assertion ID; since the cleartext Assertion's ID is
|
|
527
|
+
// the IdP-signed one, the digest check matches.
|
|
528
|
+
xml = Buffer.from(c14n.canonicalize(root)).toString("utf8");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Validate signature: prefer Assertion-level (most secure — the
|
|
532
|
+
// assertion is the security-critical element). Fall back to
|
|
533
|
+
// Response-level when the IdP signs the envelope only.
|
|
534
|
+
var assertionChildren = _findAllChildren(root, "Assertion", SAML_NS.assertion);
|
|
535
|
+
if (assertionChildren.length > 1) {
|
|
536
|
+
throw new AuthError("auth-saml/duplicate-assertion",
|
|
537
|
+
"verifyResponse: Response has multiple <Assertion> children — XSW shape refused");
|
|
538
|
+
}
|
|
539
|
+
var assertion = assertionChildren[0] || null;
|
|
540
|
+
if (!assertion) {
|
|
541
|
+
throw new AuthError("auth-saml/no-assertion", "verifyResponse: Response has no Assertion");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
var assertionSignature = _findChild(assertion, "Signature");
|
|
545
|
+
var responseSignature = _findChild(root, "Signature");
|
|
546
|
+
|
|
547
|
+
if (!assertionSignature && !responseSignature) {
|
|
548
|
+
throw new AuthError("auth-saml/unsigned",
|
|
549
|
+
"verifyResponse: neither Response nor Assertion is signed — SAML SP refuses unsigned responses");
|
|
550
|
+
}
|
|
551
|
+
var signed;
|
|
552
|
+
if (assertionSignature) {
|
|
553
|
+
signed = _verifyXmldsig(xml, assertionSignature, opts.idpCertPem);
|
|
554
|
+
if (signed.refId !== _attr(assertion, "ID")) {
|
|
555
|
+
throw new AuthError("auth-saml/signed-different-element",
|
|
556
|
+
"verifyResponse: assertion signature references a different element ID");
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
signed = _verifyXmldsig(xml, responseSignature, opts.idpCertPem);
|
|
560
|
+
if (signed.refId !== _attr(root, "ID")) {
|
|
561
|
+
throw new AuthError("auth-saml/signed-different-element",
|
|
562
|
+
"verifyResponse: response signature references a different element ID");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Issuer must match the configured IdP entityID
|
|
567
|
+
var issuerEl = _findChild(assertion, "Issuer", SAML_NS.assertion);
|
|
568
|
+
var issuer = _textContent(issuerEl);
|
|
569
|
+
if (issuer !== opts.idpEntityId) {
|
|
570
|
+
throw new AuthError("auth-saml/wrong-issuer",
|
|
571
|
+
"verifyResponse: Assertion Issuer \"" + issuer + "\" does not match expected \"" +
|
|
572
|
+
opts.idpEntityId + "\"");
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Subject + SubjectConfirmation — XSW: refuse duplicate <Subject>.
|
|
576
|
+
var subjectChildren = _findAllChildren(assertion, "Subject", SAML_NS.assertion);
|
|
577
|
+
if (subjectChildren.length > 1) {
|
|
578
|
+
throw new AuthError("auth-saml/duplicate-subject",
|
|
579
|
+
"verifyResponse: Assertion has multiple <Subject> children — XSW shape refused");
|
|
580
|
+
}
|
|
581
|
+
var subject = subjectChildren[0] || null;
|
|
582
|
+
if (!subject) throw new AuthError("auth-saml/no-subject", "verifyResponse: missing Subject");
|
|
583
|
+
var nameIdChildren = _findAllChildren(subject, "NameID", SAML_NS.assertion);
|
|
584
|
+
if (nameIdChildren.length > 1) {
|
|
585
|
+
throw new AuthError("auth-saml/duplicate-nameid",
|
|
586
|
+
"verifyResponse: <Subject> has multiple <NameID> children — XSW shape refused");
|
|
587
|
+
}
|
|
588
|
+
var nameIdEl = nameIdChildren[0] || null;
|
|
589
|
+
if (!nameIdEl) throw new AuthError("auth-saml/no-nameid", "verifyResponse: missing NameID");
|
|
590
|
+
var nameId = _textContent(nameIdEl);
|
|
591
|
+
var nameIdFormat = _attr(nameIdEl, "Format");
|
|
592
|
+
|
|
593
|
+
var nowSec = Math.floor((vopts.now || Date.now()) / 1000); // allow:raw-byte-literal — ms→s
|
|
594
|
+
var confirmations = _findAllChildren(subject, "SubjectConfirmation", SAML_NS.assertion);
|
|
595
|
+
var bearerOk = false;
|
|
596
|
+
var hokOk = false;
|
|
597
|
+
var hokFingerprint = null;
|
|
598
|
+
// Holder-of-Key SubjectConfirmation per SAML 2.0 Profile §3.1
|
|
599
|
+
// (urn:oasis:names:tc:SAML:2.0:cm:holder-of-key). The IdP binds
|
|
600
|
+
// the assertion to the subject's key by embedding a KeyInfo
|
|
601
|
+
// element inside SubjectConfirmationData; the SP MUST verify
|
|
602
|
+
// that the requesting party demonstrated possession of that key.
|
|
603
|
+
// Operators pass `vopts.holderOfKey: { presentedCertPem }` (the
|
|
604
|
+
// mTLS client cert pinned by b.network.tls.peerCert, or any
|
|
605
|
+
// operator-curated possession proof); we verify the embedded
|
|
606
|
+
// KeyInfo's SubjectPublicKeyInfo matches.
|
|
607
|
+
if (vopts.holderOfKey && typeof vopts.holderOfKey === "object" &&
|
|
608
|
+
typeof vopts.holderOfKey.presentedCertPem === "string") {
|
|
609
|
+
try {
|
|
610
|
+
var presentedKey = nodeCrypto.createPublicKey({
|
|
611
|
+
key: nodeCrypto.createPublicKey({
|
|
612
|
+
key: vopts.holderOfKey.presentedCertPem, format: "pem",
|
|
613
|
+
}).export({ type: "spki", format: "der" }),
|
|
614
|
+
format: "der", type: "spki",
|
|
615
|
+
});
|
|
616
|
+
hokFingerprint = nodeCrypto.createHash("sha3-512")
|
|
617
|
+
.update(presentedKey.export({ type: "spki", format: "der" })).digest("hex");
|
|
618
|
+
} catch (eHk) {
|
|
619
|
+
throw new AuthError("auth-saml/bad-hok-cert",
|
|
620
|
+
"verifyResponse: holderOfKey.presentedCertPem could not be parsed: " +
|
|
621
|
+
((eHk && eHk.message) || String(eHk)));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
for (var i = 0; i < confirmations.length; i++) {
|
|
625
|
+
var sc = confirmations[i];
|
|
626
|
+
var method = _attr(sc, "Method");
|
|
627
|
+
if (method === "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key") {
|
|
628
|
+
// SP MUST refuse HoK without operator-supplied presented key
|
|
629
|
+
// (RFC SAML-V2-Profile §3.1 — receiver of an HoK confirmation
|
|
630
|
+
// can't honor it without proving possession).
|
|
631
|
+
if (!hokFingerprint) {
|
|
632
|
+
throw new AuthError("auth-saml/hok-no-presented-key",
|
|
633
|
+
"Assertion uses holder-of-key SubjectConfirmation but " +
|
|
634
|
+
"vopts.holderOfKey.presentedCertPem was not supplied");
|
|
635
|
+
}
|
|
636
|
+
var scdHok = _findChild(sc, "SubjectConfirmationData", SAML_NS.assertion);
|
|
637
|
+
if (!scdHok) continue;
|
|
638
|
+
var keyInfo = _findChild(scdHok, "KeyInfo");
|
|
639
|
+
if (!keyInfo) {
|
|
640
|
+
throw new AuthError("auth-saml/hok-no-keyinfo",
|
|
641
|
+
"holder-of-key SubjectConfirmationData missing KeyInfo");
|
|
642
|
+
}
|
|
643
|
+
// Resolve KeyInfo → SubjectPublicKeyInfo. SAML 2.0 §2.4.1.3.1
|
|
644
|
+
// permits X509Data/X509Certificate or KeyValue/RSAKeyValue
|
|
645
|
+
// shapes; we accept X509Certificate (most common) + compute
|
|
646
|
+
// its SPKI fingerprint to compare against the presented key.
|
|
647
|
+
var x509Data = _findChild(keyInfo, "X509Data");
|
|
648
|
+
var x509CertEl = x509Data ? _findChild(x509Data, "X509Certificate") : null;
|
|
649
|
+
if (!x509CertEl) {
|
|
650
|
+
throw new AuthError("auth-saml/hok-unsupported-keyinfo",
|
|
651
|
+
"holder-of-key KeyInfo: only X509Data/X509Certificate is supported");
|
|
652
|
+
}
|
|
653
|
+
var certB64 = _textContent(x509CertEl).replace(/\s+/g, "");
|
|
654
|
+
if (!certB64) {
|
|
655
|
+
throw new AuthError("auth-saml/hok-no-cert",
|
|
656
|
+
"holder-of-key KeyInfo/X509Certificate is empty");
|
|
657
|
+
}
|
|
658
|
+
var assertionCertPem =
|
|
659
|
+
"-----BEGIN CERTIFICATE-----\n" + certB64.replace(/(.{64})/g, "$1\n") +
|
|
660
|
+
"\n-----END CERTIFICATE-----\n";
|
|
661
|
+
var assertionKey;
|
|
662
|
+
try {
|
|
663
|
+
assertionKey = nodeCrypto.createPublicKey({ key: assertionCertPem, format: "pem" });
|
|
664
|
+
} catch (eAk) {
|
|
665
|
+
throw new AuthError("auth-saml/hok-bad-cert",
|
|
666
|
+
"holder-of-key X509Certificate could not be parsed: " +
|
|
667
|
+
((eAk && eAk.message) || String(eAk)));
|
|
668
|
+
}
|
|
669
|
+
var assertionFingerprint = nodeCrypto.createHash("sha3-512")
|
|
670
|
+
.update(assertionKey.export({ type: "spki", format: "der" })).digest("hex");
|
|
671
|
+
if (!timingSafeEqual(Buffer.from(assertionFingerprint, "hex"),
|
|
672
|
+
Buffer.from(hokFingerprint, "hex"))) {
|
|
673
|
+
throw new AuthError("auth-saml/hok-key-mismatch",
|
|
674
|
+
"holder-of-key: assertion's bound key fingerprint does not match " +
|
|
675
|
+
"the presented mTLS / possession-proof cert (possession-proof failed)");
|
|
676
|
+
}
|
|
677
|
+
// HoK still requires the same time-window / Recipient checks
|
|
678
|
+
// as Bearer (Profile §3.1 incorporates §3 by reference).
|
|
679
|
+
var nbHok = _attr(scdHok, "NotBefore");
|
|
680
|
+
var noaHok = _attr(scdHok, "NotOnOrAfter");
|
|
681
|
+
if (nbHok && isFinite(Date.parse(nbHok) / 1000) && // allow:raw-byte-literal — ms→s
|
|
682
|
+
Date.parse(nbHok) / 1000 > nowSec + clockSkewSec) continue; // allow:raw-byte-literal — ms→s
|
|
683
|
+
if (noaHok && isFinite(Date.parse(noaHok) / 1000) && // allow:raw-byte-literal — ms→s
|
|
684
|
+
Date.parse(noaHok) / 1000 < nowSec - clockSkewSec) continue; // allow:raw-byte-literal — ms→s
|
|
685
|
+
var recipHok = _attr(scdHok, "Recipient");
|
|
686
|
+
if (recipHok && recipHok !== opts.assertionConsumerServiceUrl) continue;
|
|
687
|
+
hokOk = true;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
if (method !== "urn:oasis:names:tc:SAML:2.0:cm:bearer") continue;
|
|
691
|
+
var scd = _findChild(sc, "SubjectConfirmationData", SAML_NS.assertion);
|
|
692
|
+
if (!scd) continue;
|
|
693
|
+
var notOnOrAfter = _attr(scd, "NotOnOrAfter");
|
|
694
|
+
if (notOnOrAfter) {
|
|
695
|
+
var t = Date.parse(notOnOrAfter) / 1000; // allow:raw-byte-literal — ms→s
|
|
696
|
+
if (!isFinite(t) || t < nowSec - clockSkewSec) {
|
|
697
|
+
continue; // expired confirmation — try next
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
var notBefore = _attr(scd, "NotBefore");
|
|
701
|
+
if (notBefore) {
|
|
702
|
+
var nb = Date.parse(notBefore) / 1000; // allow:raw-byte-literal — ms→s
|
|
703
|
+
if (isFinite(nb) && nb > nowSec + clockSkewSec) continue;
|
|
704
|
+
}
|
|
705
|
+
var recipient = _attr(scd, "Recipient");
|
|
706
|
+
if (recipient && recipient !== opts.assertionConsumerServiceUrl) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
var inResponseTo = _attr(scd, "InResponseTo");
|
|
710
|
+
if (vopts.expectedInResponseTo) {
|
|
711
|
+
// Constant-time compare against the AuthnRequest ID the
|
|
712
|
+
// operator stored — protects against timing-based InResponseTo
|
|
713
|
+
// probing. timingSafeEqual returns false for missing /
|
|
714
|
+
// length-mismatch without leaking. (Audit 2026-05-11.)
|
|
715
|
+
if (inResponseTo === null || inResponseTo === undefined ||
|
|
716
|
+
!timingSafeEqual(inResponseTo, vopts.expectedInResponseTo)) {
|
|
717
|
+
throw new AuthError("auth-saml/bad-in-response-to",
|
|
718
|
+
"SubjectConfirmation InResponseTo does not match expected " +
|
|
719
|
+
"AuthnRequest ID (replay defense)");
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
bearerOk = true;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
if (!bearerOk && !hokOk) {
|
|
726
|
+
throw new AuthError("auth-saml/no-valid-confirmation",
|
|
727
|
+
"verifyResponse: no Bearer or holder-of-key SubjectConfirmation " +
|
|
728
|
+
"passed time/recipient/possession checks");
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Conditions
|
|
732
|
+
var conditions = _findChild(assertion, "Conditions", SAML_NS.assertion);
|
|
733
|
+
if (conditions) {
|
|
734
|
+
var cNotBefore = _attr(conditions, "NotBefore");
|
|
735
|
+
var cNotOnOrAfter = _attr(conditions, "NotOnOrAfter");
|
|
736
|
+
if (cNotBefore) {
|
|
737
|
+
var cnb = Date.parse(cNotBefore) / 1000; // allow:raw-byte-literal — ms→s
|
|
738
|
+
if (isFinite(cnb) && cnb > nowSec + clockSkewSec) {
|
|
739
|
+
throw new AuthError("auth-saml/conditions-not-yet-valid",
|
|
740
|
+
"Conditions NotBefore is in the future");
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (cNotOnOrAfter) {
|
|
744
|
+
var cnoa = Date.parse(cNotOnOrAfter) / 1000; // allow:raw-byte-literal — ms→s
|
|
745
|
+
if (isFinite(cnoa) && cnoa < nowSec - clockSkewSec) {
|
|
746
|
+
throw new AuthError("auth-saml/conditions-expired",
|
|
747
|
+
"Conditions NotOnOrAfter has passed");
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
var ar = _findChild(conditions, "AudienceRestriction", SAML_NS.assertion);
|
|
751
|
+
if (ar) {
|
|
752
|
+
var audiences = _findAllChildren(ar, "Audience", SAML_NS.assertion).map(_textContent);
|
|
753
|
+
if (audiences.indexOf(audience) === -1) {
|
|
754
|
+
throw new AuthError("auth-saml/wrong-audience",
|
|
755
|
+
"Audience \"" + audience + "\" not in assertion's AudienceRestriction (got " +
|
|
756
|
+
JSON.stringify(audiences) + ")");
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// AuthnStatement.SessionIndex (for SLO)
|
|
762
|
+
var sessionIndex = null;
|
|
763
|
+
var authnStmt = _findChild(assertion, "AuthnStatement", SAML_NS.assertion);
|
|
764
|
+
if (authnStmt) {
|
|
765
|
+
sessionIndex = _attr(authnStmt, "SessionIndex");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// AttributeStatement → flat map
|
|
769
|
+
var attributes = {};
|
|
770
|
+
var attrStmt = _findChild(assertion, "AttributeStatement", SAML_NS.assertion);
|
|
771
|
+
if (attrStmt) {
|
|
772
|
+
var attrEls = _findAllChildren(attrStmt, "Attribute", SAML_NS.assertion);
|
|
773
|
+
for (var ai = 0; ai < attrEls.length; ai++) {
|
|
774
|
+
var n = _attr(attrEls[ai], "Name");
|
|
775
|
+
var values = _findAllChildren(attrEls[ai], "AttributeValue", SAML_NS.assertion).map(_textContent);
|
|
776
|
+
attributes[n] = values.length === 1 ? values[0] : values;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
_emitAudit("response_verified", "success", { issuer: issuer });
|
|
781
|
+
_emitMetric("response-verified");
|
|
782
|
+
return {
|
|
783
|
+
nameId: nameId,
|
|
784
|
+
nameIdFormat: nameIdFormat,
|
|
785
|
+
sessionIndex: sessionIndex,
|
|
786
|
+
attributes: attributes,
|
|
787
|
+
audience: audience,
|
|
788
|
+
inResponseTo: bearerOk ? _attr(_findChild(_findChild(subject, "SubjectConfirmation", SAML_NS.assertion),
|
|
789
|
+
"SubjectConfirmationData", SAML_NS.assertion), "InResponseTo") : null,
|
|
790
|
+
issuer: issuer,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* @primitive b.auth.saml.sp.metadata
|
|
796
|
+
* @signature b.auth.saml.sp.metadata(metaOpts?)
|
|
797
|
+
* @since 0.8.62
|
|
798
|
+
*
|
|
799
|
+
* Emit the SP's `EntityDescriptor` XML for IdP-side configuration.
|
|
800
|
+
* Operators serve this verbatim at /saml/metadata.
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* app.get("/saml/metadata", function (req, res) {
|
|
804
|
+
* res.setHeader("Content-Type", "application/samlmetadata+xml");
|
|
805
|
+
* res.end(sp.metadata());
|
|
806
|
+
* });
|
|
807
|
+
*/
|
|
808
|
+
function metadata(metaOpts) {
|
|
809
|
+
// RFC 3741 attr/text escaping for operator-supplied URLs / IDs —
|
|
810
|
+
// same audit-finding shape as buildAuthnRequest above.
|
|
811
|
+
metaOpts = metaOpts || {};
|
|
812
|
+
var c14n = xmlC14n();
|
|
813
|
+
// v0.10.16 — operator can supply SingleLogoutService URL +
|
|
814
|
+
// additional ACS bindings (HTTP-Redirect / HTTP-Artifact). The
|
|
815
|
+
// metadata XML now reflects what the SP actually supports.
|
|
816
|
+
var sloUrl = metaOpts.singleLogoutServiceUrl || opts.singleLogoutServiceUrl;
|
|
817
|
+
var sloXml = "";
|
|
818
|
+
if (sloUrl) {
|
|
819
|
+
sloXml =
|
|
820
|
+
"<md:SingleLogoutService " +
|
|
821
|
+
"Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" " +
|
|
822
|
+
"Location=\"" + c14n.escapeAttrValue(sloUrl) + "\"/>" +
|
|
823
|
+
"<md:SingleLogoutService " +
|
|
824
|
+
"Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" " +
|
|
825
|
+
"Location=\"" + c14n.escapeAttrValue(sloUrl) + "\"/>";
|
|
826
|
+
}
|
|
827
|
+
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
|
828
|
+
"<md:EntityDescriptor xmlns:md=\"" + SAML_NS.metadata + "\" entityID=\"" + c14n.escapeAttrValue(opts.entityId) + "\">" +
|
|
829
|
+
"<md:SPSSODescriptor protocolSupportEnumeration=\"" + SAML_NS.protocol + "\" " +
|
|
830
|
+
"AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"true\">" +
|
|
831
|
+
sloXml +
|
|
832
|
+
"<md:AssertionConsumerService " +
|
|
833
|
+
"Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" " +
|
|
834
|
+
"Location=\"" + c14n.escapeAttrValue(opts.assertionConsumerServiceUrl) + "\" index=\"0\"/>" +
|
|
835
|
+
"</md:SPSSODescriptor>" +
|
|
836
|
+
"</md:EntityDescriptor>";
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// ---- v0.10.16 — Single Logout (RFC SAML Bindings §3.4 HTTP-Redirect) ----
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* @primitive b.auth.saml.sp.buildLogoutRequest
|
|
843
|
+
* @signature b.auth.saml.sp.buildLogoutRequest(opts)
|
|
844
|
+
* @since 0.10.16
|
|
845
|
+
* @status stable
|
|
846
|
+
*
|
|
847
|
+
* Build a SAML 2.0 LogoutRequest XML + the URL-safe deflate-base64
|
|
848
|
+
* encoding for the HTTP-Redirect binding. When `signingKey` /
|
|
849
|
+
* `signingAlg` are supplied, computes the binding-§3.4.4.1
|
|
850
|
+
* canonical query-string signature so the IdP can verify the
|
|
851
|
+
* request originated from a trusted SP. The signature is computed
|
|
852
|
+
* over `SAMLRequest=<v>&[RelayState=<v>&]SigAlg=<v>` in that exact
|
|
853
|
+
* order (no re-sorting per the spec).
|
|
854
|
+
*
|
|
855
|
+
* @opts
|
|
856
|
+
* nameId: string, // user's NameID from the original AuthnResponse
|
|
857
|
+
* nameIdFormat: string, // optional NameID Format URI
|
|
858
|
+
* sessionIndex: string, // SessionIndex from the original Assertion AuthnStatement
|
|
859
|
+
* relayState: string, // optional opaque blob round-tripped to LogoutResponse
|
|
860
|
+
* signingKey: Uint8Array | string | KeyObject, // PQC private key (b.pqcSoftware.ml_dsa_*.keygen()) for ML-DSA;
|
|
861
|
+
* // PEM string or node:crypto KeyObject for RSA / ECDSA / Ed25519
|
|
862
|
+
* signingAlg: "rsa-sha256" | "rsa-sha384" | "rsa-sha512" |
|
|
863
|
+
* "ecdsa-sha256" | "ecdsa-sha384" | "ecdsa-sha512" |
|
|
864
|
+
* "ed25519" | "ml-dsa-65" | "ml-dsa-87", // default omitted → unsigned
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* var lr = sp.buildLogoutRequest({
|
|
868
|
+
* nameId: "alice@idp", sessionIndex: "_session-9876",
|
|
869
|
+
* signingKey: kp.secretKey, signingAlg: "ml-dsa-65",
|
|
870
|
+
* });
|
|
871
|
+
* res.statusCode = 302;
|
|
872
|
+
* res.setHeader("Location", lr.redirectUrl);
|
|
873
|
+
*/
|
|
874
|
+
function buildLogoutRequest(bopts) {
|
|
875
|
+
bopts = validateOpts.requireObject(bopts, "auth.saml.sp.buildLogoutRequest", AuthError, "auth-saml/bad-opts");
|
|
876
|
+
validateOpts(bopts, ["nameId", "nameIdFormat", "sessionIndex", "relayState",
|
|
877
|
+
"signingKey", "signingAlg", "idpSloUrl"],
|
|
878
|
+
"auth.saml.sp.buildLogoutRequest");
|
|
879
|
+
validateOpts.requireNonEmptyString(bopts.nameId, "nameId", AuthError, "auth-saml/no-nameid");
|
|
880
|
+
var idpSloUrl = bopts.idpSloUrl || opts.idpSloUrl || opts.idpSsoUrl;
|
|
881
|
+
if (typeof idpSloUrl !== "string" || idpSloUrl.length === 0) {
|
|
882
|
+
throw new AuthError("auth-saml/no-idp-slo",
|
|
883
|
+
"buildLogoutRequest: opts.idpSloUrl (or sp.create's opts.idpSloUrl) required");
|
|
884
|
+
}
|
|
885
|
+
var id = "_" + generateToken(20); // allow:raw-byte-literal — 20-byte SAML ID token
|
|
886
|
+
var issueInstant = new Date().toISOString();
|
|
887
|
+
var c14n = xmlC14n();
|
|
888
|
+
var nameIdFormatAttr = bopts.nameIdFormat
|
|
889
|
+
? " Format=\"" + c14n.escapeAttrValue(bopts.nameIdFormat) + "\""
|
|
890
|
+
: "";
|
|
891
|
+
var sessionIndexXml = bopts.sessionIndex
|
|
892
|
+
? "<samlp:SessionIndex>" + c14n.escapeText(bopts.sessionIndex) + "</samlp:SessionIndex>"
|
|
893
|
+
: "";
|
|
894
|
+
var xml =
|
|
895
|
+
"<samlp:LogoutRequest xmlns:samlp=\"" + SAML_NS.protocol + "\" " +
|
|
896
|
+
"xmlns:saml=\"" + SAML_NS.assertion + "\" " +
|
|
897
|
+
"ID=\"" + id + "\" " +
|
|
898
|
+
"Version=\"2.0\" " +
|
|
899
|
+
"IssueInstant=\"" + issueInstant + "\" " +
|
|
900
|
+
"Destination=\"" + c14n.escapeAttrValue(idpSloUrl) + "\">" +
|
|
901
|
+
"<saml:Issuer>" + c14n.escapeText(opts.entityId) + "</saml:Issuer>" +
|
|
902
|
+
"<saml:NameID" + nameIdFormatAttr + ">" + c14n.escapeText(bopts.nameId) + "</saml:NameID>" +
|
|
903
|
+
sessionIndexXml +
|
|
904
|
+
"</samlp:LogoutRequest>";
|
|
905
|
+
var deflated = zlib.deflateRawSync(Buffer.from(xml, "utf8"));
|
|
906
|
+
var samlRequest = deflated.toString("base64");
|
|
907
|
+
var query = "SAMLRequest=" + encodeURIComponent(samlRequest);
|
|
908
|
+
if (bopts.relayState) {
|
|
909
|
+
query += "&RelayState=" + encodeURIComponent(bopts.relayState);
|
|
910
|
+
}
|
|
911
|
+
// Signature path — per SAML Bindings §3.4.4.1 the signature is
|
|
912
|
+
// computed over the URL-encoded query string with the SigAlg
|
|
913
|
+
// parameter appended (no Signature parameter, no re-sorting).
|
|
914
|
+
if (bopts.signingKey || bopts.signingAlg) {
|
|
915
|
+
var sigAlgUrn = _sigAlgUrn(bopts.signingAlg);
|
|
916
|
+
if (!sigAlgUrn) {
|
|
917
|
+
throw new AuthError("auth-saml/bad-signing-alg",
|
|
918
|
+
"buildLogoutRequest: signingAlg must be one of " +
|
|
919
|
+
"'rsa-sha256' / 'rsa-sha384' / 'rsa-sha512' / " +
|
|
920
|
+
"'ecdsa-sha256' / 'ecdsa-sha384' / 'ecdsa-sha512' / " +
|
|
921
|
+
"'ed25519' (W3C XMLDSig Core 1.1 + RFC 9231) or " +
|
|
922
|
+
"'ml-dsa-65' / 'ml-dsa-87' (framework-experimental — " +
|
|
923
|
+
"urn:blamejs:experimental:saml-sig-alg:*)");
|
|
924
|
+
}
|
|
925
|
+
var isPqc = bopts.signingAlg === "ml-dsa-65" || bopts.signingAlg === "ml-dsa-87";
|
|
926
|
+
if (isPqc && !(bopts.signingKey instanceof Uint8Array)) {
|
|
927
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
928
|
+
"buildLogoutRequest: signingKey for " + bopts.signingAlg + " must be a Uint8Array");
|
|
929
|
+
}
|
|
930
|
+
if (!isPqc && bopts.signingAlg !== "ed25519" &&
|
|
931
|
+
typeof bopts.signingKey !== "string" &&
|
|
932
|
+
!(bopts.signingKey && typeof bopts.signingKey === "object" &&
|
|
933
|
+
bopts.signingKey.type === "private")) {
|
|
934
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
935
|
+
"buildLogoutRequest: signingKey for classical " + bopts.signingAlg +
|
|
936
|
+
" must be a PEM string or node:crypto KeyObject");
|
|
937
|
+
}
|
|
938
|
+
query += "&SigAlg=" + encodeURIComponent(sigAlgUrn.urn);
|
|
939
|
+
var sigBytes = sigAlgUrn.sign(Buffer.from(query, "utf8"), bopts.signingKey);
|
|
940
|
+
query += "&Signature=" + encodeURIComponent(Buffer.from(sigBytes).toString("base64"));
|
|
941
|
+
}
|
|
942
|
+
var url = idpSloUrl + (idpSloUrl.indexOf("?") === -1 ? "?" : "&") + query;
|
|
943
|
+
_emitAudit("logoutrequest_built", "success", {
|
|
944
|
+
id: id, idp: opts.idpEntityId, signed: !!bopts.signingKey,
|
|
945
|
+
});
|
|
946
|
+
return { id: id, redirectUrl: url, raw: xml };
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* @primitive b.auth.saml.sp.parseLogoutRequest
|
|
951
|
+
* @signature b.auth.saml.sp.parseLogoutRequest(samlRequestB64, vopts?)
|
|
952
|
+
* @since 0.10.16
|
|
953
|
+
* @status stable
|
|
954
|
+
*
|
|
955
|
+
* Parse an inbound LogoutRequest (IdP-initiated SLO). Returns
|
|
956
|
+
* `{ id, nameId, nameIdFormat, sessionIndex, issuer, issueInstant }`.
|
|
957
|
+
* When `vopts.idpVerifyKey` is supplied with `vopts.queryString`,
|
|
958
|
+
* verifies the HTTP-Redirect-binding signature against the IdP key.
|
|
959
|
+
*
|
|
960
|
+
* @opts
|
|
961
|
+
* queryString: string, // raw query string (everything after `?` in the redirect URL)
|
|
962
|
+
* idpVerifyKey: Uint8Array, // IdP's PQC public key
|
|
963
|
+
* idpVerifyAlg: "ml-dsa-65" | "ml-dsa-87" | "ed25519",
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* var req = sp.parseLogoutRequest(req.query.SAMLRequest, {
|
|
967
|
+
* queryString: req.url.split("?")[1],
|
|
968
|
+
* idpVerifyKey: idpKp.publicKey,
|
|
969
|
+
* idpVerifyAlg: "ml-dsa-65",
|
|
970
|
+
* });
|
|
971
|
+
*/
|
|
972
|
+
function parseLogoutRequest(samlRequestB64, vopts) {
|
|
973
|
+
vopts = vopts || {};
|
|
974
|
+
if (typeof samlRequestB64 !== "string" || samlRequestB64.length === 0) {
|
|
975
|
+
throw new AuthError("auth-saml/no-saml-request",
|
|
976
|
+
"parseLogoutRequest: samlRequestB64 must be a non-empty string");
|
|
977
|
+
}
|
|
978
|
+
var xml;
|
|
979
|
+
try {
|
|
980
|
+
var deflated = Buffer.from(samlRequestB64, "base64");
|
|
981
|
+
xml = zlib.inflateRawSync(deflated, { maxOutputLength: 1024 * 1024 }).toString("utf8"); // allow:raw-byte-literal — 1 MiB max SAMLRequest decompressed
|
|
982
|
+
} catch (e) {
|
|
983
|
+
throw new AuthError("auth-saml/bad-saml-request",
|
|
984
|
+
"parseLogoutRequest: inflate failed: " + ((e && e.message) || String(e)));
|
|
985
|
+
}
|
|
986
|
+
// Verify the redirect-binding signature when an IdP key is supplied.
|
|
987
|
+
if (vopts.idpVerifyKey) {
|
|
988
|
+
if (typeof vopts.queryString !== "string") {
|
|
989
|
+
throw new AuthError("auth-saml/no-query-string",
|
|
990
|
+
"parseLogoutRequest: idpVerifyKey requires queryString (raw URL query)");
|
|
991
|
+
}
|
|
992
|
+
var sigAlgUrn = _sigAlgUrn(vopts.idpVerifyAlg);
|
|
993
|
+
if (!sigAlgUrn) {
|
|
994
|
+
throw new AuthError("auth-saml/bad-verify-alg",
|
|
995
|
+
"parseLogoutRequest: idpVerifyAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519'");
|
|
996
|
+
}
|
|
997
|
+
var parts = vopts.queryString.split("&");
|
|
998
|
+
var sigValue = null;
|
|
999
|
+
var signedPortion = [];
|
|
1000
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
1001
|
+
if (parts[i].indexOf("Signature=") === 0) {
|
|
1002
|
+
sigValue = decodeURIComponent(parts[i].slice("Signature=".length));
|
|
1003
|
+
} else {
|
|
1004
|
+
signedPortion.push(parts[i]);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (!sigValue) {
|
|
1008
|
+
throw new AuthError("auth-saml/no-signature",
|
|
1009
|
+
"parseLogoutRequest: queryString lacks Signature parameter");
|
|
1010
|
+
}
|
|
1011
|
+
var sigBytes = Buffer.from(sigValue, "base64");
|
|
1012
|
+
var msgBytes = Buffer.from(signedPortion.join("&"), "utf8");
|
|
1013
|
+
var ok;
|
|
1014
|
+
try { ok = sigAlgUrn.verify(new Uint8Array(sigBytes), new Uint8Array(msgBytes), vopts.idpVerifyKey); }
|
|
1015
|
+
catch (eV) {
|
|
1016
|
+
throw new AuthError("auth-saml/verify-threw",
|
|
1017
|
+
"parseLogoutRequest: signature verify threw: " + ((eV && eV.message) || String(eV)));
|
|
1018
|
+
}
|
|
1019
|
+
if (!ok) {
|
|
1020
|
+
throw new AuthError("auth-saml/bad-signature",
|
|
1021
|
+
"parseLogoutRequest: HTTP-Redirect signature does not verify against idpVerifyKey");
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
// Parse the inflated XML.
|
|
1025
|
+
var c14n = xmlC14n();
|
|
1026
|
+
var root = c14n.parse(xml);
|
|
1027
|
+
var rootLocal = root.name.indexOf(":") !== -1 ? root.name.split(":").pop() : root.name;
|
|
1028
|
+
if (rootLocal !== "LogoutRequest") {
|
|
1029
|
+
throw new AuthError("auth-saml/not-logout-request",
|
|
1030
|
+
"parseLogoutRequest: root element is " + rootLocal + ", expected LogoutRequest");
|
|
1031
|
+
}
|
|
1032
|
+
var nameIdEl = _findChild(root, "NameID", SAML_NS.assertion);
|
|
1033
|
+
if (!nameIdEl) {
|
|
1034
|
+
throw new AuthError("auth-saml/no-nameid",
|
|
1035
|
+
"parseLogoutRequest: missing NameID");
|
|
1036
|
+
}
|
|
1037
|
+
var issuerEl = _findChild(root, "Issuer", SAML_NS.assertion);
|
|
1038
|
+
var sessionIndexEl = _findChild(root, "SessionIndex", SAML_NS.protocol);
|
|
1039
|
+
return {
|
|
1040
|
+
id: _attr(root, "ID"),
|
|
1041
|
+
issueInstant: _attr(root, "IssueInstant"),
|
|
1042
|
+
destination: _attr(root, "Destination"),
|
|
1043
|
+
nameId: _textContent(nameIdEl),
|
|
1044
|
+
nameIdFormat: _attr(nameIdEl, "Format"),
|
|
1045
|
+
sessionIndex: sessionIndexEl ? _textContent(sessionIndexEl) : null,
|
|
1046
|
+
issuer: issuerEl ? _textContent(issuerEl) : null,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* @primitive b.auth.saml.sp.buildLogoutResponse
|
|
1052
|
+
* @signature b.auth.saml.sp.buildLogoutResponse(opts)
|
|
1053
|
+
* @since 0.10.16
|
|
1054
|
+
* @status stable
|
|
1055
|
+
*
|
|
1056
|
+
* Build a SAML 2.0 LogoutResponse to an IdP-initiated LogoutRequest.
|
|
1057
|
+
* Status defaults to `urn:oasis:names:tc:SAML:2.0:status:Success`.
|
|
1058
|
+
* Same HTTP-Redirect binding + optional canonical-query signature
|
|
1059
|
+
* as buildLogoutRequest.
|
|
1060
|
+
*
|
|
1061
|
+
* @opts
|
|
1062
|
+
* inResponseTo: string, // required — LogoutRequest ID being responded to
|
|
1063
|
+
* destination: string, // required — IdP SLO endpoint URL the response posts to
|
|
1064
|
+
* statusCode: string, // optional — SAML status URI; default Success
|
|
1065
|
+
* relayState: string, // optional — opaque blob from the matching LogoutRequest
|
|
1066
|
+
* signingKey: Uint8Array, // PQC private key (b.pqcSoftware.ml_dsa_*.keygen())
|
|
1067
|
+
* signingAlg: "ml-dsa-65" | "ml-dsa-87" | "ed25519", // default omitted → unsigned
|
|
1068
|
+
*
|
|
1069
|
+
* @example
|
|
1070
|
+
* var resp = sp.buildLogoutResponse({
|
|
1071
|
+
* inResponseTo: incoming.id,
|
|
1072
|
+
* destination: "https://idp.example/slo",
|
|
1073
|
+
* signingKey: kp.secretKey,
|
|
1074
|
+
* signingAlg: "ml-dsa-65",
|
|
1075
|
+
* });
|
|
1076
|
+
* res.writeHead(302, { Location: resp.redirectUrl });
|
|
1077
|
+
*/
|
|
1078
|
+
function buildLogoutResponse(bopts) {
|
|
1079
|
+
bopts = validateOpts.requireObject(bopts, "auth.saml.sp.buildLogoutResponse", AuthError, "auth-saml/bad-opts");
|
|
1080
|
+
validateOpts(bopts, ["inResponseTo", "destination", "statusCode", "relayState",
|
|
1081
|
+
"signingKey", "signingAlg"],
|
|
1082
|
+
"auth.saml.sp.buildLogoutResponse");
|
|
1083
|
+
validateOpts.requireNonEmptyString(bopts.inResponseTo, "inResponseTo", AuthError, "auth-saml/no-in-response-to");
|
|
1084
|
+
validateOpts.requireNonEmptyString(bopts.destination, "destination", AuthError, "auth-saml/no-destination");
|
|
1085
|
+
var statusCode = bopts.statusCode || "urn:oasis:names:tc:SAML:2.0:status:Success";
|
|
1086
|
+
var id = "_" + generateToken(20); // allow:raw-byte-literal — 20-byte SAML ID token
|
|
1087
|
+
var issueInstant = new Date().toISOString();
|
|
1088
|
+
var c14n = xmlC14n();
|
|
1089
|
+
var xml =
|
|
1090
|
+
"<samlp:LogoutResponse xmlns:samlp=\"" + SAML_NS.protocol + "\" " +
|
|
1091
|
+
"xmlns:saml=\"" + SAML_NS.assertion + "\" " +
|
|
1092
|
+
"ID=\"" + id + "\" " +
|
|
1093
|
+
"Version=\"2.0\" " +
|
|
1094
|
+
"IssueInstant=\"" + issueInstant + "\" " +
|
|
1095
|
+
"InResponseTo=\"" + c14n.escapeAttrValue(bopts.inResponseTo) + "\" " +
|
|
1096
|
+
"Destination=\"" + c14n.escapeAttrValue(bopts.destination) + "\">" +
|
|
1097
|
+
"<saml:Issuer>" + c14n.escapeText(opts.entityId) + "</saml:Issuer>" +
|
|
1098
|
+
"<samlp:Status><samlp:StatusCode Value=\"" + c14n.escapeAttrValue(statusCode) + "\"/></samlp:Status>" +
|
|
1099
|
+
"</samlp:LogoutResponse>";
|
|
1100
|
+
var deflated = zlib.deflateRawSync(Buffer.from(xml, "utf8"));
|
|
1101
|
+
var samlResponse = deflated.toString("base64");
|
|
1102
|
+
var query = "SAMLResponse=" + encodeURIComponent(samlResponse);
|
|
1103
|
+
if (bopts.relayState) {
|
|
1104
|
+
query += "&RelayState=" + encodeURIComponent(bopts.relayState);
|
|
1105
|
+
}
|
|
1106
|
+
if (bopts.signingKey || bopts.signingAlg) {
|
|
1107
|
+
var sigAlgUrn = _sigAlgUrn(bopts.signingAlg);
|
|
1108
|
+
if (!sigAlgUrn) {
|
|
1109
|
+
throw new AuthError("auth-saml/bad-signing-alg",
|
|
1110
|
+
"buildLogoutResponse: signingAlg must be one of " +
|
|
1111
|
+
"'rsa-sha256' / 'rsa-sha384' / 'rsa-sha512' / " +
|
|
1112
|
+
"'ecdsa-sha256' / 'ecdsa-sha384' / 'ecdsa-sha512' / " +
|
|
1113
|
+
"'ed25519' (W3C XMLDSig Core 1.1 + RFC 9231) or " +
|
|
1114
|
+
"'ml-dsa-65' / 'ml-dsa-87' (framework-experimental — " +
|
|
1115
|
+
"urn:blamejs:experimental:saml-sig-alg:*)");
|
|
1116
|
+
}
|
|
1117
|
+
var isPqcResp = bopts.signingAlg === "ml-dsa-65" || bopts.signingAlg === "ml-dsa-87";
|
|
1118
|
+
if (isPqcResp && !(bopts.signingKey instanceof Uint8Array)) {
|
|
1119
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
1120
|
+
"buildLogoutResponse: signingKey for " + bopts.signingAlg + " must be a Uint8Array");
|
|
1121
|
+
}
|
|
1122
|
+
if (!isPqcResp && bopts.signingAlg !== "ed25519" &&
|
|
1123
|
+
typeof bopts.signingKey !== "string" &&
|
|
1124
|
+
!(bopts.signingKey && typeof bopts.signingKey === "object" &&
|
|
1125
|
+
bopts.signingKey.type === "private")) {
|
|
1126
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
1127
|
+
"buildLogoutResponse: signingKey for classical " + bopts.signingAlg +
|
|
1128
|
+
" must be a PEM string or node:crypto KeyObject");
|
|
1129
|
+
}
|
|
1130
|
+
query += "&SigAlg=" + encodeURIComponent(sigAlgUrn.urn);
|
|
1131
|
+
var sigBytes = sigAlgUrn.sign(Buffer.from(query, "utf8"), bopts.signingKey);
|
|
1132
|
+
query += "&Signature=" + encodeURIComponent(Buffer.from(sigBytes).toString("base64"));
|
|
1133
|
+
}
|
|
1134
|
+
var url = bopts.destination + (bopts.destination.indexOf("?") === -1 ? "?" : "&") + query;
|
|
1135
|
+
_emitAudit("logoutresponse_built", "success", { id: id, inResponseTo: bopts.inResponseTo });
|
|
1136
|
+
return { id: id, redirectUrl: url, raw: xml };
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* @primitive b.auth.saml.sp.parseLogoutResponse
|
|
1141
|
+
* @signature b.auth.saml.sp.parseLogoutResponse(samlResponseB64, vopts?)
|
|
1142
|
+
* @since 0.10.16
|
|
1143
|
+
* @status stable
|
|
1144
|
+
*
|
|
1145
|
+
* Parse + verify an inbound SAML 2.0 LogoutResponse (the IdP's
|
|
1146
|
+
* acknowledgement of a previously-issued LogoutRequest). Returns
|
|
1147
|
+
* `{ id, inResponseTo, statusCode, issuer, success }` where
|
|
1148
|
+
* `success` is true when `statusCode` equals the spec's success
|
|
1149
|
+
* URN. When `vopts.idpVerifyKey` is supplied, verifies the
|
|
1150
|
+
* redirect-binding signature against the IdP key (same shape as
|
|
1151
|
+
* parseLogoutRequest).
|
|
1152
|
+
*
|
|
1153
|
+
* @opts
|
|
1154
|
+
* queryString: string, // raw URL query (everything past `?`)
|
|
1155
|
+
* idpVerifyKey: Uint8Array,
|
|
1156
|
+
* idpVerifyAlg: "ml-dsa-65" | "ml-dsa-87" | "ed25519",
|
|
1157
|
+
* expectedInResponseTo: string, // optional — refuses on mismatch
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* var resp = sp.parseLogoutResponse(req.query.SAMLResponse, {
|
|
1161
|
+
* queryString: req.url.split("?")[1],
|
|
1162
|
+
* idpVerifyKey: idpPub,
|
|
1163
|
+
* idpVerifyAlg: "ml-dsa-65",
|
|
1164
|
+
* expectedInResponseTo: storedLogoutRequestId,
|
|
1165
|
+
* });
|
|
1166
|
+
* resp.success; // → true on Success status
|
|
1167
|
+
*/
|
|
1168
|
+
function parseLogoutResponse(samlResponseB64, vopts) {
|
|
1169
|
+
vopts = vopts || {};
|
|
1170
|
+
if (typeof samlResponseB64 !== "string" || samlResponseB64.length === 0) {
|
|
1171
|
+
throw new AuthError("auth-saml/no-saml-response",
|
|
1172
|
+
"parseLogoutResponse: samlResponseB64 must be a non-empty string");
|
|
1173
|
+
}
|
|
1174
|
+
var xml;
|
|
1175
|
+
try {
|
|
1176
|
+
var deflated = Buffer.from(samlResponseB64, "base64");
|
|
1177
|
+
xml = zlib.inflateRawSync(deflated, { maxOutputLength: 1024 * 1024 }).toString("utf8"); // allow:raw-byte-literal — 1 MiB max SAMLResponse decompressed
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
throw new AuthError("auth-saml/bad-saml-response",
|
|
1180
|
+
"parseLogoutResponse: inflate failed: " + ((e && e.message) || String(e)));
|
|
1181
|
+
}
|
|
1182
|
+
if (vopts.idpVerifyKey) {
|
|
1183
|
+
if (typeof vopts.queryString !== "string") {
|
|
1184
|
+
throw new AuthError("auth-saml/no-query-string",
|
|
1185
|
+
"parseLogoutResponse: idpVerifyKey requires queryString");
|
|
1186
|
+
}
|
|
1187
|
+
var sigAlgUrn = _sigAlgUrn(vopts.idpVerifyAlg);
|
|
1188
|
+
if (!sigAlgUrn) {
|
|
1189
|
+
throw new AuthError("auth-saml/bad-verify-alg",
|
|
1190
|
+
"parseLogoutResponse: idpVerifyAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519'");
|
|
1191
|
+
}
|
|
1192
|
+
var parts = vopts.queryString.split("&");
|
|
1193
|
+
var sigValue = null;
|
|
1194
|
+
var signedPortion = [];
|
|
1195
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
1196
|
+
if (parts[i].indexOf("Signature=") === 0) {
|
|
1197
|
+
sigValue = decodeURIComponent(parts[i].slice("Signature=".length));
|
|
1198
|
+
} else {
|
|
1199
|
+
signedPortion.push(parts[i]);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (!sigValue) {
|
|
1203
|
+
throw new AuthError("auth-saml/no-signature",
|
|
1204
|
+
"parseLogoutResponse: queryString lacks Signature parameter");
|
|
1205
|
+
}
|
|
1206
|
+
var ok;
|
|
1207
|
+
try {
|
|
1208
|
+
ok = sigAlgUrn.verify(new Uint8Array(Buffer.from(sigValue, "base64")),
|
|
1209
|
+
new Uint8Array(Buffer.from(signedPortion.join("&"), "utf8")),
|
|
1210
|
+
vopts.idpVerifyKey);
|
|
1211
|
+
} catch (eV) {
|
|
1212
|
+
throw new AuthError("auth-saml/verify-threw",
|
|
1213
|
+
"parseLogoutResponse: signature verify threw: " + ((eV && eV.message) || String(eV)));
|
|
1214
|
+
}
|
|
1215
|
+
if (!ok) {
|
|
1216
|
+
throw new AuthError("auth-saml/bad-signature",
|
|
1217
|
+
"parseLogoutResponse: HTTP-Redirect signature does not verify against idpVerifyKey");
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
var c14n = xmlC14n();
|
|
1221
|
+
var root = c14n.parse(xml);
|
|
1222
|
+
var rootLocal = root.name.indexOf(":") !== -1 ? root.name.split(":").pop() : root.name;
|
|
1223
|
+
if (rootLocal !== "LogoutResponse") {
|
|
1224
|
+
throw new AuthError("auth-saml/not-logout-response",
|
|
1225
|
+
"parseLogoutResponse: root element is " + rootLocal + ", expected LogoutResponse");
|
|
1226
|
+
}
|
|
1227
|
+
var inResponseTo = _attr(root, "InResponseTo");
|
|
1228
|
+
if (vopts.expectedInResponseTo && inResponseTo !== vopts.expectedInResponseTo) {
|
|
1229
|
+
throw new AuthError("auth-saml/inresponseto-mismatch",
|
|
1230
|
+
"parseLogoutResponse: InResponseTo '" + inResponseTo + "' != expected '" +
|
|
1231
|
+
vopts.expectedInResponseTo + "'");
|
|
1232
|
+
}
|
|
1233
|
+
var statusEl = _findChild(root, "Status", SAML_NS.protocol);
|
|
1234
|
+
var statusCodeEl = statusEl && _findChild(statusEl, "StatusCode", SAML_NS.protocol);
|
|
1235
|
+
var statusCode = statusCodeEl ? _attr(statusCodeEl, "Value") : null;
|
|
1236
|
+
var issuerEl = _findChild(root, "Issuer", SAML_NS.assertion);
|
|
1237
|
+
return {
|
|
1238
|
+
id: _attr(root, "ID"),
|
|
1239
|
+
inResponseTo: inResponseTo,
|
|
1240
|
+
destination: _attr(root, "Destination"),
|
|
1241
|
+
statusCode: statusCode,
|
|
1242
|
+
success: statusCode === "urn:oasis:names:tc:SAML:2.0:status:Success",
|
|
1243
|
+
issuer: issuerEl ? _textContent(issuerEl) : null,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// ---- v0.10.16 — SLO HTTP-POST binding (SAML Bindings §3.5) ----
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* @primitive b.auth.saml.sp.buildLogoutRequestPost
|
|
1251
|
+
* @signature b.auth.saml.sp.buildLogoutRequestPost(opts)
|
|
1252
|
+
* @since 0.10.16
|
|
1253
|
+
* @status stable
|
|
1254
|
+
*
|
|
1255
|
+
* HTTP-POST variant of buildLogoutRequest. Returns the
|
|
1256
|
+
* base64-encoded SAMLRequest body for the IdP's /slo POST endpoint
|
|
1257
|
+
* along with an embedded XMLDSig-Enveloped signature (when
|
|
1258
|
+
* signingKey is supplied). The signature is computed over the
|
|
1259
|
+
* canonical SignedInfo element per XMLDSig §4.5 — the referenced
|
|
1260
|
+
* LogoutRequest is canonicalized via exclusive-c14n, SHA3-512
|
|
1261
|
+
* digested, and that digest goes into the Reference's DigestValue
|
|
1262
|
+
* before SignedInfo itself is canonicalized + signed.
|
|
1263
|
+
*
|
|
1264
|
+
* @opts
|
|
1265
|
+
* nameId, nameIdFormat, sessionIndex, relayState — same as buildLogoutRequest
|
|
1266
|
+
* signingKey, signingAlg — PQC ml-dsa-65 / ml-dsa-87 (Ed25519 also
|
|
1267
|
+
* accepted; URN identifies the alg)
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* var lr = sp.buildLogoutRequestPost({
|
|
1271
|
+
* nameId: "alice@idp", signingKey: kp.secretKey, signingAlg: "ml-dsa-65",
|
|
1272
|
+
* });
|
|
1273
|
+
* res.statusCode = 200;
|
|
1274
|
+
* res.setHeader("Content-Type", "text/html");
|
|
1275
|
+
* res.end(lr.formHtml); // auto-POSTs SAMLRequest to lr.action
|
|
1276
|
+
*/
|
|
1277
|
+
function buildLogoutRequestPost(bopts) {
|
|
1278
|
+
bopts = validateOpts.requireObject(bopts, "auth.saml.sp.buildLogoutRequestPost",
|
|
1279
|
+
AuthError, "auth-saml/bad-opts");
|
|
1280
|
+
validateOpts(bopts, ["nameId", "nameIdFormat", "sessionIndex", "relayState",
|
|
1281
|
+
"signingKey", "signingAlg", "idpSloUrl"],
|
|
1282
|
+
"auth.saml.sp.buildLogoutRequestPost");
|
|
1283
|
+
validateOpts.requireNonEmptyString(bopts.nameId, "nameId", AuthError, "auth-saml/no-nameid");
|
|
1284
|
+
var idpSloUrl = bopts.idpSloUrl || opts.idpSloUrl || opts.idpSsoUrl;
|
|
1285
|
+
if (typeof idpSloUrl !== "string" || idpSloUrl.length === 0) {
|
|
1286
|
+
throw new AuthError("auth-saml/no-idp-slo",
|
|
1287
|
+
"buildLogoutRequestPost: opts.idpSloUrl required");
|
|
1288
|
+
}
|
|
1289
|
+
var id = "_" + generateToken(20); // allow:raw-byte-literal — 20-byte SAML ID token
|
|
1290
|
+
var issueInstant = new Date().toISOString();
|
|
1291
|
+
var c14n = xmlC14n();
|
|
1292
|
+
var nameIdFormatAttr = bopts.nameIdFormat
|
|
1293
|
+
? " Format=\"" + c14n.escapeAttrValue(bopts.nameIdFormat) + "\""
|
|
1294
|
+
: "";
|
|
1295
|
+
var sessionIndexXml = bopts.sessionIndex
|
|
1296
|
+
? "<samlp:SessionIndex>" + c14n.escapeText(bopts.sessionIndex) + "</samlp:SessionIndex>"
|
|
1297
|
+
: "";
|
|
1298
|
+
var bodyXml =
|
|
1299
|
+
"<samlp:LogoutRequest xmlns:samlp=\"" + SAML_NS.protocol + "\" " +
|
|
1300
|
+
"xmlns:saml=\"" + SAML_NS.assertion + "\" " +
|
|
1301
|
+
"ID=\"" + id + "\" " +
|
|
1302
|
+
"Version=\"2.0\" " +
|
|
1303
|
+
"IssueInstant=\"" + issueInstant + "\" " +
|
|
1304
|
+
"Destination=\"" + c14n.escapeAttrValue(idpSloUrl) + "\">" +
|
|
1305
|
+
"<saml:Issuer>" + c14n.escapeText(opts.entityId) + "</saml:Issuer>" +
|
|
1306
|
+
"<saml:NameID" + nameIdFormatAttr + ">" + c14n.escapeText(bopts.nameId) + "</saml:NameID>" +
|
|
1307
|
+
sessionIndexXml +
|
|
1308
|
+
"</samlp:LogoutRequest>";
|
|
1309
|
+
|
|
1310
|
+
var signedXml = bodyXml;
|
|
1311
|
+
if (bopts.signingKey || bopts.signingAlg) {
|
|
1312
|
+
signedXml = _embedXmlDsig(bodyXml, id, bopts.signingKey, bopts.signingAlg);
|
|
1313
|
+
}
|
|
1314
|
+
var samlRequest = Buffer.from(signedXml, "utf8").toString("base64");
|
|
1315
|
+
var rs = bopts.relayState ? bopts.relayState : "";
|
|
1316
|
+
var formHtml =
|
|
1317
|
+
"<!DOCTYPE html><html><body onload=\"document.forms[0].submit()\">" +
|
|
1318
|
+
"<form method=\"POST\" action=\"" + c14n.escapeAttrValue(idpSloUrl) + "\">" +
|
|
1319
|
+
"<input type=\"hidden\" name=\"SAMLRequest\" value=\"" + c14n.escapeAttrValue(samlRequest) + "\"/>" +
|
|
1320
|
+
(rs ? "<input type=\"hidden\" name=\"RelayState\" value=\"" + c14n.escapeAttrValue(rs) + "\"/>" : "") +
|
|
1321
|
+
"<noscript><button type=\"submit\">Continue</button></noscript>" +
|
|
1322
|
+
"</form></body></html>";
|
|
1323
|
+
_emitAudit("logoutrequest_post_built", "success", {
|
|
1324
|
+
id: id, idp: opts.idpEntityId, signed: !!bopts.signingKey,
|
|
1325
|
+
});
|
|
1326
|
+
return { id: id, action: idpSloUrl, samlRequest: samlRequest, formHtml: formHtml, raw: signedXml };
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* @primitive b.auth.saml.sp.parseLogoutRequestPost
|
|
1331
|
+
* @signature b.auth.saml.sp.parseLogoutRequestPost(samlRequestB64, vopts?)
|
|
1332
|
+
* @since 0.10.16
|
|
1333
|
+
* @status stable
|
|
1334
|
+
*
|
|
1335
|
+
* HTTP-POST variant of parseLogoutRequest. Decodes the base64
|
|
1336
|
+
* SAMLRequest body, parses the XML, and (when `idpVerifyKey` /
|
|
1337
|
+
* `idpVerifyAlg` are supplied) verifies the embedded XMLDSig-
|
|
1338
|
+
* Enveloped signature against the IdP key. Refuses when the
|
|
1339
|
+
* signature element is missing, the Reference URI doesn't match
|
|
1340
|
+
* the document root ID, the digest doesn't match the canonicalized
|
|
1341
|
+
* referenced element (signature-wrapping defense), or the
|
|
1342
|
+
* SignedInfo signature doesn't verify.
|
|
1343
|
+
*
|
|
1344
|
+
* @opts
|
|
1345
|
+
* idpVerifyKey: Uint8Array, // optional — verify embedded XMLDSig signature against this IdP key
|
|
1346
|
+
* idpVerifyAlg: "ml-dsa-65" | "ml-dsa-87" | "ed25519", // required when idpVerifyKey is supplied
|
|
1347
|
+
*
|
|
1348
|
+
* @example
|
|
1349
|
+
* var req = sp.parseLogoutRequestPost(req.body.SAMLRequest, {
|
|
1350
|
+
* idpVerifyKey: idpPubKey, idpVerifyAlg: "ml-dsa-65",
|
|
1351
|
+
* });
|
|
1352
|
+
* // req.nameId / req.sessionIndex / req.issuer
|
|
1353
|
+
*/
|
|
1354
|
+
function parseLogoutRequestPost(samlRequestB64, vopts) {
|
|
1355
|
+
vopts = vopts || {};
|
|
1356
|
+
if (typeof samlRequestB64 !== "string" || samlRequestB64.length === 0) {
|
|
1357
|
+
throw new AuthError("auth-saml/bad-input",
|
|
1358
|
+
"parseLogoutRequestPost: samlRequestB64 must be a non-empty string");
|
|
1359
|
+
}
|
|
1360
|
+
var xml = Buffer.from(samlRequestB64, "base64").toString("utf8");
|
|
1361
|
+
if (vopts.idpVerifyKey || vopts.idpVerifyAlg) {
|
|
1362
|
+
_verifyEmbeddedXmlDsig(xml, vopts.idpVerifyKey, vopts.idpVerifyAlg, "LogoutRequest");
|
|
1363
|
+
}
|
|
1364
|
+
var c14n = xmlC14n();
|
|
1365
|
+
var root = c14n.parse(xml);
|
|
1366
|
+
var rootLocal = root.name.split(":").pop();
|
|
1367
|
+
if (rootLocal !== "LogoutRequest") {
|
|
1368
|
+
throw new AuthError("auth-saml/wrong-root",
|
|
1369
|
+
"parseLogoutRequestPost: root element is " + rootLocal + ", expected LogoutRequest");
|
|
1370
|
+
}
|
|
1371
|
+
var nameIdEl = _findChild(root, "NameID", SAML_NS.assertion);
|
|
1372
|
+
if (!nameIdEl) {
|
|
1373
|
+
throw new AuthError("auth-saml/no-nameid",
|
|
1374
|
+
"parseLogoutRequestPost: missing NameID");
|
|
1375
|
+
}
|
|
1376
|
+
var sessionIndexEl = _findChild(root, "SessionIndex", SAML_NS.protocol);
|
|
1377
|
+
var issuerEl = _findChild(root, "Issuer", SAML_NS.assertion);
|
|
1378
|
+
return {
|
|
1379
|
+
id: _attr(root, "ID"),
|
|
1380
|
+
destination: _attr(root, "Destination"),
|
|
1381
|
+
nameId: _textContent(nameIdEl),
|
|
1382
|
+
nameIdFormat: _attr(nameIdEl, "Format"),
|
|
1383
|
+
sessionIndex: sessionIndexEl ? _textContent(sessionIndexEl) : null,
|
|
1384
|
+
issuer: issuerEl ? _textContent(issuerEl) : null,
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* @primitive b.auth.saml.sp.buildLogoutRequestSoap
|
|
1390
|
+
* @signature b.auth.saml.sp.buildLogoutRequestSoap(opts)
|
|
1391
|
+
* @since 0.10.16
|
|
1392
|
+
* @status stable
|
|
1393
|
+
*
|
|
1394
|
+
* SOAP variant of buildLogoutRequest (SAML Bindings §3.2 — synchronous
|
|
1395
|
+
* back-channel binding). Wraps the LogoutRequest in
|
|
1396
|
+
* <soapenv:Envelope><soapenv:Body>...</> for an HTTP POST to the
|
|
1397
|
+
* IdP's SOAP endpoint. Embeds an XMLDSig-Enveloped signature on
|
|
1398
|
+
* the LogoutRequest itself (not the SOAP envelope) when signingKey
|
|
1399
|
+
* is supplied — matching the IdP-side parse expectation.
|
|
1400
|
+
*
|
|
1401
|
+
* @opts
|
|
1402
|
+
* same as buildLogoutRequestPost
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* var lr = sp.buildLogoutRequestSoap({ nameId: "alice@idp" });
|
|
1406
|
+
* var resp = await b.httpClient.request(lr.action, {
|
|
1407
|
+
* method: "POST",
|
|
1408
|
+
* body: lr.body,
|
|
1409
|
+
* headers: { "Content-Type": "text/xml; charset=utf-8" },
|
|
1410
|
+
* });
|
|
1411
|
+
* var result = sp.parseLogoutResponseSoap(resp.body);
|
|
1412
|
+
*/
|
|
1413
|
+
function buildLogoutRequestSoap(bopts) {
|
|
1414
|
+
var post = buildLogoutRequestPost(bopts);
|
|
1415
|
+
var body =
|
|
1416
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
|
1417
|
+
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
|
|
1418
|
+
"<soapenv:Body>" + post.raw + "</soapenv:Body>" +
|
|
1419
|
+
"</soapenv:Envelope>";
|
|
1420
|
+
return { id: post.id, action: post.action, body: body, raw: post.raw };
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* @primitive b.auth.saml.sp.parseLogoutResponseSoap
|
|
1425
|
+
* @signature b.auth.saml.sp.parseLogoutResponseSoap(soapXml, vopts?)
|
|
1426
|
+
* @since 0.10.16
|
|
1427
|
+
* @status stable
|
|
1428
|
+
*
|
|
1429
|
+
* Parse a SOAP-wrapped LogoutResponse from the IdP's synchronous
|
|
1430
|
+
* back-channel reply. Unwraps the soapenv:Body, optionally verifies
|
|
1431
|
+
* the XMLDSig signature, and returns the same shape as
|
|
1432
|
+
* parseLogoutResponse.
|
|
1433
|
+
*
|
|
1434
|
+
* @opts
|
|
1435
|
+
* idpVerifyKey: Uint8Array, // optional — verify embedded XMLDSig signature against this IdP key
|
|
1436
|
+
* idpVerifyAlg: "ml-dsa-65" | "ml-dsa-87" | "ed25519", // required when idpVerifyKey is supplied
|
|
1437
|
+
*
|
|
1438
|
+
* @example
|
|
1439
|
+
* var result = sp.parseLogoutResponseSoap(resp.body, {
|
|
1440
|
+
* idpVerifyKey: idpPubKey, idpVerifyAlg: "ml-dsa-65",
|
|
1441
|
+
* });
|
|
1442
|
+
* // result.success / result.statusCode / result.inResponseTo
|
|
1443
|
+
*/
|
|
1444
|
+
function parseLogoutResponseSoap(soapXml, vopts) {
|
|
1445
|
+
vopts = vopts || {};
|
|
1446
|
+
if (typeof soapXml !== "string" || soapXml.length === 0) {
|
|
1447
|
+
throw new AuthError("auth-saml/bad-input",
|
|
1448
|
+
"parseLogoutResponseSoap: soapXml must be a non-empty string");
|
|
1449
|
+
}
|
|
1450
|
+
var c14n = xmlC14n();
|
|
1451
|
+
var soapRoot;
|
|
1452
|
+
try { soapRoot = c14n.parse(soapXml); }
|
|
1453
|
+
catch (e) {
|
|
1454
|
+
throw new AuthError("auth-saml/bad-soap",
|
|
1455
|
+
"parseLogoutResponseSoap: XML parse failed: " + ((e && e.message) || String(e)));
|
|
1456
|
+
}
|
|
1457
|
+
var soapRootLocal = soapRoot.name.split(":").pop();
|
|
1458
|
+
if (soapRootLocal !== "Envelope") {
|
|
1459
|
+
throw new AuthError("auth-saml/bad-soap",
|
|
1460
|
+
"parseLogoutResponseSoap: root element is " + soapRootLocal + ", expected soap:Envelope");
|
|
1461
|
+
}
|
|
1462
|
+
var body = null;
|
|
1463
|
+
for (var ci = 0; ci < soapRoot.children.length; ci += 1) {
|
|
1464
|
+
var ch = soapRoot.children[ci];
|
|
1465
|
+
if (ch.type !== "element") continue;
|
|
1466
|
+
var local = ch.name.split(":").pop();
|
|
1467
|
+
if (local === "Body") { body = ch; break; }
|
|
1468
|
+
}
|
|
1469
|
+
if (!body) {
|
|
1470
|
+
throw new AuthError("auth-saml/bad-soap",
|
|
1471
|
+
"parseLogoutResponseSoap: missing soap:Body");
|
|
1472
|
+
}
|
|
1473
|
+
var inner = null;
|
|
1474
|
+
for (var bi = 0; bi < body.children.length; bi += 1) {
|
|
1475
|
+
var bc = body.children[bi];
|
|
1476
|
+
if (bc.type === "element") { inner = bc; break; }
|
|
1477
|
+
}
|
|
1478
|
+
if (!inner) {
|
|
1479
|
+
throw new AuthError("auth-saml/bad-soap",
|
|
1480
|
+
"parseLogoutResponseSoap: soap:Body is empty");
|
|
1481
|
+
}
|
|
1482
|
+
var innerXml = Buffer.from(c14n.canonicalize(inner)).toString("utf8");
|
|
1483
|
+
if (vopts.idpVerifyKey || vopts.idpVerifyAlg) {
|
|
1484
|
+
_verifyEmbeddedXmlDsig(innerXml, vopts.idpVerifyKey, vopts.idpVerifyAlg, "LogoutResponse");
|
|
1485
|
+
}
|
|
1486
|
+
var innerLocal = inner.name.split(":").pop();
|
|
1487
|
+
if (innerLocal !== "LogoutResponse") {
|
|
1488
|
+
throw new AuthError("auth-saml/wrong-root",
|
|
1489
|
+
"parseLogoutResponseSoap: body element is " + innerLocal + ", expected LogoutResponse");
|
|
1490
|
+
}
|
|
1491
|
+
var statusEl = _findChild(inner, "Status", SAML_NS.protocol);
|
|
1492
|
+
var statusCode = statusEl ? _attr(_findChild(statusEl, "StatusCode", SAML_NS.protocol), "Value") : null;
|
|
1493
|
+
var issuerEl = _findChild(inner, "Issuer", SAML_NS.assertion);
|
|
1494
|
+
return {
|
|
1495
|
+
id: _attr(inner, "ID"),
|
|
1496
|
+
inResponseTo: _attr(inner, "InResponseTo"),
|
|
1497
|
+
destination: _attr(inner, "Destination"),
|
|
1498
|
+
statusCode: statusCode,
|
|
1499
|
+
success: statusCode === "urn:oasis:names:tc:SAML:2.0:status:Success",
|
|
1500
|
+
issuer: issuerEl ? _textContent(issuerEl) : null,
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
return {
|
|
1505
|
+
buildAuthnRequest: buildAuthnRequest,
|
|
1506
|
+
verifyResponse: verifyResponse,
|
|
1507
|
+
metadata: metadata,
|
|
1508
|
+
buildLogoutRequest: buildLogoutRequest,
|
|
1509
|
+
parseLogoutRequest: parseLogoutRequest,
|
|
1510
|
+
buildLogoutResponse: buildLogoutResponse,
|
|
1511
|
+
parseLogoutResponse: parseLogoutResponse,
|
|
1512
|
+
buildLogoutRequestPost: buildLogoutRequestPost,
|
|
1513
|
+
parseLogoutRequestPost: parseLogoutRequestPost,
|
|
1514
|
+
buildLogoutRequestSoap: buildLogoutRequestSoap,
|
|
1515
|
+
parseLogoutResponseSoap: parseLogoutResponseSoap,
|
|
1516
|
+
entityId: opts.entityId,
|
|
1517
|
+
idpEntityId: opts.idpEntityId,
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// ---- v0.10.16 — SAML EncryptedAssertion decrypt (XMLEnc) ----
|
|
1522
|
+
|
|
1523
|
+
// XMLEnc Algorithm URIs we support.
|
|
1524
|
+
//
|
|
1525
|
+
// Currently-available standards (W3C XMLEnc 1.1, Recommendation 2013):
|
|
1526
|
+
// Symmetric content encryption:
|
|
1527
|
+
// http://www.w3.org/2009/xmlenc11#aes128-gcm (XMLEnc 1.1 §5.2.4)
|
|
1528
|
+
// http://www.w3.org/2009/xmlenc11#aes256-gcm (XMLEnc 1.1 §5.2.4)
|
|
1529
|
+
// Asymmetric key transport:
|
|
1530
|
+
// http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p (XMLEnc 1.0 §5.4.2 + RFC 4055)
|
|
1531
|
+
// http://www.w3.org/2009/xmlenc11#rsa-oaep (XMLEnc 1.1 §5.4.2)
|
|
1532
|
+
//
|
|
1533
|
+
// AES-CBC content encryption (xmlenc#aes128-cbc / aes256-cbc) is
|
|
1534
|
+
// intentionally REFUSED: CVE-2011-1473 + the broader XML-Encryption
|
|
1535
|
+
// padding-oracle research (Jager & Somorovsky 2011) demonstrate that
|
|
1536
|
+
// CBC mode under XMLEnc is exploitable without per-content MAC.
|
|
1537
|
+
// Operators integrating with IdPs that default to CBC (older ADFS /
|
|
1538
|
+
// Azure AD / Okta / Keycloak / OneLogin) MUST switch the IdP's
|
|
1539
|
+
// content-encryption setting to AES-128-GCM or AES-256-GCM. The
|
|
1540
|
+
// framework follows W3C's CR-2013 advice that GCM be used in new
|
|
1541
|
+
// deployments; the framework's "never weaken security middleware"
|
|
1542
|
+
// rule applies here.
|
|
1543
|
+
//
|
|
1544
|
+
// SHA-1 anywhere (rsa-oaep-mgf1p with SHA-1 OAEP DigestMethod,
|
|
1545
|
+
// xmldsig#sha1 DigestMethod) is also refused — Bleichenbacher /
|
|
1546
|
+
// collision risk plus CVE-2023-49141-class advisories outweigh
|
|
1547
|
+
// "interop with stale IdPs". Operators upgrade the IdP's digest
|
|
1548
|
+
// algorithm to SHA-256+ rather than relax the framework defense.
|
|
1549
|
+
//
|
|
1550
|
+
// Experimental (framework-private URNs — no IETF/W3C registration;
|
|
1551
|
+
// these are clearly under `urn:blamejs:experimental:` so operators
|
|
1552
|
+
// grep them in logs and know the framework owns them. Swap to the
|
|
1553
|
+
// registered URI once the relevant IETF/W3C WG publishes one):
|
|
1554
|
+
// urn:blamejs:experimental:xmlenc:xchacha20-poly1305 (XChaCha20-Poly1305 content encryption)
|
|
1555
|
+
// urn:blamejs:experimental:xmlenc:ml-kem-1024 (ML-KEM-1024 key transport)
|
|
1556
|
+
function _decryptEncryptedAssertion(encAssertion, spPrivateKeyPem) {
|
|
1557
|
+
var encData = _findChild(encAssertion, "EncryptedData");
|
|
1558
|
+
if (!encData) {
|
|
1559
|
+
throw new AuthError("auth-saml/encrypted-no-encrypted-data",
|
|
1560
|
+
"EncryptedAssertion missing EncryptedData");
|
|
1561
|
+
}
|
|
1562
|
+
var encMethod = _findChild(encData, "EncryptionMethod");
|
|
1563
|
+
var contentAlg = encMethod && _attr(encMethod, "Algorithm");
|
|
1564
|
+
if (!contentAlg) {
|
|
1565
|
+
throw new AuthError("auth-saml/encrypted-no-method",
|
|
1566
|
+
"EncryptedData missing EncryptionMethod/@Algorithm");
|
|
1567
|
+
}
|
|
1568
|
+
var keyInfo = _findChild(encData, "KeyInfo");
|
|
1569
|
+
if (!keyInfo) {
|
|
1570
|
+
throw new AuthError("auth-saml/encrypted-no-keyinfo",
|
|
1571
|
+
"EncryptedData missing KeyInfo (EncryptedKey transport required)");
|
|
1572
|
+
}
|
|
1573
|
+
var encKey = _findChild(keyInfo, "EncryptedKey");
|
|
1574
|
+
if (!encKey) {
|
|
1575
|
+
throw new AuthError("auth-saml/encrypted-no-encrypted-key",
|
|
1576
|
+
"EncryptedData/KeyInfo missing EncryptedKey");
|
|
1577
|
+
}
|
|
1578
|
+
var ekMethod = _findChild(encKey, "EncryptionMethod");
|
|
1579
|
+
var keyAlg = ekMethod && _attr(ekMethod, "Algorithm");
|
|
1580
|
+
if (!keyAlg) {
|
|
1581
|
+
throw new AuthError("auth-saml/encrypted-no-key-alg",
|
|
1582
|
+
"EncryptedKey missing EncryptionMethod/@Algorithm");
|
|
1583
|
+
}
|
|
1584
|
+
var ekCipherDataNode = _findChild(encKey, "CipherData");
|
|
1585
|
+
var ekCipherValueNode = ekCipherDataNode && _findChild(ekCipherDataNode, "CipherValue");
|
|
1586
|
+
if (!ekCipherValueNode) {
|
|
1587
|
+
throw new AuthError("auth-saml/encrypted-no-key-cipher-value",
|
|
1588
|
+
"EncryptedKey missing CipherData/CipherValue");
|
|
1589
|
+
}
|
|
1590
|
+
var wrappedKey = Buffer.from(_textContent(ekCipherValueNode).replace(/\s+/g, ""), "base64");
|
|
1591
|
+
// Unwrap the CEK.
|
|
1592
|
+
var cek;
|
|
1593
|
+
if (keyAlg === "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" ||
|
|
1594
|
+
keyAlg === "http://www.w3.org/2009/xmlenc11#rsa-oaep") {
|
|
1595
|
+
var oaepHashName = "sha1";
|
|
1596
|
+
var digestMethodEk = _findChild(ekMethod, "DigestMethod");
|
|
1597
|
+
var oaepDigestUri = digestMethodEk && _attr(digestMethodEk, "Algorithm");
|
|
1598
|
+
if (oaepDigestUri) {
|
|
1599
|
+
if (oaepDigestUri === "http://www.w3.org/2001/04/xmlenc#sha256") oaepHashName = "sha256";
|
|
1600
|
+
else if (oaepDigestUri === "http://www.w3.org/2001/04/xmlenc#sha384") oaepHashName = "sha384";
|
|
1601
|
+
else if (oaepDigestUri === "http://www.w3.org/2001/04/xmlenc#sha512") oaepHashName = "sha512";
|
|
1602
|
+
else {
|
|
1603
|
+
throw new AuthError("auth-saml/encrypted-unsupported-oaep-digest",
|
|
1604
|
+
"EncryptedKey OAEP DigestMethod " + oaepDigestUri + " not supported");
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
if (oaepHashName === "sha1") {
|
|
1608
|
+
throw new AuthError("auth-saml/encrypted-weak-oaep-digest",
|
|
1609
|
+
"EncryptedKey OAEP DigestMethod is SHA-1 — refused (CVE-2023-49141-class). " +
|
|
1610
|
+
"Require SHA-256+ on IdP side.");
|
|
1611
|
+
}
|
|
1612
|
+
var spKey;
|
|
1613
|
+
try { spKey = nodeCrypto.createPrivateKey({ key: spPrivateKeyPem, format: "pem" }); }
|
|
1614
|
+
catch (e) {
|
|
1615
|
+
throw new AuthError("auth-saml/encrypted-bad-sp-key",
|
|
1616
|
+
"spPrivateKeyPem parse failed: " + ((e && e.message) || String(e)));
|
|
1617
|
+
}
|
|
1618
|
+
try {
|
|
1619
|
+
cek = nodeCrypto.privateDecrypt({
|
|
1620
|
+
key: spKey,
|
|
1621
|
+
padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
1622
|
+
oaepHash: oaepHashName,
|
|
1623
|
+
}, wrappedKey);
|
|
1624
|
+
} catch (eR) {
|
|
1625
|
+
throw new AuthError("auth-saml/encrypted-key-unwrap-failed",
|
|
1626
|
+
"OAEP unwrap failed: " + ((eR && eR.message) || String(eR)));
|
|
1627
|
+
}
|
|
1628
|
+
} else if (keyAlg === "urn:blamejs:experimental:xmlenc:ml-kem-1024") {
|
|
1629
|
+
// Framework PQC envelope — wrappedKey carries the ML-KEM
|
|
1630
|
+
// ciphertext concatenated with the AEAD-wrapped CEK. We invoke
|
|
1631
|
+
// b.pqcSoftware.ml_kem_1024.decapsulate to recover the shared
|
|
1632
|
+
// secret, then ChaCha20-Poly1305 unwrap. The exact wire shape is
|
|
1633
|
+
// the framework's `b.crypto.envelope` format.
|
|
1634
|
+
try {
|
|
1635
|
+
cek = bCrypto.decryptEnvelope({
|
|
1636
|
+
envelope: wrappedKey,
|
|
1637
|
+
privateKey: nodeCrypto.createPrivateKey({ key: spPrivateKeyPem, format: "pem" }),
|
|
1638
|
+
});
|
|
1639
|
+
} catch (eM) {
|
|
1640
|
+
throw new AuthError("auth-saml/encrypted-key-unwrap-failed",
|
|
1641
|
+
"ML-KEM-1024 unwrap failed: " + ((eM && eM.message) || String(eM)));
|
|
1642
|
+
}
|
|
1643
|
+
if (!Buffer.isBuffer(cek)) cek = Buffer.from(cek);
|
|
1644
|
+
} else {
|
|
1645
|
+
throw new AuthError("auth-saml/encrypted-unsupported-key-alg",
|
|
1646
|
+
"EncryptedKey EncryptionMethod " + keyAlg + " not supported " +
|
|
1647
|
+
"(supported: W3C xmlenc#rsa-oaep-mgf1p, xmlenc11#rsa-oaep, " +
|
|
1648
|
+
"framework-experimental urn:blamejs:experimental:xmlenc:ml-kem-1024). " +
|
|
1649
|
+
"AES-CBC content encryption is refused — switch the IdP to AES-128-GCM " +
|
|
1650
|
+
"or AES-256-GCM.");
|
|
1651
|
+
}
|
|
1652
|
+
// Decrypt content with the CEK.
|
|
1653
|
+
var contentCipherDataNode = _findChild(encData, "CipherData");
|
|
1654
|
+
var contentCipherValueNode = contentCipherDataNode && _findChild(contentCipherDataNode, "CipherValue");
|
|
1655
|
+
if (!contentCipherValueNode) {
|
|
1656
|
+
throw new AuthError("auth-saml/encrypted-no-content-cipher-value",
|
|
1657
|
+
"EncryptedData missing CipherData/CipherValue");
|
|
1658
|
+
}
|
|
1659
|
+
var contentBlob = Buffer.from(_textContent(contentCipherValueNode).replace(/\s+/g, ""), "base64");
|
|
1660
|
+
var clearBytes;
|
|
1661
|
+
if (contentAlg === "http://www.w3.org/2009/xmlenc11#aes128-gcm" ||
|
|
1662
|
+
contentAlg === "http://www.w3.org/2009/xmlenc11#aes256-gcm") {
|
|
1663
|
+
var aesBits = contentAlg.indexOf("aes128") !== -1 ? 128 : 256; // allow:raw-byte-literal — AES key size
|
|
1664
|
+
var expectedKeyBytes = aesBits / 8; // allow:raw-byte-literal — bits→bytes
|
|
1665
|
+
if (cek.length !== expectedKeyBytes) {
|
|
1666
|
+
throw new AuthError("auth-saml/encrypted-wrong-cek-len",
|
|
1667
|
+
"AES-" + aesBits + "-GCM CEK length is " + cek.length + ", expected " + expectedKeyBytes);
|
|
1668
|
+
}
|
|
1669
|
+
if (contentBlob.length < 28) { // allow:raw-byte-literal — 12 IV + 16 tag
|
|
1670
|
+
throw new AuthError("auth-saml/encrypted-content-too-short",
|
|
1671
|
+
"AES-GCM CipherValue too short to contain IV (12) + tag (16)");
|
|
1672
|
+
}
|
|
1673
|
+
var iv = contentBlob.subarray(0, 12); // allow:raw-byte-literal — GCM IV size
|
|
1674
|
+
var tag = contentBlob.subarray(contentBlob.length - 16); // allow:raw-byte-literal — GCM tag size
|
|
1675
|
+
var ct = contentBlob.subarray(12, contentBlob.length - 16);
|
|
1676
|
+
var decipher = nodeCrypto.createDecipheriv("aes-" + aesBits + "-gcm", cek, iv);
|
|
1677
|
+
decipher.setAuthTag(tag);
|
|
1678
|
+
try { clearBytes = Buffer.concat([decipher.update(ct), decipher.final()]); }
|
|
1679
|
+
catch (eD) {
|
|
1680
|
+
throw new AuthError("auth-saml/encrypted-content-tag-mismatch",
|
|
1681
|
+
"AES-GCM authentication tag mismatch: " + ((eD && eD.message) || String(eD)));
|
|
1682
|
+
}
|
|
1683
|
+
} else if (contentAlg === "urn:blamejs:experimental:xmlenc:xchacha20-poly1305") {
|
|
1684
|
+
if (cek.length !== 32) { // allow:raw-byte-literal — XChaCha20 key size
|
|
1685
|
+
throw new AuthError("auth-saml/encrypted-wrong-cek-len",
|
|
1686
|
+
"XChaCha20-Poly1305 CEK length is " + cek.length + ", expected 32");
|
|
1687
|
+
}
|
|
1688
|
+
if (contentBlob.length < 40) { // allow:raw-byte-literal — 24 nonce + 16 tag
|
|
1689
|
+
throw new AuthError("auth-saml/encrypted-content-too-short",
|
|
1690
|
+
"XChaCha20-Poly1305 CipherValue too short");
|
|
1691
|
+
}
|
|
1692
|
+
var xnonce = contentBlob.subarray(0, 24); // allow:raw-byte-literal — XChaCha20 nonce size
|
|
1693
|
+
var xtag = contentBlob.subarray(contentBlob.length - 16); // allow:raw-byte-literal — Poly1305 tag size
|
|
1694
|
+
var xct = contentBlob.subarray(24, contentBlob.length - 16);
|
|
1695
|
+
try {
|
|
1696
|
+
clearBytes = bCrypto.aeadDecrypt({
|
|
1697
|
+
alg: "xchacha20-poly1305",
|
|
1698
|
+
key: cek,
|
|
1699
|
+
nonce: xnonce,
|
|
1700
|
+
ct: xct,
|
|
1701
|
+
tag: xtag,
|
|
1702
|
+
});
|
|
1703
|
+
} catch (eX) {
|
|
1704
|
+
throw new AuthError("auth-saml/encrypted-content-tag-mismatch",
|
|
1705
|
+
"XChaCha20-Poly1305 tag mismatch: " + ((eX && eX.message) || String(eX)));
|
|
1706
|
+
}
|
|
1707
|
+
} else {
|
|
1708
|
+
throw new AuthError("auth-saml/encrypted-unsupported-content-alg",
|
|
1709
|
+
"EncryptedData EncryptionMethod " + contentAlg + " not supported " +
|
|
1710
|
+
"(supported: W3C xmlenc11#aes128-gcm, xmlenc11#aes256-gcm, " +
|
|
1711
|
+
"framework-experimental urn:blamejs:experimental:xmlenc:xchacha20-poly1305). " +
|
|
1712
|
+
"AES-CBC content encryption is refused — switch the IdP to AES-128-GCM or AES-256-GCM " +
|
|
1713
|
+
"(CVE-2011-1473 padding-oracle class).");
|
|
1714
|
+
}
|
|
1715
|
+
return clearBytes.toString("utf8");
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// ---- v0.10.16 — SAML SLO XMLDSig-Enveloped (HTTP-POST/SOAP) ----
|
|
1719
|
+
|
|
1720
|
+
// PQC SignatureMethod URIs used by the embedded XMLDSig signatures.
|
|
1721
|
+
// Standard XMLDSig vocabulary classical signing URIs (W3C XMLDSig
|
|
1722
|
+
// Core 1.1 + RFC 9231 for Ed25519) are dispatched via _sigAlgUrn /
|
|
1723
|
+
// _sigAlgFromUri. The framework adds two non-standard URNs for
|
|
1724
|
+
// ML-DSA because no W3C/IETF XMLDSig URI registration exists for
|
|
1725
|
+
// post-quantum signers yet (LAMPS WG has open drafts but none final).
|
|
1726
|
+
// Operators integrating with PQC-aware IdPs that exchange those URNs
|
|
1727
|
+
// out-of-band can use them; operators integrating with classical IdPs
|
|
1728
|
+
// (the public SAML deployment baseline today) use the W3C URIs.
|
|
1729
|
+
|
|
1730
|
+
function _embedXmlDsig(bodyXml, refId, signingKey, signingAlg) {
|
|
1731
|
+
// XMLDSig-Enveloped over the LogoutRequest / LogoutResponse root.
|
|
1732
|
+
// Pipeline:
|
|
1733
|
+
// 1. exclusive-c14n digest the LogoutRequest element with the
|
|
1734
|
+
// operator-supplied SignatureMethod's digest (SHA3-512 for
|
|
1735
|
+
// PQC + Ed25519, SHA-256/384/512 for classical RSA/ECDSA).
|
|
1736
|
+
// 2. Build SignedInfo with that digest in the Reference.
|
|
1737
|
+
// 3. exclusive-c14n SignedInfo and sign it via the chosen alg.
|
|
1738
|
+
// 4. Emit the ds:Signature element inside the root.
|
|
1739
|
+
var sigAlgUrn = _sigAlgUrn(signingAlg);
|
|
1740
|
+
if (!sigAlgUrn) {
|
|
1741
|
+
throw new AuthError("auth-saml/bad-signing-alg",
|
|
1742
|
+
"_embedXmlDsig: signingAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519' / " +
|
|
1743
|
+
"'rsa-sha256' / 'rsa-sha384' / 'rsa-sha512' / " +
|
|
1744
|
+
"'ecdsa-sha256' / 'ecdsa-sha384' / 'ecdsa-sha512'");
|
|
1745
|
+
}
|
|
1746
|
+
// PQC requires a Uint8Array; classical accepts PEM string or
|
|
1747
|
+
// KeyObject. ed25519 accepts both raw Uint8Array (32 bytes) and
|
|
1748
|
+
// KeyObject/PEM. We validate the key shape per alg family.
|
|
1749
|
+
var isPqc = signingAlg === "ml-dsa-65" || signingAlg === "ml-dsa-87";
|
|
1750
|
+
if (isPqc && !(signingKey instanceof Uint8Array)) {
|
|
1751
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
1752
|
+
"_embedXmlDsig: signingKey for " + signingAlg + " must be a Uint8Array");
|
|
1753
|
+
}
|
|
1754
|
+
if (!isPqc && signingAlg !== "ed25519" &&
|
|
1755
|
+
typeof signingKey !== "string" &&
|
|
1756
|
+
!(signingKey && typeof signingKey === "object" && signingKey.type === "private")) {
|
|
1757
|
+
throw new AuthError("auth-saml/bad-signing-key",
|
|
1758
|
+
"_embedXmlDsig: signingKey for classical " + signingAlg +
|
|
1759
|
+
" must be a PEM string or node:crypto KeyObject");
|
|
1760
|
+
}
|
|
1761
|
+
var sigMethodUri = sigAlgUrn.urn;
|
|
1762
|
+
// DigestMethod follows the SignatureMethod family:
|
|
1763
|
+
// classical SHA-256 family → xmlenc#sha256/384/512 (W3C XMLDSig)
|
|
1764
|
+
// PQC + Ed25519 → xmldsig-more#sha3-512 (framework default)
|
|
1765
|
+
var digestMethodUri;
|
|
1766
|
+
if (signingAlg === "rsa-sha256" || signingAlg === "ecdsa-sha256") {
|
|
1767
|
+
digestMethodUri = "http://www.w3.org/2001/04/xmlenc#sha256";
|
|
1768
|
+
} else if (signingAlg === "rsa-sha384" || signingAlg === "ecdsa-sha384") {
|
|
1769
|
+
digestMethodUri = "http://www.w3.org/2001/04/xmlenc#sha384";
|
|
1770
|
+
} else if (signingAlg === "rsa-sha512" || signingAlg === "ecdsa-sha512") {
|
|
1771
|
+
digestMethodUri = "http://www.w3.org/2001/04/xmlenc#sha512";
|
|
1772
|
+
} else {
|
|
1773
|
+
digestMethodUri = "http://www.w3.org/2007/05/xmldsig-more#sha3-512";
|
|
1774
|
+
}
|
|
1775
|
+
var c14n = xmlC14n();
|
|
1776
|
+
// Pick the digest function matching digestMethodUri.
|
|
1777
|
+
var digestNodeAlg;
|
|
1778
|
+
if (digestMethodUri === "http://www.w3.org/2001/04/xmlenc#sha256") digestNodeAlg = "sha256";
|
|
1779
|
+
else if (digestMethodUri === "http://www.w3.org/2001/04/xmlenc#sha384") digestNodeAlg = "sha384";
|
|
1780
|
+
else if (digestMethodUri === "http://www.w3.org/2001/04/xmlenc#sha512") digestNodeAlg = "sha512";
|
|
1781
|
+
else digestNodeAlg = "sha3-512";
|
|
1782
|
+
var refDigest = nodeCrypto.createHash(digestNodeAlg).update(c14n.canonicalize(c14n.parse(bodyXml))).digest();
|
|
1783
|
+
var signedInfo =
|
|
1784
|
+
"<ds:SignedInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">" +
|
|
1785
|
+
"<ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>" +
|
|
1786
|
+
"<ds:SignatureMethod Algorithm=\"" + sigMethodUri + "\"/>" +
|
|
1787
|
+
"<ds:Reference URI=\"#" + refId + "\">" +
|
|
1788
|
+
"<ds:Transforms>" +
|
|
1789
|
+
"<ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>" +
|
|
1790
|
+
"<ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>" +
|
|
1791
|
+
"</ds:Transforms>" +
|
|
1792
|
+
"<ds:DigestMethod Algorithm=\"" + digestMethodUri + "\"/>" +
|
|
1793
|
+
"<ds:DigestValue>" + refDigest.toString("base64") + "</ds:DigestValue>" +
|
|
1794
|
+
"</ds:Reference>" +
|
|
1795
|
+
"</ds:SignedInfo>";
|
|
1796
|
+
var signedInfoCanonical = c14n.canonicalize(c14n.parse(signedInfo));
|
|
1797
|
+
var sigBytes = sigAlgUrn.sign(signedInfoCanonical, signingKey);
|
|
1798
|
+
var sigEl =
|
|
1799
|
+
"<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">" +
|
|
1800
|
+
signedInfo +
|
|
1801
|
+
"<ds:SignatureValue>" + Buffer.from(sigBytes).toString("base64") + "</ds:SignatureValue>" +
|
|
1802
|
+
"</ds:Signature>";
|
|
1803
|
+
// Insert Signature as the second child after Issuer (per SAML 2.0
|
|
1804
|
+
// schema — saml:Issuer always precedes ds:Signature).
|
|
1805
|
+
var issuerCloseIdx = bodyXml.indexOf("</saml:Issuer>");
|
|
1806
|
+
if (issuerCloseIdx === -1) {
|
|
1807
|
+
throw new AuthError("auth-saml/no-issuer",
|
|
1808
|
+
"_embedXmlDsig: bodyXml missing saml:Issuer element");
|
|
1809
|
+
}
|
|
1810
|
+
var splitAt = issuerCloseIdx + "</saml:Issuer>".length;
|
|
1811
|
+
return bodyXml.substring(0, splitAt) + sigEl + bodyXml.substring(splitAt);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function _verifyEmbeddedXmlDsig(xml, idpVerifyKey, idpVerifyAlg, expectedRootLocal) {
|
|
1815
|
+
if (!idpVerifyKey || !idpVerifyAlg) return;
|
|
1816
|
+
var sigAlgUrn = _sigAlgUrn(idpVerifyAlg);
|
|
1817
|
+
if (!sigAlgUrn) {
|
|
1818
|
+
throw new AuthError("auth-saml/bad-verify-alg",
|
|
1819
|
+
"idpVerifyAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519' / " +
|
|
1820
|
+
"'rsa-sha256' / 'rsa-sha384' / 'rsa-sha512' / " +
|
|
1821
|
+
"'ecdsa-sha256' / 'ecdsa-sha384' / 'ecdsa-sha512'");
|
|
1822
|
+
}
|
|
1823
|
+
var expectedSigUri = sigAlgUrn.urn;
|
|
1824
|
+
var c14n = xmlC14n();
|
|
1825
|
+
var root = c14n.parse(xml);
|
|
1826
|
+
var rootLocal = root.name.split(":").pop();
|
|
1827
|
+
if (rootLocal !== expectedRootLocal) {
|
|
1828
|
+
throw new AuthError("auth-saml/wrong-root",
|
|
1829
|
+
"_verifyEmbeddedXmlDsig: root is " + rootLocal + ", expected " + expectedRootLocal);
|
|
1830
|
+
}
|
|
1831
|
+
var sigNode = _findChild(root, "Signature");
|
|
1832
|
+
if (!sigNode) {
|
|
1833
|
+
throw new AuthError("auth-saml/no-signature",
|
|
1834
|
+
"_verifyEmbeddedXmlDsig: " + expectedRootLocal + " has no embedded ds:Signature");
|
|
1835
|
+
}
|
|
1836
|
+
var signedInfo = _findChild(sigNode, "SignedInfo");
|
|
1837
|
+
if (!signedInfo) {
|
|
1838
|
+
throw new AuthError("auth-saml/no-signed-info",
|
|
1839
|
+
"_verifyEmbeddedXmlDsig: Signature missing SignedInfo");
|
|
1840
|
+
}
|
|
1841
|
+
// CanonicalizationMethod check (W3C XMLDSig Core 1.1 §4.5). Only
|
|
1842
|
+
// exclusive-c14n (with or without comments) is supported because the
|
|
1843
|
+
// framework's xml-c14n module canonicalizes via xml-exc-c14n. Older
|
|
1844
|
+
// SAML deployments using inclusive c14n
|
|
1845
|
+
// (http://www.w3.org/TR/2001/REC-xml-c14n-20010315) would silently
|
|
1846
|
+
// digest-mismatch — refuse explicitly with a clear error.
|
|
1847
|
+
var canonMethodNode = _findChild(signedInfo, "CanonicalizationMethod");
|
|
1848
|
+
var canonUri = canonMethodNode && _attr(canonMethodNode, "Algorithm");
|
|
1849
|
+
if (canonUri !== "http://www.w3.org/2001/10/xml-exc-c14n#" &&
|
|
1850
|
+
canonUri !== "http://www.w3.org/2001/10/xml-exc-c14n#WithComments") {
|
|
1851
|
+
throw new AuthError("auth-saml/unsupported-c14n",
|
|
1852
|
+
"_verifyEmbeddedXmlDsig: CanonicalizationMethod " + canonUri + " not supported " +
|
|
1853
|
+
"(only W3C exclusive xml-exc-c14n is supported; inclusive c14n is refused — " +
|
|
1854
|
+
"switch the IdP to exclusive canonicalization)");
|
|
1855
|
+
}
|
|
1856
|
+
var sigMethodNode = _findChild(signedInfo, "SignatureMethod");
|
|
1857
|
+
var sigUri = sigMethodNode && _attr(sigMethodNode, "Algorithm");
|
|
1858
|
+
if (sigUri !== expectedSigUri) {
|
|
1859
|
+
throw new AuthError("auth-saml/wrong-sig-alg",
|
|
1860
|
+
"_verifyEmbeddedXmlDsig: SignatureMethod " + sigUri + " != expected " + expectedSigUri +
|
|
1861
|
+
" (alg-confusion defense)");
|
|
1862
|
+
}
|
|
1863
|
+
var refNode = _findChild(signedInfo, "Reference");
|
|
1864
|
+
if (!refNode) {
|
|
1865
|
+
throw new AuthError("auth-saml/no-reference",
|
|
1866
|
+
"_verifyEmbeddedXmlDsig: SignedInfo missing Reference");
|
|
1867
|
+
}
|
|
1868
|
+
var refUri = _attr(refNode, "URI") || "";
|
|
1869
|
+
if (refUri.charAt(0) !== "#") {
|
|
1870
|
+
throw new AuthError("auth-saml/external-reference",
|
|
1871
|
+
"_verifyEmbeddedXmlDsig: Reference URI must be a same-document fragment");
|
|
1872
|
+
}
|
|
1873
|
+
var refId = refUri.substring(1);
|
|
1874
|
+
var rootId = _attr(root, "ID");
|
|
1875
|
+
if (rootId !== refId) {
|
|
1876
|
+
throw new AuthError("auth-saml/ref-mismatch",
|
|
1877
|
+
"_verifyEmbeddedXmlDsig: Reference URI '#" + refId + "' does not match root ID '" + rootId +
|
|
1878
|
+
"' (signature-wrapping defense)");
|
|
1879
|
+
}
|
|
1880
|
+
var digestMethodNode = _findChild(refNode, "DigestMethod");
|
|
1881
|
+
var digestUri = digestMethodNode && _attr(digestMethodNode, "Algorithm");
|
|
1882
|
+
// Allow either sha3-512 (framework default) or the SHA-2 family.
|
|
1883
|
+
var digestAlgName;
|
|
1884
|
+
if (digestUri === "http://www.w3.org/2007/05/xmldsig-more#sha3-512") digestAlgName = "sha3-512";
|
|
1885
|
+
else if (SUPPORTED_DIGEST[digestUri]) digestAlgName = SUPPORTED_DIGEST[digestUri];
|
|
1886
|
+
else {
|
|
1887
|
+
throw new AuthError("auth-saml/unsupported-digest",
|
|
1888
|
+
"_verifyEmbeddedXmlDsig: DigestMethod " + digestUri + " not supported");
|
|
1889
|
+
}
|
|
1890
|
+
var digestValueNode = _findChild(refNode, "DigestValue");
|
|
1891
|
+
var expectedDigestB64 = _textContent(digestValueNode);
|
|
1892
|
+
if (!expectedDigestB64) {
|
|
1893
|
+
throw new AuthError("auth-saml/no-digest-value",
|
|
1894
|
+
"_verifyEmbeddedXmlDsig: Reference missing DigestValue");
|
|
1895
|
+
}
|
|
1896
|
+
// Recompute the digest over the root with Signature stripped
|
|
1897
|
+
// (enveloped-signature transform). Clone root + filter out
|
|
1898
|
+
// ds:Signature children, then canonicalize.
|
|
1899
|
+
var rootForDigest = structuredClone(root);
|
|
1900
|
+
rootForDigest.children = rootForDigest.children.filter(function (c) {
|
|
1901
|
+
if (c.type !== "element") return true;
|
|
1902
|
+
return c.name.split(":").pop() !== "Signature";
|
|
1903
|
+
});
|
|
1904
|
+
var canonical = c14n.canonicalize(rootForDigest);
|
|
1905
|
+
var actualDigest = nodeCrypto.createHash(digestAlgName).update(canonical).digest();
|
|
1906
|
+
if (!timingSafeEqual(Buffer.from(expectedDigestB64, "base64"), actualDigest)) {
|
|
1907
|
+
throw new AuthError("auth-saml/digest-mismatch",
|
|
1908
|
+
"_verifyEmbeddedXmlDsig: Reference DigestValue does not match canonicalized root " +
|
|
1909
|
+
"(signature-wrapping or tampered content)");
|
|
1910
|
+
}
|
|
1911
|
+
// Canonicalize SignedInfo + PQC-verify signature.
|
|
1912
|
+
var signedInfoCanonical = c14n.canonicalize(signedInfo);
|
|
1913
|
+
var sigValueNode = _findChild(sigNode, "SignatureValue");
|
|
1914
|
+
var sigB64 = sigValueNode ? _textContent(sigValueNode).replace(/\s+/g, "") : "";
|
|
1915
|
+
if (!sigB64) {
|
|
1916
|
+
throw new AuthError("auth-saml/no-signature-value",
|
|
1917
|
+
"_verifyEmbeddedXmlDsig: Signature missing SignatureValue");
|
|
1918
|
+
}
|
|
1919
|
+
var sigBytes = Buffer.from(sigB64, "base64");
|
|
1920
|
+
var ok = false;
|
|
1921
|
+
try { ok = sigAlgUrn.verify(sigBytes, signedInfoCanonical, idpVerifyKey); }
|
|
1922
|
+
catch (e) {
|
|
1923
|
+
throw new AuthError("auth-saml/sig-verify-threw",
|
|
1924
|
+
"_verifyEmbeddedXmlDsig: signature verify threw: " + ((e && e.message) || String(e)));
|
|
1925
|
+
}
|
|
1926
|
+
if (!ok) {
|
|
1927
|
+
throw new AuthError("auth-saml/bad-signature",
|
|
1928
|
+
"_verifyEmbeddedXmlDsig: embedded XMLDSig signature does not verify against idpVerifyKey");
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// ---- v0.10.16 SAML SLO signature-alg dispatch ----
|
|
1933
|
+
|
|
1934
|
+
function _sigAlgUrn(alg) {
|
|
1935
|
+
// PQC signers — framework-private experimental URIs. The `urn:`
|
|
1936
|
+
// prefix lives under `urn:blamejs:experimental:` so operators
|
|
1937
|
+
// grepping their IdP / SP logs immediately see the framework
|
|
1938
|
+
// ownership and know these are NOT IANA/W3C-registered. No IETF /
|
|
1939
|
+
// W3C XMLDSig assignment for ML-DSA exists yet; the IETF LAMPS WG
|
|
1940
|
+
// has open drafts (draft-ietf-lamps-x509-mldsa, -lamps-cms-mldsa)
|
|
1941
|
+
// but no XMLDSig URI registration. Once a registered URI exists,
|
|
1942
|
+
// we'll add it alongside and deprecate the experimental one.
|
|
1943
|
+
//
|
|
1944
|
+
// These URNs interop only with peers that share them out-of-band
|
|
1945
|
+
// (e.g. two SPs of the same vendor). Operators integrating with
|
|
1946
|
+
// real-world classical IdPs use the W3C XMLDSig URIs below.
|
|
1947
|
+
if (alg === "ml-dsa-65") {
|
|
1948
|
+
return {
|
|
1949
|
+
urn: "urn:blamejs:experimental:saml-sig-alg:ml-dsa-65",
|
|
1950
|
+
sign: function (bytes, sk) { return pqcSoftware.ml_dsa_65.sign(new Uint8Array(bytes), sk); },
|
|
1951
|
+
verify: function (sig, msg, pk) { return pqcSoftware.ml_dsa_65.verify(sig, msg, pk); },
|
|
1952
|
+
experimental: true,
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
if (alg === "ml-dsa-87") {
|
|
1956
|
+
return {
|
|
1957
|
+
urn: "urn:blamejs:experimental:saml-sig-alg:ml-dsa-87",
|
|
1958
|
+
sign: function (bytes, sk) { return pqcSoftware.ml_dsa_87.sign(new Uint8Array(bytes), sk); },
|
|
1959
|
+
verify: function (sig, msg, pk) { return pqcSoftware.ml_dsa_87.verify(sig, msg, pk); },
|
|
1960
|
+
experimental: true,
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
// Ed25519 — W3C XMLDSig URN registered in RFC 9231.
|
|
1964
|
+
if (alg === "ed25519") {
|
|
1965
|
+
return {
|
|
1966
|
+
urn: "http://www.w3.org/2021/04/xmldsig-more#ed25519",
|
|
1967
|
+
sign: function (bytes, sk) {
|
|
1968
|
+
var keyObj = (sk && typeof sk === "object" && sk.type === "private") ? sk
|
|
1969
|
+
: (typeof sk === "string" || (sk && sk.kty)) ? nodeCrypto.createPrivateKey(sk)
|
|
1970
|
+
: nodeCrypto.createPrivateKey({ key: Buffer.concat([
|
|
1971
|
+
Buffer.from("302e020100300506032b657004220420", "hex"), // allow:raw-byte-literal — Ed25519 PKCS#8 prefix
|
|
1972
|
+
Buffer.from(sk),
|
|
1973
|
+
]), format: "der", type: "pkcs8" });
|
|
1974
|
+
return nodeCrypto.sign(null, Buffer.from(bytes), keyObj);
|
|
1975
|
+
},
|
|
1976
|
+
verify: function (sig, msg, pk) {
|
|
1977
|
+
var keyObj = (pk && typeof pk === "object" && pk.type === "public") ? pk
|
|
1978
|
+
: (typeof pk === "string" || (pk && pk.kty)) ? nodeCrypto.createPublicKey(pk)
|
|
1979
|
+
: nodeCrypto.createPublicKey({ key: Buffer.concat([
|
|
1980
|
+
Buffer.from("302a300506032b6570032100", "hex"), // allow:raw-byte-literal — Ed25519 SPKI prefix
|
|
1981
|
+
Buffer.from(pk),
|
|
1982
|
+
]), format: "der", type: "spki" });
|
|
1983
|
+
return nodeCrypto.verify(null, Buffer.from(msg), keyObj, Buffer.from(sig));
|
|
1984
|
+
},
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
// Classical XMLDSig algorithms registered in W3C XMLDSig Core 1.1 /
|
|
1988
|
+
// RFC 4051. Keys are PEM-formatted strings or node:crypto KeyObject
|
|
1989
|
+
// instances. Operators integrating with real-world IdPs that
|
|
1990
|
+
// haven't moved to PQC use these — RSA-SHA-256 is by far the most
|
|
1991
|
+
// common signing algorithm on the public SAML IdP wire today.
|
|
1992
|
+
var classical = {
|
|
1993
|
+
"rsa-sha256": { urn: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", hash: "sha256" },
|
|
1994
|
+
"rsa-sha384": { urn: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", hash: "sha384" },
|
|
1995
|
+
"rsa-sha512": { urn: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", hash: "sha512" },
|
|
1996
|
+
"ecdsa-sha256": { urn: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", hash: "sha256", ec: true },
|
|
1997
|
+
"ecdsa-sha384": { urn: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", hash: "sha384", ec: true },
|
|
1998
|
+
"ecdsa-sha512": { urn: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", hash: "sha512", ec: true },
|
|
1999
|
+
};
|
|
2000
|
+
if (Object.prototype.hasOwnProperty.call(classical, alg)) {
|
|
2001
|
+
var spec = classical[alg];
|
|
2002
|
+
return {
|
|
2003
|
+
urn: spec.urn,
|
|
2004
|
+
sign: function (bytes, sk) {
|
|
2005
|
+
var keyObj = (sk && typeof sk === "object" && sk.type === "private") ? sk
|
|
2006
|
+
: nodeCrypto.createPrivateKey(sk);
|
|
2007
|
+
var opts2 = { key: keyObj };
|
|
2008
|
+
if (spec.ec) opts2.dsaEncoding = "der";
|
|
2009
|
+
return nodeCrypto.sign(spec.hash, Buffer.from(bytes), opts2);
|
|
2010
|
+
},
|
|
2011
|
+
verify: function (sig, msg, pk) {
|
|
2012
|
+
var keyObj = (pk && typeof pk === "object" && pk.type === "public") ? pk
|
|
2013
|
+
: nodeCrypto.createPublicKey(pk);
|
|
2014
|
+
var opts2 = { key: keyObj };
|
|
2015
|
+
if (spec.ec) opts2.dsaEncoding = "der";
|
|
2016
|
+
return nodeCrypto.verify(spec.hash, Buffer.from(msg), opts2, Buffer.from(sig));
|
|
2017
|
+
},
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// Reverse lookup — SignatureMethod URI on the inbound wire → alg
|
|
2024
|
+
// shorthand for _sigAlgUrn dispatch. Used by _verifyEmbeddedXmlDsig
|
|
2025
|
+
// to pick the right verifier when an IdP signs with a classical alg.
|
|
2026
|
+
function _sigAlgFromUri(uri) {
|
|
2027
|
+
if (uri === "urn:blamejs:experimental:saml-sig-alg:ml-dsa-65") return "ml-dsa-65";
|
|
2028
|
+
if (uri === "urn:blamejs:experimental:saml-sig-alg:ml-dsa-87") return "ml-dsa-87";
|
|
2029
|
+
if (uri === "http://www.w3.org/2021/04/xmldsig-more#ed25519") return "ed25519";
|
|
2030
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") return "rsa-sha256";
|
|
2031
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") return "rsa-sha384";
|
|
2032
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512") return "rsa-sha512";
|
|
2033
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256") return "ecdsa-sha256";
|
|
2034
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384") return "ecdsa-sha384";
|
|
2035
|
+
if (uri === "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512") return "ecdsa-sha512";
|
|
2036
|
+
return null;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
/**
|
|
2040
|
+
* @primitive b.auth.saml.fetchMdq
|
|
2041
|
+
* @signature b.auth.saml.fetchMdq(opts)
|
|
2042
|
+
* @since 0.8.62
|
|
2043
|
+
* @status stable
|
|
2044
|
+
* @related b.auth.saml.sp.create, b.network.tls.checkServerIdentity9525
|
|
2045
|
+
*
|
|
2046
|
+
* Fetch an entity's signed metadata from a Metadata Query (MDQ)
|
|
2047
|
+
* server per SAML 2.0 MDQ. Composes b.httpClient with strict server-
|
|
2048
|
+
* identity (RFC 9525) and verifies the metadata XMLDSig against the
|
|
2049
|
+
* operator-supplied trust cert. Returns the raw metadata XML on
|
|
2050
|
+
* success.
|
|
2051
|
+
*
|
|
2052
|
+
* The MDQ URL pattern is `<baseUrl>/entities/{sha1(entityId)}` per
|
|
2053
|
+
* the spec — operators with a federation MDQ deployment supply the
|
|
2054
|
+
* baseUrl + their pinned trust cert.
|
|
2055
|
+
*
|
|
2056
|
+
* @opts
|
|
2057
|
+
* {
|
|
2058
|
+
* baseUrl: string,
|
|
2059
|
+
* entityId: string,
|
|
2060
|
+
* trustCertPem?: string, // PEM of the federation operator's signing cert
|
|
2061
|
+
* }
|
|
2062
|
+
*
|
|
2063
|
+
* @example
|
|
2064
|
+
* var xml = await b.auth.saml.fetchMdq({
|
|
2065
|
+
* baseUrl: "https://mdq.federation.example",
|
|
2066
|
+
* entityId: "https://idp.example",
|
|
2067
|
+
* trustCertPem: process.env.FEDERATION_TRUST_CERT_PEM,
|
|
2068
|
+
* });
|
|
2069
|
+
*/
|
|
2070
|
+
async function fetchMdq(opts) {
|
|
2071
|
+
validateOpts.requireObject(opts, "auth.saml.fetchMdq", AuthError);
|
|
2072
|
+
validateOpts.requireNonEmptyString(opts.baseUrl, "baseUrl", AuthError, "auth-saml/no-mdq-base");
|
|
2073
|
+
validateOpts.requireNonEmptyString(opts.entityId, "entityId", AuthError, "auth-saml/no-mdq-entity");
|
|
2074
|
+
var hash = nodeCrypto.createHash("sha1").update(opts.entityId, "utf8").digest("hex");
|
|
2075
|
+
var url = opts.baseUrl.replace(/\/$/, "") + "/entities/%7Bsha1%7D" + hash;
|
|
2076
|
+
var hc = httpClient();
|
|
2077
|
+
var res = await hc.request({
|
|
2078
|
+
url: url,
|
|
2079
|
+
method: "GET",
|
|
2080
|
+
headers: { Accept: "application/samlmetadata+xml" },
|
|
2081
|
+
});
|
|
2082
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
2083
|
+
throw new AuthError("auth-saml/mdq-fetch-failed",
|
|
2084
|
+
"fetchMdq " + url + " returned " + res.statusCode);
|
|
2085
|
+
}
|
|
2086
|
+
if (!res.body || res.body.length === 0) {
|
|
2087
|
+
throw new AuthError("auth-saml/mdq-empty",
|
|
2088
|
+
"fetchMdq " + url + " returned empty body");
|
|
2089
|
+
}
|
|
2090
|
+
var xml = res.body.toString("utf8");
|
|
2091
|
+
if (opts.trustCertPem) {
|
|
2092
|
+
var c14n = xmlC14n();
|
|
2093
|
+
var root = c14n.parse(xml);
|
|
2094
|
+
var sig = _findChild(root, "Signature");
|
|
2095
|
+
if (!sig) {
|
|
2096
|
+
throw new AuthError("auth-saml/mdq-unsigned",
|
|
2097
|
+
"fetchMdq: metadata is unsigned but trustCertPem was supplied");
|
|
2098
|
+
}
|
|
2099
|
+
_verifyXmldsig(xml, sig, opts.trustCertPem);
|
|
2100
|
+
}
|
|
2101
|
+
_emitAudit("mdq_fetched", "success", { entityId: opts.entityId });
|
|
2102
|
+
_emitMetric("mdq-fetched");
|
|
2103
|
+
return xml;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
module.exports = {
|
|
2107
|
+
sp: { create: create },
|
|
2108
|
+
fetchMdq: fetchMdq,
|
|
2109
|
+
};
|