@blamejs/blamejs-shop 0.5.12 → 0.5.14
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 +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/sales-tax-filings.js +143 -0
- package/lib/vendor/MANIFEST.json +632 -416
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
- package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
- package/lib/vendor/blamejs/.gitignore +2 -0
- package/lib/vendor/blamejs/CHANGELOG.md +122 -0
- package/lib/vendor/blamejs/NOTICE +26 -1
- package/lib/vendor/blamejs/SECURITY.md +1 -1
- package/lib/vendor/blamejs/api-snapshot.json +207 -5
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
- package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
- package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
- package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
- package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/a2a.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
- package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
- package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
- package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
- package/lib/vendor/blamejs/lib/archive-read.js +8 -0
- package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
- package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
- package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
- package/lib/vendor/blamejs/lib/audit.js +71 -8
- package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
- package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
- package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
- package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
- package/lib/vendor/blamejs/lib/backup/index.js +16 -5
- package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
- package/lib/vendor/blamejs/lib/base32.js +21 -0
- package/lib/vendor/blamejs/lib/break-glass.js +19 -10
- package/lib/vendor/blamejs/lib/bundler.js +5 -1
- package/lib/vendor/blamejs/lib/cache-status.js +3 -7
- package/lib/vendor/blamejs/lib/calendar.js +21 -1
- package/lib/vendor/blamejs/lib/cbor.js +12 -1
- package/lib/vendor/blamejs/lib/cert.js +29 -11
- package/lib/vendor/blamejs/lib/cli.js +94 -24
- package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
- package/lib/vendor/blamejs/lib/content-digest.js +2 -1
- package/lib/vendor/blamejs/lib/cookies.js +13 -3
- package/lib/vendor/blamejs/lib/cose.js +31 -16
- package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
- package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
- package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
- package/lib/vendor/blamejs/lib/crypto.js +314 -19
- package/lib/vendor/blamejs/lib/csp.js +11 -3
- package/lib/vendor/blamejs/lib/daemon.js +270 -24
- package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
- package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
- package/lib/vendor/blamejs/lib/db-query.js +34 -7
- package/lib/vendor/blamejs/lib/db.js +18 -6
- package/lib/vendor/blamejs/lib/dbsc.js +17 -0
- package/lib/vendor/blamejs/lib/dev.js +21 -2
- package/lib/vendor/blamejs/lib/dsr.js +157 -102
- package/lib/vendor/blamejs/lib/external-db.js +41 -2
- package/lib/vendor/blamejs/lib/file-upload.js +25 -7
- package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
- package/lib/vendor/blamejs/lib/forms.js +113 -27
- package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
- package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
- package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
- package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
- package/lib/vendor/blamejs/lib/guard-html.js +26 -53
- package/lib/vendor/blamejs/lib/guard-image.js +24 -5
- package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
- package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
- package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
- package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
- package/lib/vendor/blamejs/lib/guard-time.js +1 -1
- package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
- package/lib/vendor/blamejs/lib/html-balance.js +3 -3
- package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
- package/lib/vendor/blamejs/lib/http-client.js +39 -7
- package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
- package/lib/vendor/blamejs/lib/i18n.js +35 -21
- package/lib/vendor/blamejs/lib/incident-report.js +8 -1
- package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
- package/lib/vendor/blamejs/lib/json-path.js +23 -1
- package/lib/vendor/blamejs/lib/jwk.js +20 -3
- package/lib/vendor/blamejs/lib/link-header.js +2 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream.js +8 -3
- package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
- package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
- package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
- package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
- package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
- package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
- package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
- package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
- package/lib/vendor/blamejs/lib/mail.js +15 -5
- package/lib/vendor/blamejs/lib/metrics.js +268 -63
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
- package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
- package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
- package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
- package/lib/vendor/blamejs/lib/network-dns.js +21 -2
- package/lib/vendor/blamejs/lib/network-tls.js +69 -7
- package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
- package/lib/vendor/blamejs/lib/permissions.js +6 -3
- package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
- package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
- package/lib/vendor/blamejs/lib/pubsub.js +20 -9
- package/lib/vendor/blamejs/lib/queue-local.js +8 -1
- package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
- package/lib/vendor/blamejs/lib/redact.js +54 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +12 -4
- package/lib/vendor/blamejs/lib/router.js +60 -6
- package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
- package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
- package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
- package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
- package/lib/vendor/blamejs/lib/safe-object.js +80 -0
- package/lib/vendor/blamejs/lib/safe-path.js +52 -4
- package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
- package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
- package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
- package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
- package/lib/vendor/blamejs/lib/scheduler.js +8 -1
- package/lib/vendor/blamejs/lib/security-assert.js +6 -6
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
- package/lib/vendor/blamejs/lib/self-update.js +517 -87
- package/lib/vendor/blamejs/lib/server-timing.js +2 -4
- package/lib/vendor/blamejs/lib/session.js +34 -12
- package/lib/vendor/blamejs/lib/sql.js +15 -1
- package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
- package/lib/vendor/blamejs/lib/static.js +55 -13
- package/lib/vendor/blamejs/lib/storage.js +8 -2
- package/lib/vendor/blamejs/lib/subject.js +6 -1
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
- package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
- package/lib/vendor/blamejs/lib/tsa.js +34 -13
- package/lib/vendor/blamejs/lib/uri-template.js +8 -1
- package/lib/vendor/blamejs/lib/vault/index.js +13 -0
- package/lib/vendor/blamejs/lib/vc.js +44 -5
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
- package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
- package/lib/vendor/blamejs/lib/watcher.js +89 -17
- package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
- package/lib/vendor/blamejs/lib/webhook.js +12 -2
- package/lib/vendor/blamejs/lib/ws-client.js +32 -1
- package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
- package/lib/vendor/blamejs/package-lock.json +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
- package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
- package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
- package/lib/vendor/blamejs/socket.yml +94 -0
- package/lib/vendor/blamejs/test/00-primitives.js +605 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
- package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
- package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
- 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 +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
- package/lib/vendor/blamejs/test/smoke.js +2 -0
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
- package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
- package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
- package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
- package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
- package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
|
@@ -28,36 +28,50 @@ var check = helpers.check;
|
|
|
28
28
|
|
|
29
29
|
var fwkErr = require("../../lib/framework-error");
|
|
30
30
|
|
|
31
|
-
// Mint
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
31
|
+
// Mint a self-signed cert + matching private-key PEM via the vendored pki
|
|
32
|
+
// bundle. `opts.algo` selects the key type ("RSA" default / "EC" P-256 /
|
|
33
|
+
// "Ed25519") so a single builder drives the RS256 / ES256 / alg-mismatch JWS
|
|
34
|
+
// verify branches; `opts.ca` toggles basicConstraints cA (false exercises the
|
|
35
|
+
// fingerprint-anchor fallback, which accepts a pinned non-CA leaf only on an
|
|
36
|
+
// exact fingerprint match); `opts.notBefore` / `opts.notAfter` drive the
|
|
37
|
+
// not-yet-valid / expired validity-window refusals. The cert is used as both
|
|
38
|
+
// the JWS leaf (x5c[0]) and the trust root passed via caCertificate so the
|
|
39
|
+
// chain anchors against itself — the operator escape-hatch the primitive docs.
|
|
40
|
+
async function _makeCert(opts) {
|
|
41
|
+
opts = opts || {};
|
|
38
42
|
var pki = require("../../lib/vendor/pki.cjs");
|
|
39
43
|
var x509 = pki.x509;
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
modulusLength: 2048, // allow:raw-byte-literal — RSA modulus bits
|
|
43
|
-
publicExponent: new Uint8Array([1, 0, 1]), // allow:raw-byte-literal — RSA F4 exponent
|
|
44
|
-
hash: "SHA-256" },
|
|
45
|
-
true, ["sign", "verify"]);
|
|
44
|
+
var algo = opts.algo || "RSA";
|
|
45
|
+
var isCa = opts.ca === undefined ? true : opts.ca;
|
|
46
46
|
var now = new Date();
|
|
47
|
-
var
|
|
47
|
+
var notBefore = opts.notBefore || now;
|
|
48
|
+
var notAfter = opts.notAfter || new Date(now.getTime() + 7 * 86400000); // allow:raw-byte-literal — fixture validity ms
|
|
49
|
+
var genAlg, signAlg;
|
|
50
|
+
if (algo === "EC") {
|
|
51
|
+
genAlg = { name: "ECDSA", namedCurve: "P-256" };
|
|
52
|
+
signAlg = { name: "ECDSA", hash: "SHA-256" };
|
|
53
|
+
} else if (algo === "Ed25519") {
|
|
54
|
+
genAlg = { name: "Ed25519" };
|
|
55
|
+
signAlg = { name: "Ed25519" };
|
|
56
|
+
} else {
|
|
57
|
+
genAlg = { name: "RSASSA-PKCS1-v1_5",
|
|
58
|
+
modulusLength: 2048, // allow:raw-byte-literal — RSA modulus bits
|
|
59
|
+
publicExponent: new Uint8Array([1, 0, 1]), // allow:raw-byte-literal — RSA F4 exponent
|
|
60
|
+
hash: "SHA-256" };
|
|
61
|
+
signAlg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
|
|
62
|
+
}
|
|
63
|
+
var keys = await nodeCrypto.webcrypto.subtle.generateKey(genAlg, true, ["sign", "verify"]);
|
|
64
|
+
var exts = [ new x509.BasicConstraintsExtension(isCa, 0, true) ];
|
|
65
|
+
if (isCa) exts.push(new x509.KeyUsagesExtension(
|
|
66
|
+
x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.digitalSignature, true));
|
|
48
67
|
var cert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
49
|
-
serialNumber: "01",
|
|
50
|
-
name: "CN=blamejs-mds3-test-root",
|
|
51
|
-
notBefore:
|
|
68
|
+
serialNumber: opts.serial || "01",
|
|
69
|
+
name: "CN=" + (opts.cn || "blamejs-mds3-test-root"),
|
|
70
|
+
notBefore: notBefore,
|
|
52
71
|
notAfter: notAfter,
|
|
53
|
-
signingAlgorithm:
|
|
72
|
+
signingAlgorithm: signAlg,
|
|
54
73
|
keys: keys,
|
|
55
|
-
extensions:
|
|
56
|
-
new x509.BasicConstraintsExtension(true, 0, true),
|
|
57
|
-
new x509.KeyUsagesExtension(
|
|
58
|
-
x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.digitalSignature,
|
|
59
|
-
true),
|
|
60
|
-
],
|
|
74
|
+
extensions: exts,
|
|
61
75
|
});
|
|
62
76
|
var pkcs8 = await nodeCrypto.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
|
|
63
77
|
var pkB64 = Buffer.from(pkcs8).toString("base64").match(/.{1,64}/g).join("\n"); // allow:raw-byte-literal — RFC 7468 line width
|
|
@@ -65,35 +79,108 @@ async function _makeSelfSignedRsaCert() {
|
|
|
65
79
|
return { keyPem: keyPem, certPem: cert.toString("pem") };
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
// RS256 self-signed cert — the common case, matching the real FIDO Alliance
|
|
83
|
+
// BLOB shape. Thin wrapper over the general builder.
|
|
84
|
+
async function _makeSelfSignedRsaCert() {
|
|
85
|
+
return _makeCert({});
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
function _b64url(buf) {
|
|
69
89
|
return Buffer.from(buf).toString("base64url");
|
|
70
90
|
}
|
|
71
91
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.replace(/-----BEGIN CERTIFICATE-----/g, "")
|
|
78
|
-
.replace(/-----END CERTIFICATE-----/g, "")
|
|
79
|
-
.replace(/\s+/g, "");
|
|
80
|
-
var header = { alg: "RS256", typ: "JWT", x5c: [derB64] };
|
|
92
|
+
// Assemble the compact JWS from a caller-supplied header + payload, signing the
|
|
93
|
+
// signing-input with `hashAlgo` + `signOpts` (node:crypto.sign options). One
|
|
94
|
+
// core so the RS256 / ES256 / PS256 / Ed25519 fixture builders don't each
|
|
95
|
+
// re-roll the base64url-segment-then-sign body.
|
|
96
|
+
function _makeBlobSig(header, payload, keyPem, hashAlgo, signOpts) {
|
|
81
97
|
var headerB64 = _b64url(JSON.stringify(header));
|
|
82
98
|
var payloadB64 = _b64url(JSON.stringify(payload));
|
|
83
99
|
var signingInput = headerB64 + "." + payloadB64;
|
|
84
|
-
var sig = nodeCrypto.sign(
|
|
85
|
-
|
|
86
|
-
padding: nodeCrypto.constants.RSA_PKCS1_PADDING,
|
|
87
|
-
});
|
|
100
|
+
var sig = nodeCrypto.sign(hashAlgo, Buffer.from(signingInput, "ascii"),
|
|
101
|
+
Object.assign({ key: keyPem }, signOpts || {}));
|
|
88
102
|
return signingInput + "." + _b64url(sig);
|
|
89
103
|
}
|
|
90
104
|
|
|
91
|
-
//
|
|
105
|
+
// Build a synthetic JWS-signed MDS3 BLOB. payload is the inner JSON;
|
|
106
|
+
// keyPem signs it with RS256; certPem is the leaf x5c entry.
|
|
107
|
+
function _makeBlob(payload, keyPem, certPem) {
|
|
108
|
+
var header = { alg: "RS256", typ: "JWT", x5c: [_certDerB64(certPem)] };
|
|
109
|
+
return _makeBlobSig(header, payload, keyPem, "sha256",
|
|
110
|
+
{ padding: nodeCrypto.constants.RSA_PKCS1_PADDING });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// In-future date (YYYY-MM-DD) so nextUpdate parsing succeeds. A negative
|
|
114
|
+
// argument produces a PAST date (used to drive the stale-BLOB refusal).
|
|
92
115
|
function _futureDateString(daysFromNow) {
|
|
93
116
|
var d = new Date(Date.now() + daysFromNow * 86400000); // allow:raw-byte-literal — ms-per-day
|
|
94
117
|
return d.toISOString().slice(0, 10); // allow:raw-byte-literal — ISO date prefix length
|
|
95
118
|
}
|
|
96
119
|
|
|
120
|
+
// Strip PEM markers + whitespace from a cert to get its DER-base64 (x5c form).
|
|
121
|
+
function _certDerB64(certPem) {
|
|
122
|
+
return certPem
|
|
123
|
+
.replace(/-----BEGIN CERTIFICATE-----/g, "")
|
|
124
|
+
.replace(/-----END CERTIFICATE-----/g, "")
|
|
125
|
+
.replace(/\s+/g, "");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Like _makeBlob but the caller supplies the full JWS header (so a test can
|
|
129
|
+
// override alg / x5c to exercise the header-adversarial branches). The payload
|
|
130
|
+
// is still RS256-signed with keyPem, so the signature segment is valid,
|
|
131
|
+
// non-empty base64url even when the header advertises a different alg — the
|
|
132
|
+
// verifier reaches its alg check before the signature check.
|
|
133
|
+
function _makeBlobRaw(header, payload, keyPem) {
|
|
134
|
+
return _makeBlobSig(header, payload, keyPem, "sha256",
|
|
135
|
+
{ padding: nodeCrypto.constants.RSA_PKCS1_PADDING });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Mock httpClient.request with `handler`, reload fido-mds3 so it re-resolves
|
|
139
|
+
// the lazyRequire'd httpClient against the mock, run fn(fm), then restore the
|
|
140
|
+
// require cache. Mirrors testFetchRoundTrip's require-cache override so no real
|
|
141
|
+
// network handle is ever opened.
|
|
142
|
+
async function _withMockedHttp(handler, fn) {
|
|
143
|
+
var hcPath = require.resolve("../../lib/http-client");
|
|
144
|
+
var origHc = require.cache[hcPath].exports;
|
|
145
|
+
require.cache[hcPath].exports = Object.assign({}, origHc, { request: handler });
|
|
146
|
+
var fmPath = require.resolve("../../lib/auth/fido-mds3");
|
|
147
|
+
delete require.cache[fmPath];
|
|
148
|
+
var fm = require(fmPath);
|
|
149
|
+
try {
|
|
150
|
+
return await fn(fm);
|
|
151
|
+
} finally {
|
|
152
|
+
require.cache[hcPath].exports = origHc;
|
|
153
|
+
delete require.cache[fmPath];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A request handler returning a 200 with `token` as the body.
|
|
158
|
+
function _respondWith(token) {
|
|
159
|
+
return async function () {
|
|
160
|
+
return { statusCode: 200, headers: {}, body: Buffer.from(token, "ascii") }; // allow:raw-byte-literal — HTTP success
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Patch the vendored SettingsService.getRootCertificates (the DEFAULT-trust-root
|
|
165
|
+
// resolver used when no caCertificate override is supplied), run fn, restore.
|
|
166
|
+
// `fakeGetter` receives the { identifier } arg fido-mds3 passes and returns the
|
|
167
|
+
// PEM array (or throws) so the no-trust-root fail-closed branch and the
|
|
168
|
+
// default-roots stale refusal can be driven without a live vendored bundle
|
|
169
|
+
// edit. fido-mds3 reads _wa.SettingsService.getRootCertificates dynamically at
|
|
170
|
+
// resolve time, so no module reload is needed — the same cached vendor object
|
|
171
|
+
// backs both this test and the primitive.
|
|
172
|
+
async function _withMockedRoots(fakeGetter, fn) {
|
|
173
|
+
var wa = require("../../lib/vendor/simplewebauthn-server.cjs");
|
|
174
|
+
var svc = wa.SettingsService;
|
|
175
|
+
var orig = svc.getRootCertificates;
|
|
176
|
+
svc.getRootCertificates = fakeGetter;
|
|
177
|
+
try {
|
|
178
|
+
return await fn();
|
|
179
|
+
} finally {
|
|
180
|
+
svc.getRootCertificates = orig;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
97
184
|
// ---- surface ----
|
|
98
185
|
|
|
99
186
|
function testSurface() {
|
|
@@ -346,6 +433,86 @@ function testCertifiedLevelPlus() {
|
|
|
346
433
|
rv.certifiedLevel.level === 3 && rv.certifiedLevel.plus === true);
|
|
347
434
|
}
|
|
348
435
|
|
|
436
|
+
function testVerifyAuthenticatorAttestationKeyCompromise() {
|
|
437
|
+
// ATTESTATION_KEY_COMPROMISE is in the FIDO MDS3 3.1.4 compromise bucket: the
|
|
438
|
+
// manufacturer's batch-signing key is suspect, so every credential attested
|
|
439
|
+
// under it MUST be refused. Sibling of the REVOKED / USER_KEY_* refusals but
|
|
440
|
+
// previously untested.
|
|
441
|
+
var blob = {
|
|
442
|
+
entries: [{
|
|
443
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
444
|
+
metadataStatement: { description: "Compromised batch key" },
|
|
445
|
+
statusReports: [{ status: "ATTESTATION_KEY_COMPROMISE" }],
|
|
446
|
+
}],
|
|
447
|
+
};
|
|
448
|
+
var rv = b.auth.fidoMds3.verifyAuthenticator(blob, {
|
|
449
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
450
|
+
});
|
|
451
|
+
check("verifyAuthenticator ok=false on ATTESTATION_KEY_COMPROMISE", rv.ok === false);
|
|
452
|
+
check("verifyAuthenticator reason cites ATTESTATION_KEY_COMPROMISE",
|
|
453
|
+
rv.reason && /ATTESTATION_KEY_COMPROMISE/.test(rv.reason));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function testCertifiedLevelMissingDateOrdering() {
|
|
457
|
+
// A decertification (NOT_FIDO_CERTIFIED) appended AFTER a dated grant but
|
|
458
|
+
// WITHOUT its own effectiveDate must still win by array order (append order
|
|
459
|
+
// == chronological per spec). If the undated report is coerced to "" and run
|
|
460
|
+
// through a lexical compare it loses to every earlier DATED report, so
|
|
461
|
+
// certifiedLevel freezes at the stale L2 and a step-up policy requiring L2
|
|
462
|
+
// would accept a now-decertified authenticator (authenticator-assurance
|
|
463
|
+
// bypass).
|
|
464
|
+
var decertBlob = {
|
|
465
|
+
entries: [{
|
|
466
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
467
|
+
statusReports: [
|
|
468
|
+
{ status: "FIDO_CERTIFIED_L2", effectiveDate: "2020-01-01" },
|
|
469
|
+
{ status: "NOT_FIDO_CERTIFIED" }, // later in array, no effectiveDate
|
|
470
|
+
],
|
|
471
|
+
}],
|
|
472
|
+
};
|
|
473
|
+
var rv = b.auth.fidoMds3.verifyAuthenticator(decertBlob, {
|
|
474
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
475
|
+
});
|
|
476
|
+
check("certifiedLevel: undated later NOT_FIDO_CERTIFIED decertifies to level 0",
|
|
477
|
+
rv.certifiedLevel.level === 0 && rv.certifiedLevel.plus === false);
|
|
478
|
+
|
|
479
|
+
// A downgrade whose newer level report has no effectiveDate must report the
|
|
480
|
+
// CURRENT (later) L1, not the historical dated L3.
|
|
481
|
+
var downgradeBlob = {
|
|
482
|
+
entries: [{
|
|
483
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
484
|
+
statusReports: [
|
|
485
|
+
{ status: "FIDO_CERTIFIED_L3", effectiveDate: "2019-01-01" },
|
|
486
|
+
{ status: "FIDO_CERTIFIED_L1" }, // later in array, no effectiveDate
|
|
487
|
+
],
|
|
488
|
+
}],
|
|
489
|
+
};
|
|
490
|
+
var rv2 = b.auth.fidoMds3.verifyAuthenticator(downgradeBlob, {
|
|
491
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
492
|
+
});
|
|
493
|
+
check("certifiedLevel: undated later downgrade reports current L1, not historical L3",
|
|
494
|
+
rv2.certifiedLevel.level === 1);
|
|
495
|
+
|
|
496
|
+
// Symmetric case: an UPGRADE (later, higher level) whose effectiveDate is
|
|
497
|
+
// absent must also win by array order and report the current L3 — not be
|
|
498
|
+
// suppressed because its coerced "" loses the lexical compare against the
|
|
499
|
+
// earlier dated L1.
|
|
500
|
+
var upgradeBlob = {
|
|
501
|
+
entries: [{
|
|
502
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
503
|
+
statusReports: [
|
|
504
|
+
{ status: "FIDO_CERTIFIED_L1", effectiveDate: "2024-01-01" },
|
|
505
|
+
{ status: "FIDO_CERTIFIED_L3" }, // later in array, no effectiveDate
|
|
506
|
+
],
|
|
507
|
+
}],
|
|
508
|
+
};
|
|
509
|
+
var rv3 = b.auth.fidoMds3.verifyAuthenticator(upgradeBlob, {
|
|
510
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
511
|
+
});
|
|
512
|
+
check("certifiedLevel: undated later upgrade reports current L3, not earlier L1",
|
|
513
|
+
rv3.certifiedLevel.level === 3);
|
|
514
|
+
}
|
|
515
|
+
|
|
349
516
|
// ---- fetch (synthetic BLOB) ----
|
|
350
517
|
|
|
351
518
|
async function testFetchRoundTrip() {
|
|
@@ -401,6 +568,207 @@ async function testFetchRoundTrip() {
|
|
|
401
568
|
}
|
|
402
569
|
}
|
|
403
570
|
|
|
571
|
+
async function testFetchRejectsStaleBlob() {
|
|
572
|
+
// A signed-but-expired BLOB (nextUpdate in the past) must be refused by the
|
|
573
|
+
// operator-facing fetch path, not accepted and cached. The internal
|
|
574
|
+
// _verifyAndParseBlob refuses stale BLOBs; fetch is the path operators
|
|
575
|
+
// actually drive, and it must enforce the same refusal (otherwise an attacker
|
|
576
|
+
// serving an ancient BLOB freezes the revoked-authenticator list at a chosen
|
|
577
|
+
// time).
|
|
578
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
579
|
+
var payload = {
|
|
580
|
+
legalHeader: "Stale BLOB",
|
|
581
|
+
no: 7,
|
|
582
|
+
nextUpdate: _futureDateString(-7), // 7 days in the PAST — stale
|
|
583
|
+
entries: [{
|
|
584
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
585
|
+
metadataStatement: { description: "Stale entry" },
|
|
586
|
+
statusReports: [{ status: "REVOKED", effectiveDate: "2020-01-01" }],
|
|
587
|
+
}],
|
|
588
|
+
};
|
|
589
|
+
var token = _makeBlob(payload, pair.keyPem, pair.certPem);
|
|
590
|
+
var threw = null;
|
|
591
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
592
|
+
try {
|
|
593
|
+
await fm.fetch({ url: "https://stale.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
594
|
+
} catch (e) { threw = e; }
|
|
595
|
+
});
|
|
596
|
+
check("fetch refuses a stale BLOB (nextUpdate in the past)",
|
|
597
|
+
threw && /blob-stale/.test(threw.code || ""));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function testFetchTamperedSignature() {
|
|
601
|
+
// Swap the payload segment after signing, keep the original header + sig: the
|
|
602
|
+
// signature no longer covers the payload, so the verify seam must refuse.
|
|
603
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
604
|
+
var token = _makeBlob({
|
|
605
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
606
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
607
|
+
}, pair.keyPem, pair.certPem);
|
|
608
|
+
var parts = token.split(".");
|
|
609
|
+
var forgedPayload = _b64url(JSON.stringify({
|
|
610
|
+
no: 999, nextUpdate: _futureDateString(7),
|
|
611
|
+
entries: [{ aaguid: "ffffffff-ffff-ffff-ffff-ffffffffffff", statusReports: [] }],
|
|
612
|
+
}));
|
|
613
|
+
var forged = parts[0] + "." + forgedPayload + "." + parts[2];
|
|
614
|
+
var threw = null;
|
|
615
|
+
await _withMockedHttp(_respondWith(forged), async function (fm) {
|
|
616
|
+
try {
|
|
617
|
+
await fm.fetch({ url: "https://tamper.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
618
|
+
} catch (e) { threw = e; }
|
|
619
|
+
});
|
|
620
|
+
check("fetch refuses a BLOB whose signature does not cover the payload",
|
|
621
|
+
threw && /bad-signature/.test(threw.code || ""));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
async function testFetchWrongTrustRoot() {
|
|
625
|
+
// The BLOB is signed by `signer`; the operator pins a DIFFERENT root
|
|
626
|
+
// (`stranger`). The x5c tail cannot anchor to it -> chain-not-anchored.
|
|
627
|
+
var signer = await _makeSelfSignedRsaCert();
|
|
628
|
+
var stranger = await _makeSelfSignedRsaCert();
|
|
629
|
+
var token = _makeBlob({
|
|
630
|
+
no: 2, nextUpdate: _futureDateString(7),
|
|
631
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
632
|
+
}, signer.keyPem, signer.certPem);
|
|
633
|
+
var threw = null;
|
|
634
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
635
|
+
try {
|
|
636
|
+
await fm.fetch({ url: "https://wrongroot.invalid/mds3", caCertificate: stranger.certPem, force: true });
|
|
637
|
+
} catch (e) { threw = e; }
|
|
638
|
+
});
|
|
639
|
+
check("fetch refuses a BLOB that does not anchor to the pinned trust root",
|
|
640
|
+
threw && /chain-not-anchored/.test(threw.code || ""));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async function testFetchUnsupportedAlg() {
|
|
644
|
+
// HS256 (HMAC) is the canonical alg-confusion vector; the MDS3 verifier's alg
|
|
645
|
+
// table has no HMAC/none entry, so it must refuse rather than treat the leaf
|
|
646
|
+
// cert's public key as an HMAC secret. The chain still anchors (self-signed
|
|
647
|
+
// via caCertificate), so the refusal proves the alg gate, not chain failure.
|
|
648
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
649
|
+
var header = { alg: "HS256", typ: "JWT", x5c: [_certDerB64(pair.certPem)] };
|
|
650
|
+
var token = _makeBlobRaw(header, {
|
|
651
|
+
no: 3, nextUpdate: _futureDateString(7),
|
|
652
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
653
|
+
}, pair.keyPem);
|
|
654
|
+
var threw = null;
|
|
655
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
656
|
+
try {
|
|
657
|
+
await fm.fetch({ url: "https://badalg.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
658
|
+
} catch (e) { threw = e; }
|
|
659
|
+
});
|
|
660
|
+
check("fetch refuses an HMAC alg (no alg-confusion path)",
|
|
661
|
+
threw && /unsupported-alg/.test(threw.code || ""));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async function testFetchRejectsImpossibleNextUpdate() {
|
|
665
|
+
// 2026-02-31 is not a real calendar date; _parseNextUpdate must reject it
|
|
666
|
+
// (Date.UTC would silently normalise it to 2026-03-03 and let a malformed
|
|
667
|
+
// nextUpdate masquerade as a valid future timestamp). Driven through fetch.
|
|
668
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
669
|
+
var token = _makeBlob({
|
|
670
|
+
no: 1, nextUpdate: "2026-02-31",
|
|
671
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
672
|
+
}, pair.keyPem, pair.certPem);
|
|
673
|
+
var threw = null;
|
|
674
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
675
|
+
try {
|
|
676
|
+
await fm.fetch({ url: "https://baddate.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
677
|
+
} catch (e) { threw = e; }
|
|
678
|
+
});
|
|
679
|
+
check("fetch rejects an impossible calendar nextUpdate (2026-02-31)",
|
|
680
|
+
threw && /bad-payload/.test(threw.code || ""));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async function testFetchMalformedBlobHeaders() {
|
|
684
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
685
|
+
var goodPayload = {
|
|
686
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
687
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
688
|
+
};
|
|
689
|
+
var noAlg = _makeBlobRaw({ typ: "JWT", x5c: [_certDerB64(pair.certPem)] }, goodPayload, pair.keyPem);
|
|
690
|
+
var noX5c = _makeBlobRaw({ alg: "RS256", typ: "JWT" }, goodPayload, pair.keyPem);
|
|
691
|
+
var badX5c = _makeBlobRaw({ alg: "RS256", typ: "JWT", x5c: ["!!!not-a-cert!!!"] }, goodPayload, pair.keyPem);
|
|
692
|
+
|
|
693
|
+
async function fetchExpect(token, codeRe, label) {
|
|
694
|
+
var threw = null;
|
|
695
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
696
|
+
try {
|
|
697
|
+
await fm.fetch({ url: "https://malformed.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
698
|
+
} catch (e) { threw = e; }
|
|
699
|
+
});
|
|
700
|
+
check(label, threw && codeRe.test(threw.code || ""));
|
|
701
|
+
}
|
|
702
|
+
await fetchExpect(noAlg, /bad-jws-header/, "fetch rejects a header missing alg");
|
|
703
|
+
await fetchExpect(noX5c, /bad-jws-header/, "fetch rejects a header missing x5c");
|
|
704
|
+
await fetchExpect(badX5c, /bad-x5c/, "fetch rejects an unparseable x5c cert");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function testFetchRejectsBadStatus() {
|
|
708
|
+
// Root resolution (a plain string caCertificate) succeeds before the request;
|
|
709
|
+
// the non-2xx status is refused inside the loader.
|
|
710
|
+
var threw = null;
|
|
711
|
+
await _withMockedHttp(async function () {
|
|
712
|
+
return { statusCode: 404, headers: {}, body: Buffer.from("not found", "ascii") }; // allow:raw-byte-literal — HTTP error
|
|
713
|
+
}, async function (fm) {
|
|
714
|
+
try {
|
|
715
|
+
await fm.fetch({ url: "https://notfound.invalid/mds3", caCertificate: "ca-not-reached", force: true });
|
|
716
|
+
} catch (e) { threw = e; }
|
|
717
|
+
});
|
|
718
|
+
check("fetch refuses a non-2xx HTTP status",
|
|
719
|
+
threw && /bad-status/.test(threw.code || ""));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function testFetchNetworkFailure() {
|
|
723
|
+
var threw = null;
|
|
724
|
+
await _withMockedHttp(async function () {
|
|
725
|
+
throw new Error("ECONNREFUSED synthetic");
|
|
726
|
+
}, async function (fm) {
|
|
727
|
+
try {
|
|
728
|
+
await fm.fetch({ url: "https://down.invalid/mds3", caCertificate: "ca-not-reached", force: true });
|
|
729
|
+
} catch (e) { threw = e; }
|
|
730
|
+
});
|
|
731
|
+
check("fetch wraps a transport failure as fido-mds3/network",
|
|
732
|
+
threw && /fido-mds3\/network/.test(threw.code || ""));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async function testFetchBadCaCertificate() {
|
|
736
|
+
// _resolveRoots runs before any HTTP; each bad shape fails closed with
|
|
737
|
+
// bad-ca. No mock needed — the throw precedes the request.
|
|
738
|
+
async function expectBadCa(ca, label) {
|
|
739
|
+
var threw = null;
|
|
740
|
+
try { await b.auth.fidoMds3.fetch({ url: "https://x.invalid/mds3", caCertificate: ca }); }
|
|
741
|
+
catch (e) { threw = e; }
|
|
742
|
+
check(label, threw && /bad-ca/.test(threw.code || ""));
|
|
743
|
+
}
|
|
744
|
+
await expectBadCa([], "fetch rejects an empty caCertificate array");
|
|
745
|
+
await expectBadCa([123], "fetch rejects a non-string caCertificate array element"); // allow:raw-byte-literal — bad type sample
|
|
746
|
+
await expectBadCa(5, "fetch rejects a non-string/non-array caCertificate"); // allow:raw-byte-literal — bad type sample
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function testFetchCacheHitAndForceBypass() {
|
|
750
|
+
// The cache path: a non-force second call is served from cache (no new HTTP);
|
|
751
|
+
// force:true bypasses the cache and refetches.
|
|
752
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
753
|
+
var token = _makeBlob({
|
|
754
|
+
no: 100, nextUpdate: _futureDateString(7), // allow:raw-byte-literal — fixture identifier
|
|
755
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
756
|
+
}, pair.keyPem, pair.certPem);
|
|
757
|
+
var calls = 0;
|
|
758
|
+
await _withMockedHttp(async function () {
|
|
759
|
+
calls += 1;
|
|
760
|
+
return { statusCode: 200, headers: {}, body: Buffer.from(token, "ascii") }; // allow:raw-byte-literal — HTTP success
|
|
761
|
+
}, async function (fm) {
|
|
762
|
+
var url = "https://cache.invalid/mds3";
|
|
763
|
+
var a = await fm.fetch({ url: url, caCertificate: pair.certPem, force: true });
|
|
764
|
+
check("fetch (force) performs one HTTP call", calls === 1 && a.no === 100); // allow:raw-byte-literal — fixture identifier
|
|
765
|
+
var hit = await fm.fetch({ url: url, caCertificate: pair.certPem });
|
|
766
|
+
check("fetch serves the second (non-force) call from cache", calls === 1 && hit.no === 100); // allow:raw-byte-literal — fixture identifier
|
|
767
|
+
var refetched = await fm.fetch({ url: url, caCertificate: pair.certPem, force: true });
|
|
768
|
+
check("fetch force:true bypasses the cache and refetches", calls === 2 && refetched.no === 100); // allow:raw-byte-literal — fixture identifier
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
404
772
|
async function testFetchRejectsNonHttps() {
|
|
405
773
|
var threw = null;
|
|
406
774
|
try { await b.auth.fidoMds3.fetch({ url: "http://insecure.example/mds3" }); }
|
|
@@ -445,6 +813,473 @@ async function testInternalVerifyRejectsBadJws() {
|
|
|
445
813
|
threw && /bad-jws/.test(threw.code || ""));
|
|
446
814
|
}
|
|
447
815
|
|
|
816
|
+
// ---- fetch: x5c chain validity + anchoring adversarial branches ----
|
|
817
|
+
|
|
818
|
+
async function testFetchExpiredSigningCert() {
|
|
819
|
+
// A signed BLOB whose leaf certificate has already expired must be refused,
|
|
820
|
+
// even though the JWS signature itself would verify. An attacker replaying an
|
|
821
|
+
// ancient BLOB signed under a now-expired cert must not slip past the validity
|
|
822
|
+
// window (fail-open would pin operators to a frozen, possibly-revoked list).
|
|
823
|
+
var expired = await _makeCert({
|
|
824
|
+
notBefore: new Date(Date.now() - 30 * 86400000), // allow:raw-byte-literal — fixture days
|
|
825
|
+
notAfter: new Date(Date.now() - 10 * 86400000), // allow:raw-byte-literal — fixture days
|
|
826
|
+
});
|
|
827
|
+
var token = _makeBlob({
|
|
828
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
829
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
830
|
+
}, expired.keyPem, expired.certPem);
|
|
831
|
+
var threw = null;
|
|
832
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
833
|
+
try {
|
|
834
|
+
await fm.fetch({ url: "https://expired.invalid/mds3", caCertificate: expired.certPem, force: true });
|
|
835
|
+
} catch (e) { threw = e; }
|
|
836
|
+
});
|
|
837
|
+
check("fetch refuses a BLOB whose leaf signing cert has expired",
|
|
838
|
+
threw && /cert-expired/.test(threw.code || ""));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async function testFetchNotYetValidCert() {
|
|
842
|
+
// A leaf certificate whose notBefore is in the future is not yet valid and
|
|
843
|
+
// must be refused — a pre-dated / clock-skew forgery must not validate.
|
|
844
|
+
var future = await _makeCert({
|
|
845
|
+
notBefore: new Date(Date.now() + 20 * 86400000), // allow:raw-byte-literal — fixture days
|
|
846
|
+
notAfter: new Date(Date.now() + 40 * 86400000), // allow:raw-byte-literal — fixture days
|
|
847
|
+
});
|
|
848
|
+
var token = _makeBlob({
|
|
849
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
850
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
851
|
+
}, future.keyPem, future.certPem);
|
|
852
|
+
var threw = null;
|
|
853
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
854
|
+
try {
|
|
855
|
+
await fm.fetch({ url: "https://notyet.invalid/mds3", caCertificate: future.certPem, force: true });
|
|
856
|
+
} catch (e) { threw = e; }
|
|
857
|
+
});
|
|
858
|
+
check("fetch refuses a BLOB whose leaf signing cert is not yet valid",
|
|
859
|
+
threw && /cert-not-yet-valid/.test(threw.code || ""));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
async function testFetchBrokenChain() {
|
|
863
|
+
// A two-cert x5c where the leaf is NOT validly issued by the second link must
|
|
864
|
+
// be refused with chain-broken — a cert spliced into the chain that did not
|
|
865
|
+
// sign the leaf cannot bridge it to the root, even when that second cert is
|
|
866
|
+
// itself the pinned trust anchor.
|
|
867
|
+
var leaf = await _makeCert({ cn: "blamejs-leaf" });
|
|
868
|
+
var other = await _makeCert({ cn: "blamejs-other-ca" });
|
|
869
|
+
var header = { alg: "RS256", typ: "JWT",
|
|
870
|
+
x5c: [_certDerB64(leaf.certPem), _certDerB64(other.certPem)] };
|
|
871
|
+
var token = _makeBlobRaw(header, {
|
|
872
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
873
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
874
|
+
}, leaf.keyPem);
|
|
875
|
+
var threw = null;
|
|
876
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
877
|
+
try {
|
|
878
|
+
await fm.fetch({ url: "https://broken.invalid/mds3", caCertificate: other.certPem, force: true });
|
|
879
|
+
} catch (e) { threw = e; }
|
|
880
|
+
});
|
|
881
|
+
check("fetch refuses a two-cert chain whose leaf is not issued by the next link",
|
|
882
|
+
threw && /chain-broken/.test(threw.code || ""));
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
async function testFetchGarbageRootInArrayStillAnchors() {
|
|
886
|
+
// caCertificate as an ARRAY exercises the array-of-PEMs override path; a
|
|
887
|
+
// garbage PEM among the roots must be skipped (parse-fail -> continue), and a
|
|
888
|
+
// valid trailing root still anchors the chain.
|
|
889
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
890
|
+
var token = _makeBlob({
|
|
891
|
+
no: 5, nextUpdate: _futureDateString(7),
|
|
892
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
893
|
+
metadataStatement: { description: "arr" }, statusReports: [] }],
|
|
894
|
+
}, pair.keyPem, pair.certPem);
|
|
895
|
+
var garbageRoot = "-----BEGIN CERTIFICATE-----\nnot-a-real-cert\n-----END CERTIFICATE-----\n";
|
|
896
|
+
var blob = null, threw = null;
|
|
897
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
898
|
+
try {
|
|
899
|
+
blob = await fm.fetch({ url: "https://arrroot.invalid/mds3",
|
|
900
|
+
caCertificate: [garbageRoot, pair.certPem], force: true });
|
|
901
|
+
} catch (e) { threw = e; }
|
|
902
|
+
});
|
|
903
|
+
check("fetch skips an unparseable root in the caCertificate array and anchors on a valid one",
|
|
904
|
+
!threw && blob && blob.no === 5); // allow:raw-byte-literal — fixture identifier
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function testFetchFingerprintAnchorsPinnedNonCaLeaf() {
|
|
908
|
+
// A self-signed, NON-CA leaf pinned as the trust root anchors via the exact
|
|
909
|
+
// fingerprint256-match fallback: issuerValidlyIssued fails because the pinned
|
|
910
|
+
// cert asserts basicConstraints cA:FALSE, but an operator pinning that exact
|
|
911
|
+
// cert as their root trusts it by identity. Covers the CAs-ship-root-in-x5c
|
|
912
|
+
// convenience branch (a SHA-256 collision would be needed to abuse it).
|
|
913
|
+
var leaf = await _makeCert({ ca: false });
|
|
914
|
+
var token = _makeBlob({
|
|
915
|
+
no: 6, nextUpdate: _futureDateString(7),
|
|
916
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
917
|
+
}, leaf.keyPem, leaf.certPem);
|
|
918
|
+
var blob = null, threw = null;
|
|
919
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
920
|
+
try {
|
|
921
|
+
blob = await fm.fetch({ url: "https://fp.invalid/mds3", caCertificate: leaf.certPem, force: true });
|
|
922
|
+
} catch (e) { threw = e; }
|
|
923
|
+
});
|
|
924
|
+
check("fetch anchors a pinned non-CA leaf via exact fingerprint match",
|
|
925
|
+
!threw && blob && blob.no === 6); // allow:raw-byte-literal — fixture identifier
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// ---- fetch: JWS alg verify branches (ES256 / PS256 / alg-key mismatch) ----
|
|
929
|
+
|
|
930
|
+
async function testFetchEs256() {
|
|
931
|
+
// An ES256 (ECDSA P-256) BLOB exercises the dsaEncoding verify branch. MDS3 is
|
|
932
|
+
// RS256 in practice, but the shared alg table lists ES256; a real ES256 BLOB
|
|
933
|
+
// must validate without a code edit.
|
|
934
|
+
var ec = await _makeCert({ algo: "EC" });
|
|
935
|
+
var header = { alg: "ES256", typ: "JWT", x5c: [_certDerB64(ec.certPem)] };
|
|
936
|
+
var token = _makeBlobSig(header, {
|
|
937
|
+
no: 7, nextUpdate: _futureDateString(7),
|
|
938
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
939
|
+
metadataStatement: { description: "ec" }, statusReports: [] }],
|
|
940
|
+
}, ec.keyPem, "sha256", { dsaEncoding: "ieee-p1363" });
|
|
941
|
+
var blob = null, threw = null;
|
|
942
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
943
|
+
try {
|
|
944
|
+
blob = await fm.fetch({ url: "https://es256.invalid/mds3", caCertificate: ec.certPem, force: true });
|
|
945
|
+
} catch (e) { threw = e; }
|
|
946
|
+
});
|
|
947
|
+
check("fetch verifies an ES256 (ECDSA) BLOB",
|
|
948
|
+
!threw && blob && blob.no === 7); // allow:raw-byte-literal — fixture identifier
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async function testFetchPs256() {
|
|
952
|
+
// A PS256 (RSASSA-PSS) BLOB exercises the saltLength verify branch. The
|
|
953
|
+
// signer's RSA_PSS_SALTLEN_DIGEST salt equals the SHA-256 digest length, which
|
|
954
|
+
// is exactly the salt length the shared verifier expects for PS256.
|
|
955
|
+
var rsa = await _makeSelfSignedRsaCert();
|
|
956
|
+
var header = { alg: "PS256", typ: "JWT", x5c: [_certDerB64(rsa.certPem)] };
|
|
957
|
+
var token = _makeBlobSig(header, {
|
|
958
|
+
no: 8, nextUpdate: _futureDateString(7),
|
|
959
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
960
|
+
}, rsa.keyPem, "sha256", {
|
|
961
|
+
padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
962
|
+
saltLength: nodeCrypto.constants.RSA_PSS_SALTLEN_DIGEST,
|
|
963
|
+
});
|
|
964
|
+
var blob = null, threw = null;
|
|
965
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
966
|
+
try {
|
|
967
|
+
blob = await fm.fetch({ url: "https://ps256.invalid/mds3", caCertificate: rsa.certPem, force: true });
|
|
968
|
+
} catch (e) { threw = e; }
|
|
969
|
+
});
|
|
970
|
+
check("fetch verifies a PS256 (RSASSA-PSS) BLOB",
|
|
971
|
+
!threw && blob && blob.no === 8); // allow:raw-byte-literal — fixture identifier
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async function testFetchAlgKeyTypeMismatch() {
|
|
975
|
+
// A header advertising RS256 (a hash + RSA-padding verify) over a leaf carrying
|
|
976
|
+
// an Ed25519 key makes node:crypto.verify throw ("invalid digest"); the
|
|
977
|
+
// verifier must CATCH it and refuse as bad-signature rather than let the
|
|
978
|
+
// exception crash the request. Alg/key-type confusion is refused, not
|
|
979
|
+
// propagated.
|
|
980
|
+
var ed = await _makeCert({ algo: "Ed25519" });
|
|
981
|
+
var header = { alg: "RS256", typ: "JWT", x5c: [_certDerB64(ed.certPem)] };
|
|
982
|
+
var token = _makeBlobSig(header, {
|
|
983
|
+
no: 9, nextUpdate: _futureDateString(7),
|
|
984
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
985
|
+
}, ed.keyPem, null, {});
|
|
986
|
+
var threw = null;
|
|
987
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
988
|
+
try {
|
|
989
|
+
await fm.fetch({ url: "https://algmix.invalid/mds3", caCertificate: ed.certPem, force: true });
|
|
990
|
+
} catch (e) { threw = e; }
|
|
991
|
+
});
|
|
992
|
+
check("fetch refuses (not crashes) when the leaf key type cannot satisfy the header alg",
|
|
993
|
+
threw && /bad-signature/.test(threw.code || ""));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ---- fetch: JWS parse-segment adversarial branches ----
|
|
997
|
+
|
|
998
|
+
async function testFetchBadJwsSegment() {
|
|
999
|
+
// A JWS whose SIGNATURE segment carries a non-base64url character is refused at
|
|
1000
|
+
// decode (bad-jws-segment). The header/payload segments are decoded inside a
|
|
1001
|
+
// try that rewraps any decode failure as bad-jws-json, so the signature
|
|
1002
|
+
// segment — decoded outside that try — is the one that surfaces the raw
|
|
1003
|
+
// segment error; a mangled sig can't be quietly coerced to empty bytes.
|
|
1004
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1005
|
+
var token = _makeBlob({
|
|
1006
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
1007
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1008
|
+
}, pair.keyPem, pair.certPem);
|
|
1009
|
+
var parts = token.split(".");
|
|
1010
|
+
var mangled = parts[0] + "." + parts[1] + "." + parts[2] + "#bad"; // '#' is not base64url
|
|
1011
|
+
var threw = null;
|
|
1012
|
+
await _withMockedHttp(_respondWith(mangled), async function (fm) {
|
|
1013
|
+
try {
|
|
1014
|
+
await fm.fetch({ url: "https://seg.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1015
|
+
} catch (e) { threw = e; }
|
|
1016
|
+
});
|
|
1017
|
+
check("fetch refuses a JWS signature segment with a non-base64url character",
|
|
1018
|
+
threw && /bad-jws-segment/.test(threw.code || ""));
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async function testFetchBadJwsJson() {
|
|
1022
|
+
// A header segment that is valid base64url but decodes to invalid JSON is
|
|
1023
|
+
// refused as bad-jws-json (the decode succeeds, the parse fails).
|
|
1024
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1025
|
+
var token = _makeBlob({
|
|
1026
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
1027
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1028
|
+
}, pair.keyPem, pair.certPem);
|
|
1029
|
+
var parts = token.split(".");
|
|
1030
|
+
var mangled = _b64url("{ not valid json ") + "." + parts[1] + "." + parts[2];
|
|
1031
|
+
var threw = null;
|
|
1032
|
+
await _withMockedHttp(_respondWith(mangled), async function (fm) {
|
|
1033
|
+
try {
|
|
1034
|
+
await fm.fetch({ url: "https://json.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1035
|
+
} catch (e) { threw = e; }
|
|
1036
|
+
});
|
|
1037
|
+
check("fetch refuses a base64url header that is not valid JSON",
|
|
1038
|
+
threw && /bad-jws-json/.test(threw.code || ""));
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
async function testFetchBadX5cElements() {
|
|
1042
|
+
// x5c array present but the element itself is unusable. An empty-string and a
|
|
1043
|
+
// non-string element are refused as bad-x5c (inside chain validation); an
|
|
1044
|
+
// EMPTY x5c array is refused earlier as bad-jws-header (the header-shape gate).
|
|
1045
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1046
|
+
var goodPayload = {
|
|
1047
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
1048
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1049
|
+
};
|
|
1050
|
+
var emptyStr = _makeBlobRaw({ alg: "RS256", typ: "JWT", x5c: [""] }, goodPayload, pair.keyPem);
|
|
1051
|
+
var nonString = _makeBlobRaw({ alg: "RS256", typ: "JWT", x5c: [123] }, goodPayload, pair.keyPem); // allow:raw-byte-literal — bad x5c type
|
|
1052
|
+
var emptyArr = _makeBlobRaw({ alg: "RS256", typ: "JWT", x5c: [] }, goodPayload, pair.keyPem);
|
|
1053
|
+
async function fetchExpect(token, codeRe, label) {
|
|
1054
|
+
var threw = null;
|
|
1055
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1056
|
+
try {
|
|
1057
|
+
await fm.fetch({ url: "https://x5c.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1058
|
+
} catch (e) { threw = e; }
|
|
1059
|
+
});
|
|
1060
|
+
check(label, threw && codeRe.test(threw.code || ""));
|
|
1061
|
+
}
|
|
1062
|
+
await fetchExpect(emptyStr, /bad-x5c/, "fetch rejects an empty-string x5c element");
|
|
1063
|
+
await fetchExpect(nonString, /bad-x5c/, "fetch rejects a non-string x5c element");
|
|
1064
|
+
await fetchExpect(emptyArr, /bad-jws-header/, "fetch rejects an empty x5c array");
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ---- fetch: payload-shape + nextUpdate adversarial branches ----
|
|
1068
|
+
|
|
1069
|
+
async function testFetchPayloadMissingEntries() {
|
|
1070
|
+
// A validly-signed, anchored BLOB whose payload lacks an entries array is
|
|
1071
|
+
// refused as bad-payload.
|
|
1072
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1073
|
+
var token = _makeBlob({ no: 1, nextUpdate: _futureDateString(7) }, pair.keyPem, pair.certPem);
|
|
1074
|
+
var threw = null;
|
|
1075
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1076
|
+
try {
|
|
1077
|
+
await fm.fetch({ url: "https://noentries.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1078
|
+
} catch (e) { threw = e; }
|
|
1079
|
+
});
|
|
1080
|
+
check("fetch refuses a payload with no entries array",
|
|
1081
|
+
threw && /bad-payload/.test(threw.code || ""));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async function testFetchPayloadMissingNo() {
|
|
1085
|
+
// A payload with entries but no numeric 'no' sequence number is refused.
|
|
1086
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1087
|
+
var token = _makeBlob({ nextUpdate: _futureDateString(7), entries: [] }, pair.keyPem, pair.certPem);
|
|
1088
|
+
var threw = null;
|
|
1089
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1090
|
+
try {
|
|
1091
|
+
await fm.fetch({ url: "https://nono.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1092
|
+
} catch (e) { threw = e; }
|
|
1093
|
+
});
|
|
1094
|
+
check("fetch refuses a payload missing the numeric 'no'",
|
|
1095
|
+
threw && /bad-payload/.test(threw.code || ""));
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async function testFetchBadNextUpdateShapes() {
|
|
1099
|
+
// nextUpdate must be a real YYYY-MM-DD calendar date. A non-string value, a
|
|
1100
|
+
// non-date string, and an out-of-range month each parse to null and are
|
|
1101
|
+
// refused as bad-payload (a malformed nextUpdate must not masquerade as a
|
|
1102
|
+
// valid future timestamp and influence the cache-TTL clamp).
|
|
1103
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1104
|
+
function payload(nu) {
|
|
1105
|
+
return { no: 1, nextUpdate: nu,
|
|
1106
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }] };
|
|
1107
|
+
}
|
|
1108
|
+
var numberNu = _makeBlob(payload(20260101), pair.keyPem, pair.certPem); // allow:raw-byte-literal — non-string nextUpdate
|
|
1109
|
+
var wordNu = _makeBlob(payload("not-a-date"), pair.keyPem, pair.certPem);
|
|
1110
|
+
var badMonth = _makeBlob(payload("2026-13-15"), pair.keyPem, pair.certPem);
|
|
1111
|
+
async function fetchExpect(token, label) {
|
|
1112
|
+
var threw = null;
|
|
1113
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1114
|
+
try {
|
|
1115
|
+
await fm.fetch({ url: "https://nu.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1116
|
+
} catch (e) { threw = e; }
|
|
1117
|
+
});
|
|
1118
|
+
check(label, threw && /bad-payload/.test(threw.code || ""));
|
|
1119
|
+
}
|
|
1120
|
+
await fetchExpect(numberNu, "fetch refuses a non-string nextUpdate");
|
|
1121
|
+
await fetchExpect(wordNu, "fetch refuses a nextUpdate that is not a date");
|
|
1122
|
+
await fetchExpect(badMonth, "fetch refuses a nextUpdate with an out-of-range month");
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function testFetchFarFutureNextUpdateClampsTtl() {
|
|
1126
|
+
// A nextUpdate far beyond the 30-day cache ceiling is still accepted; the TTL
|
|
1127
|
+
// clamp caps the cached lifetime at the ceiling so a runaway nextUpdate can't
|
|
1128
|
+
// freeze trust indefinitely.
|
|
1129
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1130
|
+
var token = _makeBlob({
|
|
1131
|
+
no: 11, nextUpdate: _futureDateString(45), // allow:raw-byte-literal — beyond 30-day ceiling
|
|
1132
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1133
|
+
}, pair.keyPem, pair.certPem);
|
|
1134
|
+
var blob = null, threw = null;
|
|
1135
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1136
|
+
try {
|
|
1137
|
+
blob = await fm.fetch({ url: "https://farfuture.invalid/mds3", caCertificate: pair.certPem, force: true });
|
|
1138
|
+
} catch (e) { threw = e; }
|
|
1139
|
+
});
|
|
1140
|
+
check("fetch accepts a far-future nextUpdate (TTL clamped to the ceiling)",
|
|
1141
|
+
!threw && blob && blob.no === 11); // allow:raw-byte-literal — fixture identifier
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// ---- fetch / parse: default-trust-root resolution branches ----
|
|
1145
|
+
|
|
1146
|
+
async function testFetchNoTrustRootWhenVendorEmpty() {
|
|
1147
|
+
// When the DEFAULT trust-root resolver yields no anchors, fetch must fail
|
|
1148
|
+
// closed with no-trust-root rather than proceed against an empty root set
|
|
1149
|
+
// (which would let any chain "anchor" against nothing). Driven for an empty
|
|
1150
|
+
// return, a null return, and a throwing resolver.
|
|
1151
|
+
async function expectNoRoot(getter, label) {
|
|
1152
|
+
var threw = null;
|
|
1153
|
+
await _withMockedRoots(getter, async function () {
|
|
1154
|
+
try { await b.auth.fidoMds3.fetch({ url: "https://noroot.invalid/mds3" }); }
|
|
1155
|
+
catch (e) { threw = e; }
|
|
1156
|
+
});
|
|
1157
|
+
check(label, threw && /no-trust-root/.test(threw.code || ""));
|
|
1158
|
+
}
|
|
1159
|
+
await expectNoRoot(function () { return []; },
|
|
1160
|
+
"fetch fails closed when the default root set is empty");
|
|
1161
|
+
await expectNoRoot(function () { return null; },
|
|
1162
|
+
"fetch fails closed when the default root resolver returns null");
|
|
1163
|
+
await expectNoRoot(function () { throw new Error("vendor boom"); },
|
|
1164
|
+
"fetch fails closed when the default root resolver throws");
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
async function testParsePathRefusesStaleBlob() {
|
|
1168
|
+
// The internal parse path (_verifyAndParseBlob, DEFAULT vendored roots) must
|
|
1169
|
+
// enforce the same stale-BLOB refusal as fetch — both route through the one
|
|
1170
|
+
// shared verifier. Pinning the default root to a self-signed cert lets the
|
|
1171
|
+
// parse path reach the stale check on an anchoring chain and confirms it
|
|
1172
|
+
// refuses a nextUpdate-in-the-past BLOB (the v0.16.18 fail-open class, on the
|
|
1173
|
+
// non-fetch path).
|
|
1174
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1175
|
+
var token = _makeBlob({
|
|
1176
|
+
no: 1, nextUpdate: _futureDateString(-7), // 7 days in the PAST — stale
|
|
1177
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1178
|
+
}, pair.keyPem, pair.certPem);
|
|
1179
|
+
var threw = null;
|
|
1180
|
+
await _withMockedRoots(function () { return [pair.certPem]; }, async function () {
|
|
1181
|
+
try { b.auth.fidoMds3._verifyAndParseBlob(token); }
|
|
1182
|
+
catch (e) { threw = e; }
|
|
1183
|
+
});
|
|
1184
|
+
check("_verifyAndParseBlob (default-roots parse path) refuses a stale BLOB",
|
|
1185
|
+
threw && /blob-stale/.test(threw.code || ""));
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function testFetchNoArgUsesDefaultRoots() {
|
|
1189
|
+
// fetch() with no argument object exercises the opts-defaulting path and the
|
|
1190
|
+
// real vendored default-root resolution. A synthetic BLOB signed by a test
|
|
1191
|
+
// cert cannot anchor to the genuine FIDO root, so it is refused — proving
|
|
1192
|
+
// fetch() reaches root resolution + chain anchoring even with no opts passed.
|
|
1193
|
+
var pair = await _makeSelfSignedRsaCert();
|
|
1194
|
+
var token = _makeBlob({
|
|
1195
|
+
no: 1, nextUpdate: _futureDateString(7),
|
|
1196
|
+
entries: [{ aaguid: "01234567-89ab-cdef-0123-456789abcdef", statusReports: [] }],
|
|
1197
|
+
}, pair.keyPem, pair.certPem);
|
|
1198
|
+
var threw = null;
|
|
1199
|
+
await _withMockedHttp(_respondWith(token), async function (fm) {
|
|
1200
|
+
try { await fm.fetch(); }
|
|
1201
|
+
catch (e) { threw = e; }
|
|
1202
|
+
});
|
|
1203
|
+
check("fetch() with no opts resolves default roots and refuses an unanchored BLOB",
|
|
1204
|
+
threw && /chain-not-anchored/.test(threw.code || ""));
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
async function testFetchNetworkFailureNonError() {
|
|
1208
|
+
// The transport rejection is normalized to fido-mds3/network even when it is
|
|
1209
|
+
// not a well-formed Error (a message-less value); the network wrapper falls
|
|
1210
|
+
// back to String(e) rather than surfacing "undefined" in the audit + message.
|
|
1211
|
+
var threw = null;
|
|
1212
|
+
await _withMockedHttp(async function () {
|
|
1213
|
+
var rejection = new Error(""); // Error with an empty message
|
|
1214
|
+
throw rejection;
|
|
1215
|
+
}, async function (fm) {
|
|
1216
|
+
try {
|
|
1217
|
+
await fm.fetch({ url: "https://nonerr.invalid/mds3", caCertificate: "ca-not-reached", force: true });
|
|
1218
|
+
} catch (e) { threw = e; }
|
|
1219
|
+
});
|
|
1220
|
+
check("fetch normalizes a message-less transport rejection to fido-mds3/network",
|
|
1221
|
+
threw && /fido-mds3\/network/.test(threw.code || ""));
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// ---- lookupAaguid / certifiedLevel malformed-entry branches ----
|
|
1225
|
+
|
|
1226
|
+
function testLookupAaguidSkipsMalformedEntries() {
|
|
1227
|
+
// Entries that are null or whose aaguid is non-string are skipped during
|
|
1228
|
+
// lookup; a valid later entry still resolves.
|
|
1229
|
+
var blob = {
|
|
1230
|
+
entries: [
|
|
1231
|
+
null,
|
|
1232
|
+
{ aaguid: 12345, metadataStatement: { description: "non-string aaguid" } }, // allow:raw-byte-literal — malformed entry
|
|
1233
|
+
{ aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
1234
|
+
metadataStatement: { description: "good" }, statusReports: [] },
|
|
1235
|
+
],
|
|
1236
|
+
};
|
|
1237
|
+
var hit = b.auth.fidoMds3.lookupAaguid(blob, "01234567-89ab-cdef-0123-456789abcdef");
|
|
1238
|
+
check("lookupAaguid skips null + non-string-aaguid entries and still finds the match",
|
|
1239
|
+
hit && hit.metadataStatement.description === "good");
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function testCertifiedLevelSkipsMalformedReports() {
|
|
1243
|
+
// A null report, a non-string status, and an over-64-char status token are all
|
|
1244
|
+
// skipped when resolving the certified level; only the well-formed grant
|
|
1245
|
+
// counts. Guards the length-bound-before-regex convention (an unbounded status
|
|
1246
|
+
// must never reach the CERT_LEVEL_RE test).
|
|
1247
|
+
var blob = {
|
|
1248
|
+
entries: [{
|
|
1249
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
1250
|
+
statusReports: [
|
|
1251
|
+
null,
|
|
1252
|
+
{ status: 42 }, // allow:raw-byte-literal — non-string status
|
|
1253
|
+
{ status: "A".repeat(65) }, // allow:raw-byte-literal — over-64-char status
|
|
1254
|
+
{ status: "FIDO_CERTIFIED_L1" },
|
|
1255
|
+
],
|
|
1256
|
+
}],
|
|
1257
|
+
};
|
|
1258
|
+
var rv = b.auth.fidoMds3.verifyAuthenticator(blob, {
|
|
1259
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
1260
|
+
});
|
|
1261
|
+
check("certifiedLevel ignores null / non-string / over-long status reports",
|
|
1262
|
+
rv.certifiedLevel.level === 1 && rv.ok === true);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function testVerifyAuthenticatorEntryWithoutStatusReports() {
|
|
1266
|
+
// An entry that omits statusReports (a non-array) is treated as an empty report
|
|
1267
|
+
// list: ok=true, certifiedLevel 0. Exercises the non-array coercion in
|
|
1268
|
+
// verifyAuthenticator.
|
|
1269
|
+
var blob = {
|
|
1270
|
+
entries: [{
|
|
1271
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
1272
|
+
metadataStatement: { description: "no reports" },
|
|
1273
|
+
}],
|
|
1274
|
+
};
|
|
1275
|
+
var rv = b.auth.fidoMds3.verifyAuthenticator(blob, {
|
|
1276
|
+
aaguid: "01234567-89ab-cdef-0123-456789abcdef",
|
|
1277
|
+
});
|
|
1278
|
+
check("verifyAuthenticator treats a missing statusReports as empty (ok=true, level 0)",
|
|
1279
|
+
rv.ok === true && rv.certifiedLevel.level === 0 &&
|
|
1280
|
+
rv.statement && rv.statement.description === "no reports");
|
|
1281
|
+
}
|
|
1282
|
+
|
|
448
1283
|
// ---- run ----
|
|
449
1284
|
|
|
450
1285
|
// fido-mds3.fetch dials the MDS3 endpoint through the shared httpClient
|
|
@@ -471,14 +1306,48 @@ async function run() {
|
|
|
471
1306
|
testVerifyAuthenticatorDecertified();
|
|
472
1307
|
testVerifyAuthenticatorPhysicalCompromise();
|
|
473
1308
|
testVerifyAuthenticatorRemoteCompromise();
|
|
1309
|
+
testVerifyAuthenticatorAttestationKeyCompromise();
|
|
474
1310
|
testVerifyAuthenticatorUnknownAaguid();
|
|
475
1311
|
testVerifyAuthenticatorBadInputs();
|
|
476
1312
|
testCertifiedLevelPlus();
|
|
1313
|
+
testCertifiedLevelMissingDateOrdering();
|
|
477
1314
|
await testFetchRoundTrip();
|
|
1315
|
+
await testFetchRejectsStaleBlob();
|
|
1316
|
+
await testFetchTamperedSignature();
|
|
1317
|
+
await testFetchWrongTrustRoot();
|
|
1318
|
+
await testFetchUnsupportedAlg();
|
|
1319
|
+
await testFetchRejectsImpossibleNextUpdate();
|
|
1320
|
+
await testFetchMalformedBlobHeaders();
|
|
1321
|
+
await testFetchRejectsBadStatus();
|
|
1322
|
+
await testFetchNetworkFailure();
|
|
1323
|
+
await testFetchBadCaCertificate();
|
|
1324
|
+
await testFetchCacheHitAndForceBypass();
|
|
478
1325
|
await testFetchRejectsNonHttps();
|
|
479
1326
|
await testFetchRejectsEmptyUrl();
|
|
480
1327
|
await testFetchRejectsBadTimeout();
|
|
481
1328
|
await testInternalVerifyRejectsBadJws();
|
|
1329
|
+
await testFetchExpiredSigningCert();
|
|
1330
|
+
await testFetchNotYetValidCert();
|
|
1331
|
+
await testFetchBrokenChain();
|
|
1332
|
+
await testFetchGarbageRootInArrayStillAnchors();
|
|
1333
|
+
await testFetchFingerprintAnchorsPinnedNonCaLeaf();
|
|
1334
|
+
await testFetchEs256();
|
|
1335
|
+
await testFetchPs256();
|
|
1336
|
+
await testFetchAlgKeyTypeMismatch();
|
|
1337
|
+
await testFetchBadJwsSegment();
|
|
1338
|
+
await testFetchBadJwsJson();
|
|
1339
|
+
await testFetchBadX5cElements();
|
|
1340
|
+
await testFetchPayloadMissingEntries();
|
|
1341
|
+
await testFetchPayloadMissingNo();
|
|
1342
|
+
await testFetchBadNextUpdateShapes();
|
|
1343
|
+
await testFetchFarFutureNextUpdateClampsTtl();
|
|
1344
|
+
await testFetchNoTrustRootWhenVendorEmpty();
|
|
1345
|
+
await testParsePathRefusesStaleBlob();
|
|
1346
|
+
await testFetchNoArgUsesDefaultRoots();
|
|
1347
|
+
await testFetchNetworkFailureNonError();
|
|
1348
|
+
testLookupAaguidSkipsMalformedEntries();
|
|
1349
|
+
testCertifiedLevelSkipsMalformedReports();
|
|
1350
|
+
testVerifyAuthenticatorEntryWithoutStatusReports();
|
|
482
1351
|
} finally {
|
|
483
1352
|
await _drainTcpHandles();
|
|
484
1353
|
}
|