@blamejs/blamejs-shop 0.5.12 → 0.5.13
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 +2 -0
- package/lib/asset-manifest.json +1 -1
- 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
|
@@ -353,6 +353,31 @@ async function testDkimVerifyHappyPath() {
|
|
|
353
353
|
check("verify: warnings array on pass", Array.isArray(rv[0].warnings));
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
async function testDkimVerifyBareLfMessage() {
|
|
357
|
+
// A message signed on the CRLF wire but read back from a Unix file /
|
|
358
|
+
// mbox (CRs stripped) arrives bare-LF. The public verifier must
|
|
359
|
+
// normalize it to canonical CRLF and verify — not throw an uncaught
|
|
360
|
+
// dkim/missing-body-separator out of the header/body split. RED before
|
|
361
|
+
// the _splitHeadersBody LF->CRLF normalization: verify() threw.
|
|
362
|
+
// Reset the process-local DKIM key cache: a prior test cached a different
|
|
363
|
+
// key under the same example.com/s1 selector, which would otherwise shadow
|
|
364
|
+
// this message's key (the cache is consulted before the injected dnsLookup).
|
|
365
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
366
|
+
var kp = _rsaKeypair();
|
|
367
|
+
var signed = await _signedMessage(kp);
|
|
368
|
+
var bareLf = signed.replace(/\r\n/g, "\n");
|
|
369
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
370
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
371
|
+
var threw = null;
|
|
372
|
+
var rv = null;
|
|
373
|
+
try { rv = await b.mail.dkim.verify(bareLf, { dnsLookup: dnsLookup }); }
|
|
374
|
+
catch (e) { threw = e; }
|
|
375
|
+
check("verify: bare-LF message does not throw", threw === null);
|
|
376
|
+
check("verify: bare-LF message returns a verdict array", Array.isArray(rv));
|
|
377
|
+
check("verify: CRLF-signed message transported bare-LF verifies pass",
|
|
378
|
+
rv && rv[0] && rv[0].result === "pass");
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
async function testDkimVerifyKeyCacheHit() {
|
|
357
382
|
// Same selector/domain twice → second fetch must hit cache.
|
|
358
383
|
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
@@ -798,6 +823,773 @@ async function testArcAmsLTagAppendRefused() {
|
|
|
798
823
|
rv[0] && rv[0].result === "fail");
|
|
799
824
|
}
|
|
800
825
|
|
|
826
|
+
// ---- Coverage: uncovered error / adversarial / defensive / option-default
|
|
827
|
+
// branches (sign + verify). Each drives the REAL b.mail.dkim consumer
|
|
828
|
+
// path with an injected DNS stub — never real DNS. ----
|
|
829
|
+
|
|
830
|
+
// Body-hash (relaxed) helper mirroring lib _bodyHashB64 with the default
|
|
831
|
+
// (no l=) path, so an adversarial signature can carry a body-correct bh=
|
|
832
|
+
// and reach the header / key / algorithm guards past the body-hash gate.
|
|
833
|
+
function _relaxedBodyHash(body) {
|
|
834
|
+
var canon = b.mail.dkim._canonBodyRelaxedForTest(body);
|
|
835
|
+
return nodeCrypto.createHash("sha256").update(Buffer.from(canon, "utf8")).digest("base64");
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Build a message with an arbitrary DKIM-Signature tag list. Used for the
|
|
839
|
+
// adversarial-verify cases whose verdict is decided BEFORE cryptographic
|
|
840
|
+
// verification (v=, x=/t=, missing d/s, missing bh/b, h= without from) — so
|
|
841
|
+
// the b= value need not be a valid signature.
|
|
842
|
+
function _messageWithSig(tags, body) {
|
|
843
|
+
return "From: alice@example.com\r\n" +
|
|
844
|
+
"DKIM-Signature: " + tags.join("; ") + "\r\n" +
|
|
845
|
+
"\r\n" + (body === undefined ? "Hi\r\n" : body);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Build a FULLY-VALID relaxed/relaxed single-signature message (h=from)
|
|
849
|
+
// with optional extra tags (t=, x=, l=) folded into the signed content, so
|
|
850
|
+
// the signature verifies and the run reaches _verifySingleSignature's
|
|
851
|
+
// pass path. Mirrors the manual construction the l= tests already use.
|
|
852
|
+
function _buildRelaxedSigned(kp, domain, selector, extraTags, body) {
|
|
853
|
+
var dkim = b.mail.dkim;
|
|
854
|
+
body = body === undefined ? "Hello world.\r\n" : body;
|
|
855
|
+
var bh = _relaxedBodyHash(body);
|
|
856
|
+
var fromValue = " Alice <alice@" + domain + ">";
|
|
857
|
+
var tags = ["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
858
|
+
"d=" + domain, "s=" + selector, "h=from", "bh=" + bh];
|
|
859
|
+
if (extraTags) tags = tags.concat(extraTags);
|
|
860
|
+
tags.push("b=");
|
|
861
|
+
var unsigned = tags.join("; ");
|
|
862
|
+
var canonHeaders =
|
|
863
|
+
dkim._canonHeaderRelaxedForTest("From", fromValue) +
|
|
864
|
+
dkim._canonHeaderRelaxedForTest("DKIM-Signature", unsigned).replace(/\r\n$/, "");
|
|
865
|
+
var sig = nodeCrypto.createSign("RSA-SHA256").update(canonHeaders)
|
|
866
|
+
.sign(kp.privateKey).toString("base64");
|
|
867
|
+
var finalSig = unsigned.replace(/b=$/, "b=" + sig);
|
|
868
|
+
return "From:" + fromValue + "\r\n" +
|
|
869
|
+
"DKIM-Signature: " + finalSig + "\r\n" +
|
|
870
|
+
"\r\n" + body;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ---- create() input-shape branches ----
|
|
874
|
+
|
|
875
|
+
function testDkimCreateInputBranches() {
|
|
876
|
+
var kp = _rsaKeypair();
|
|
877
|
+
|
|
878
|
+
// headersToSign element that isn't a non-empty string → bad-headers.
|
|
879
|
+
var threw = null;
|
|
880
|
+
try {
|
|
881
|
+
b.mail.dkim.create({ domain: "example.com", selector: "s1",
|
|
882
|
+
privateKey: kp.privateKey, headersToSign: ["from", 123] });
|
|
883
|
+
} catch (e) { threw = e; }
|
|
884
|
+
check("create: non-string headersToSign element → bad-headers",
|
|
885
|
+
threw && /dkim\/bad-headers/.test(threw.code || ""));
|
|
886
|
+
|
|
887
|
+
// privateKey of a non-string / non-object type → missing-private-key.
|
|
888
|
+
threw = null;
|
|
889
|
+
try {
|
|
890
|
+
b.mail.dkim.create({ domain: "example.com", selector: "s1", privateKey: 123 });
|
|
891
|
+
} catch (e) { threw = e; }
|
|
892
|
+
check("create: numeric privateKey → missing-private-key",
|
|
893
|
+
threw && /dkim\/missing-private-key/.test(threw.code || ""));
|
|
894
|
+
|
|
895
|
+
// privateKey as a Buffer (Buffer.isBuffer branch) → parses + signs.
|
|
896
|
+
var bufSigner = b.mail.dkim.create({ domain: "example.com", selector: "s1",
|
|
897
|
+
privateKey: Buffer.from(kp.privateKey, "utf8") });
|
|
898
|
+
check("create: accepts a Buffer PEM privateKey",
|
|
899
|
+
typeof bufSigner.sign === "function");
|
|
900
|
+
|
|
901
|
+
// privateKey as a pre-built crypto.KeyObject (the non-string, non-Buffer
|
|
902
|
+
// object branch — used directly without re-parsing).
|
|
903
|
+
var keyObj = nodeCrypto.createPrivateKey({ key: kp.privateKey, format: "pem" });
|
|
904
|
+
var koSigner = b.mail.dkim.create({ domain: "example.com", selector: "s1",
|
|
905
|
+
privateKey: keyObj });
|
|
906
|
+
var koSigned = koSigner.sign(
|
|
907
|
+
"From: a@example.com\r\nSubject: t\r\n\r\nbody\r\n");
|
|
908
|
+
check("create: accepts a crypto.KeyObject privateKey and signs",
|
|
909
|
+
/^DKIM-Signature: /.test(koSigned));
|
|
910
|
+
|
|
911
|
+
// create() with no argument object at all → opts defaults to {} then the
|
|
912
|
+
// domain guard throws (the opts || {} default branch).
|
|
913
|
+
threw = null;
|
|
914
|
+
try { b.mail.dkim.create(); } catch (e) { threw = e; }
|
|
915
|
+
check("create: no argument → bad-domain",
|
|
916
|
+
threw && /dkim\/bad-domain/.test(threw.code || ""));
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function testDkimSignerAuditDisabled() {
|
|
920
|
+
// audit:false suppresses the sign-success + missing-header audit emits
|
|
921
|
+
// (the auditOn === false option-default branch), and signing still works.
|
|
922
|
+
var kp = _rsaKeypair();
|
|
923
|
+
var signer = b.mail.dkim.create({
|
|
924
|
+
domain: "noaudit.example", selector: "na",
|
|
925
|
+
privateKey: kp.privateKey, audit: false,
|
|
926
|
+
headersToSign: ["from", "subject", "x-absent-header"],
|
|
927
|
+
});
|
|
928
|
+
var signed = signer.sign(
|
|
929
|
+
"From: a@noaudit.example\r\nSubject: t\r\nDate: Mon, 5 May 2026 12:00:00 +0000\r\n" +
|
|
930
|
+
"Message-ID: <n@noaudit.example>\r\n\r\nbody\r\n");
|
|
931
|
+
check("create audit:false → signs with audit emits suppressed",
|
|
932
|
+
/^DKIM-Signature: /.test(signed));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function testDkimEd25519VerifyRoundTrip() {
|
|
936
|
+
// The ed25519-sha256 VERIFY path (RFC 8463): sign then verify against the
|
|
937
|
+
// published k=ed25519 key. Exercises the ed25519 nodeAlgo=null verify
|
|
938
|
+
// branch that the RSA round-trips never reach.
|
|
939
|
+
var kp = _ed25519Keypair();
|
|
940
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
941
|
+
var signer = b.mail.dkim.create({
|
|
942
|
+
domain: "ed.example", selector: "e1",
|
|
943
|
+
privateKey: kp.privateKey, algorithm: "ed25519-sha256",
|
|
944
|
+
});
|
|
945
|
+
var msg =
|
|
946
|
+
"From: alice@ed.example\r\nTo: bob@example.org\r\nSubject: Ed\r\n" +
|
|
947
|
+
"Date: Mon, 5 May 2026 12:00:00 +0000\r\nMessage-ID: <ed@example.com>\r\n\r\nBody\r\n";
|
|
948
|
+
var signed = signer.sign(msg);
|
|
949
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
950
|
+
var rv = await b.mail.dkim.verify(signed,
|
|
951
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=ed25519; p=" + b64]]; } });
|
|
952
|
+
check("ed25519 verify: round-trip passes (RFC 8463)",
|
|
953
|
+
rv[0] && rv[0].result === "pass");
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// ---- simple/* canonicalization sign + verify round-trips ----
|
|
957
|
+
|
|
958
|
+
function testDkimSimpleCanonDirect() {
|
|
959
|
+
var sb = b.mail.dkim._canonBodySimpleForTest;
|
|
960
|
+
var rb = b.mail.dkim._canonBodyRelaxedForTest;
|
|
961
|
+
// simple body: strips trailing empty lines (the while-loop branch).
|
|
962
|
+
check("simple body: strips trailing empty lines",
|
|
963
|
+
sb("hi\r\n\r\n\r\n") === "hi\r\n");
|
|
964
|
+
// simple body: a body with no trailing CRLF gains one.
|
|
965
|
+
check("simple body: appends missing trailing CRLF",
|
|
966
|
+
sb("hi") === "hi\r\n");
|
|
967
|
+
// relaxed body: an all-empty-line body collapses to a bare CRLF.
|
|
968
|
+
check("relaxed body: all-empty-line body → bare CRLF",
|
|
969
|
+
rb("\r\n\r\n") === "\r\n");
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function testDkimSimpleCanonRoundTrips() {
|
|
973
|
+
// relaxed/simple exercises SIMPLE BODY canonicalization on both the sign
|
|
974
|
+
// and verify sides; the header canon stays relaxed (which unfolds the
|
|
975
|
+
// folded DKIM-Signature header) so the round-trip is RFC-correct and
|
|
976
|
+
// passes. This is the legitimate simple-body coverage path.
|
|
977
|
+
var kpB = _rsaKeypair();
|
|
978
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
979
|
+
var signerB = b.mail.dkim.create({
|
|
980
|
+
domain: "sbody.example", selector: "sb",
|
|
981
|
+
privateKey: kpB.privateKey, canonicalization: "relaxed/simple",
|
|
982
|
+
});
|
|
983
|
+
var msgB =
|
|
984
|
+
"From: alice@sbody.example\r\nTo: bob@example.org\r\n" +
|
|
985
|
+
"Subject: Simple body\r\nDate: Mon, 5 May 2026 12:00:00 +0000\r\n" +
|
|
986
|
+
"Message-ID: <sb@example.com>\r\n\r\nLine one\r\nLine two\r\n\r\n";
|
|
987
|
+
var signedB = signerB.sign(msgB);
|
|
988
|
+
check("relaxed/simple sign: emits c=relaxed/simple",
|
|
989
|
+
signedB.indexOf("c=relaxed/simple") !== -1);
|
|
990
|
+
var b64B = _spkiPemToB64(kpB.publicKey);
|
|
991
|
+
var rvB = await b.mail.dkim.verify(signedB,
|
|
992
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=" + b64B]]; } });
|
|
993
|
+
check("relaxed/simple verify: simple-body round-trip passes",
|
|
994
|
+
rvB[0] && rvB[0].result === "pass");
|
|
995
|
+
|
|
996
|
+
// simple HEADER canonicalization (simple/simple, simple/relaxed) drives
|
|
997
|
+
// the _canonHeaderSimple sign + verify branches. NOTE: the framework
|
|
998
|
+
// signer signs the UNFOLDED DKIM-Signature tag string, but the wire
|
|
999
|
+
// header is folded (CRLF+WSP) and simple header canonicalization does not
|
|
1000
|
+
// unfold — so the verifier canonicalizes different bytes than were signed
|
|
1001
|
+
// and the self-verify does NOT pass. This documents current behavior; see
|
|
1002
|
+
// the reported simple-header-canon signature-computation bug.
|
|
1003
|
+
var simpleHeaderCanons = ["simple/simple", "simple/relaxed"];
|
|
1004
|
+
for (var si = 0; si < simpleHeaderCanons.length; si += 1) {
|
|
1005
|
+
var canon = simpleHeaderCanons[si];
|
|
1006
|
+
var kp = _rsaKeypair();
|
|
1007
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1008
|
+
var signer = b.mail.dkim.create({
|
|
1009
|
+
domain: "shdr" + si + ".example", selector: "sh" + si,
|
|
1010
|
+
privateKey: kp.privateKey, canonicalization: canon,
|
|
1011
|
+
});
|
|
1012
|
+
var msg =
|
|
1013
|
+
"From: alice@shdr" + si + ".example\r\nTo: bob@example.org\r\n" +
|
|
1014
|
+
"Subject: Simple header\r\nDate: Mon, 5 May 2026 12:00:00 +0000\r\n" +
|
|
1015
|
+
"Message-ID: <sh" + si + "@example.com>\r\n\r\nLine one\r\n";
|
|
1016
|
+
var signed = signer.sign(msg);
|
|
1017
|
+
check("simple-header sign: emits c=" + canon,
|
|
1018
|
+
signed.indexOf("c=" + canon) !== -1);
|
|
1019
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1020
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
1021
|
+
var rv = await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup });
|
|
1022
|
+
// Simple header canonicalization signs and verifies the DKIM-Signature
|
|
1023
|
+
// header verbatim (folded, one space after the colon) per RFC 6376 §3.4.1,
|
|
1024
|
+
// so a folded simple-header signature now round-trips.
|
|
1025
|
+
check("simple-header verify: folded " + canon + " self-verify passes",
|
|
1026
|
+
rv[0] && rv[0].result === "pass");
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
async function testDkimSimpleCanonHonorsWireFieldName() {
|
|
1031
|
+
// Simple header canon is verbatim, INCLUDING the DKIM-Signature field-name
|
|
1032
|
+
// casing exactly as it appears on the wire — the verifier must canonicalize
|
|
1033
|
+
// with the parsed field name, not a hardcoded "DKIM-Signature". So a peer that
|
|
1034
|
+
// signs and sends `dkim-signature:` (lowercase) verifies, and a message whose
|
|
1035
|
+
// field name was altered after signing no longer matches. Proven here by
|
|
1036
|
+
// altering the on-wire casing after signing: the signature was computed over
|
|
1037
|
+
// "DKIM-Signature", so the lowercased wire must NOT verify. (Before the fix
|
|
1038
|
+
// the verifier ignored the on-wire name and this passed spuriously.)
|
|
1039
|
+
var kp = _rsaKeypair();
|
|
1040
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1041
|
+
var signer = b.mail.dkim.create({
|
|
1042
|
+
domain: "wirename.example", selector: "wn",
|
|
1043
|
+
privateKey: kp.privateKey, canonicalization: "simple/simple",
|
|
1044
|
+
});
|
|
1045
|
+
var msg =
|
|
1046
|
+
"From: alice@wirename.example\r\nTo: bob@example.org\r\n" +
|
|
1047
|
+
"Subject: Wire name\r\nDate: Mon, 5 May 2026 12:00:00 +0000\r\n" +
|
|
1048
|
+
"Message-ID: <wn@example.com>\r\n\r\nBody.\r\n";
|
|
1049
|
+
var signed = signer.sign(msg);
|
|
1050
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1051
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
1052
|
+
var rvUpper = await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup });
|
|
1053
|
+
check("simple-canon verify: canonical DKIM-Signature casing verifies",
|
|
1054
|
+
rvUpper[0] && rvUpper[0].result === "pass");
|
|
1055
|
+
// Alter ONLY the field-name casing on the wire (the signed bytes used
|
|
1056
|
+
// "DKIM-Signature"), leaving everything else intact.
|
|
1057
|
+
var loweredWire = signed.replace(/^DKIM-Signature:/, "dkim-signature:");
|
|
1058
|
+
var rvLower = await b.mail.dkim.verify(loweredWire, { dnsLookup: dnsLookup });
|
|
1059
|
+
check("simple-canon verify: honors the on-wire field-name casing (altered name does not verify)",
|
|
1060
|
+
rvLower[0] && rvLower[0].result === "fail");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ---- Cross-implementation conformance: openssl-signed simple/simple vector ----
|
|
1064
|
+
//
|
|
1065
|
+
// The signature bytes below were produced by an INDEPENDENT implementation:
|
|
1066
|
+
// OpenSSL (`openssl dgst -sha256 -sign` — RSASSA-PKCS1-v1_5) over
|
|
1067
|
+
// hand-constructed RFC 6376 simple/simple canonicalization. bh= is the
|
|
1068
|
+
// SHA-256 of the verbatim CRLF body (section 3.4.3); b= signs the four h=
|
|
1069
|
+
// headers verbatim (each with its CRLF) followed by the DKIM-Signature
|
|
1070
|
+
// header with an empty b= value and no trailing CRLF (sections 3.4.1 and
|
|
1071
|
+
// 3.7). No framework code participated in producing the fixture, so a pass
|
|
1072
|
+
// here proves cross-implementation ACCEPTANCE of simple canonicalization —
|
|
1073
|
+
// a self-round-trip alone stays green when sign and verify share the same
|
|
1074
|
+
// canonicalization bug. The tampered-body / tampered-header controls prove
|
|
1075
|
+
// the pass is cryptographic, not vacuous.
|
|
1076
|
+
async function testDkimOpensslSimpleCanonInteropVector() {
|
|
1077
|
+
var BH = "AD876huuF731JvLItVmRCvApI83P8LTA8/xaXm2Hu8Y=";
|
|
1078
|
+
var SIG =
|
|
1079
|
+
"i62Ti4k531wo/A2u2rk8G5J8JpgM0+MBj6vJbmwzz6MiMYEwS6XfO1GAOL0rUtM3TSNeWjKg" +
|
|
1080
|
+
"ul2XjS4C5bVMjRQma1W+TzcJh3aHL8UXTc8K64DQ8isZ61BsN8v/iaIf/z5/6MZIOxlnaAbn" +
|
|
1081
|
+
"CVWm/1OU3qvhKxyFOU9aCl+EaHuHGlvF1EM/bBpPpu9jmjRzSvj6Nyojw1tFl/g4HfOaCvpv" +
|
|
1082
|
+
"wVmOBo49O1voWHC3CHDHK5rbURzf/dVC37aviTkjTgbh5I8QTcRVllBAMOs5LYl5zHAr1Jop" +
|
|
1083
|
+
"h+8omZwednKI/Inj//S0EsyZeM6iKkuNX1nP3c460DH65jexsLj49Q==";
|
|
1084
|
+
var PUB_B64 =
|
|
1085
|
+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0VM3DGY3FSvXw58NTv2nMzJ149kB" +
|
|
1086
|
+
"Q6KuTQBaRjjQfrYxRAbLhCSgatNz55GbZlBR0Uj7sYmGzte18sef5AqCWY4lXrD/+97S0GjH" +
|
|
1087
|
+
"jUMHzQhp9Tk1m4yVnfqJp2MgXyMi31yrRvRkmNM7XD/EC07HuugHcKGnPRsHQV6kyGUkeuHt" +
|
|
1088
|
+
"lQYRx058jTOWja6mcc0/nVQa08GM4703QYl6j3ZgSiu90fFnw77uswcuaKgwPIw3V66uPNPS" +
|
|
1089
|
+
"VhqxGLyuXF4fw1DvYtRyJ/3liMNhqClEqDSwKfSEK6Z8nE/4M8Nv3BZA0OOzitDmqzyXkcdY" +
|
|
1090
|
+
"At3XbaUHc0Gpbs5MYV7k2YXKWQIDAQAB";
|
|
1091
|
+
|
|
1092
|
+
var message =
|
|
1093
|
+
"DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; " +
|
|
1094
|
+
"d=openssl-interop.example; s=oss1; h=from:to:subject:date; " +
|
|
1095
|
+
"bh=" + BH + "; b=" + SIG + "\r\n" +
|
|
1096
|
+
"From: interop@openssl-interop.example\r\n" +
|
|
1097
|
+
"To: rcpt@blamejs.example\r\n" +
|
|
1098
|
+
"Subject: DKIM simple canonicalization interop\r\n" +
|
|
1099
|
+
"Date: Thu, 01 Jan 2026 00:00:00 +0000\r\n" +
|
|
1100
|
+
"\r\n" +
|
|
1101
|
+
"Interop body line one.\r\nInterop body line two.\r\n";
|
|
1102
|
+
|
|
1103
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=rsa; p=" + PUB_B64]]; };
|
|
1104
|
+
|
|
1105
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1106
|
+
var rv = await b.mail.dkim.verify(message, { dnsLookup: dnsLookup });
|
|
1107
|
+
check("openssl interop: independently-signed simple/simple vector passes",
|
|
1108
|
+
Array.isArray(rv) && rv.length === 1 && rv[0].result === "pass");
|
|
1109
|
+
check("openssl interop: pass verdict carries zero errors",
|
|
1110
|
+
rv[0] && Array.isArray(rv[0].errors) && rv[0].errors.length === 0);
|
|
1111
|
+
check("openssl interop: verdict identifies the signing domain and selector",
|
|
1112
|
+
rv[0] && rv[0].d === "openssl-interop.example" && rv[0].s === "oss1");
|
|
1113
|
+
|
|
1114
|
+
// Control 1: one flipped body byte breaks the simple body hash.
|
|
1115
|
+
var bodyTampered = message.replace("Interop body line two.", "Interop body line 2wo.");
|
|
1116
|
+
var rvBody = await b.mail.dkim.verify(bodyTampered, { dnsLookup: dnsLookup });
|
|
1117
|
+
check("openssl interop: tampered body fails (body hash mismatch)",
|
|
1118
|
+
rvBody[0] && rvBody[0].result === "fail" &&
|
|
1119
|
+
/body hash mismatch/.test((rvBody[0].errors || []).join(",")));
|
|
1120
|
+
|
|
1121
|
+
// Control 2: one flipped byte in a signed header breaks the signature.
|
|
1122
|
+
var hdrTampered = message.replace("Subject: DKIM simple", "Subject: DKIM Simple");
|
|
1123
|
+
var rvHdr = await b.mail.dkim.verify(hdrTampered, { dnsLookup: dnsLookup });
|
|
1124
|
+
check("openssl interop: tampered signed header fails signature verification",
|
|
1125
|
+
rvHdr[0] && rvHdr[0].result === "fail" &&
|
|
1126
|
+
/signature verification failed/.test((rvHdr[0].errors || []).join(",")));
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// ---- verify(): input + option-default guards ----
|
|
1130
|
+
|
|
1131
|
+
async function testDkimVerifyBadInputAndOptions() {
|
|
1132
|
+
var kp = _rsaKeypair();
|
|
1133
|
+
var signed = await _signedMessage(kp);
|
|
1134
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1135
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
1136
|
+
|
|
1137
|
+
// Empty / non-string rfc822 → bad-input throw.
|
|
1138
|
+
var threw = null;
|
|
1139
|
+
try { await b.mail.dkim.verify(""); } catch (e) { threw = e; }
|
|
1140
|
+
check("verify: empty rfc822 → bad-input", threw && /dkim\/bad-input/.test(threw.code || ""));
|
|
1141
|
+
threw = null;
|
|
1142
|
+
try { await b.mail.dkim.verify(12345); } catch (e) { threw = e; }
|
|
1143
|
+
check("verify: non-string rfc822 → bad-input", threw && /dkim\/bad-input/.test(threw.code || ""));
|
|
1144
|
+
|
|
1145
|
+
// A well-formed valid clockSkewMs (the accepted numeric branch).
|
|
1146
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1147
|
+
var rvSkew = await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup, clockSkewMs: 60000 });
|
|
1148
|
+
check("verify: accepts a valid clockSkewMs (finite, < ceiling)",
|
|
1149
|
+
rvSkew[0] && rvSkew[0].result === "pass");
|
|
1150
|
+
|
|
1151
|
+
// maxSignatures range-check: below 1 and above the ceiling throw.
|
|
1152
|
+
threw = null;
|
|
1153
|
+
try { await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup, maxSignatures: 0 }); }
|
|
1154
|
+
catch (e) { threw = e; }
|
|
1155
|
+
check("verify: maxSignatures < 1 → bad-max-signatures",
|
|
1156
|
+
threw && /dkim\/bad-max-signatures/.test(threw.code || ""));
|
|
1157
|
+
threw = null;
|
|
1158
|
+
try { await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup, maxSignatures: 99 }); }
|
|
1159
|
+
catch (e) { threw = e; }
|
|
1160
|
+
check("verify: maxSignatures > ceiling → bad-max-signatures",
|
|
1161
|
+
threw && /dkim\/bad-max-signatures/.test(threw.code || ""));
|
|
1162
|
+
|
|
1163
|
+
// A valid in-range maxSignatures (the accepted numeric branch).
|
|
1164
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1165
|
+
var rvMax = await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup, maxSignatures: 4 });
|
|
1166
|
+
check("verify: accepts an in-range maxSignatures",
|
|
1167
|
+
rvMax[0] && rvMax[0].result === "pass");
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
async function testDkimVerifyNoSignatureHeaders() {
|
|
1171
|
+
// A message with no DKIM-Signature header → single "none" result.
|
|
1172
|
+
var msg = "From: a@example.com\r\nSubject: unsigned\r\n\r\nbody\r\n";
|
|
1173
|
+
var rv = await b.mail.dkim.verify(msg, { dnsLookup: async function () { return [[""]]; } });
|
|
1174
|
+
check("verify: unsigned message → [{ result: 'none' }]",
|
|
1175
|
+
Array.isArray(rv) && rv[0] && rv[0].result === "none" &&
|
|
1176
|
+
/no DKIM-Signature/.test((rv[0].errors || []).join(",")));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// ---- verify(): v= / x= / t= NumericDate guards (decided pre-crypto) ----
|
|
1180
|
+
|
|
1181
|
+
async function testDkimVerifyBadVersion() {
|
|
1182
|
+
var rv = await b.mail.dkim.verify(
|
|
1183
|
+
_messageWithSig(["v=2", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1184
|
+
"d=v.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1185
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=AAA"]]; } });
|
|
1186
|
+
check("verify: v=2 → permerror (only v=1 supported)",
|
|
1187
|
+
rv[0] && rv[0].result === "permerror" && /only v=1/.test((rv[0].errors || []).join(",")));
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
async function testDkimVerifyExpiredAndUnparseableDates() {
|
|
1191
|
+
var nowSec = Math.floor(Date.now() / 1000);
|
|
1192
|
+
var stub = { dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=AAA"]]; } };
|
|
1193
|
+
|
|
1194
|
+
// x= in the past (beyond the default 5-min skew) → expired permerror.
|
|
1195
|
+
var rvExp = await b.mail.dkim.verify(
|
|
1196
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1197
|
+
"d=x.example", "s=s", "h=from", "bh=AAA", "x=" + (nowSec - 3600), "b=AAA"]), stub);
|
|
1198
|
+
check("verify: expired x= → permerror",
|
|
1199
|
+
rvExp[0] && rvExp[0].result === "permerror" && /has expired/.test((rvExp[0].errors || []).join(",")));
|
|
1200
|
+
|
|
1201
|
+
// x= present but not digits-only → fail-closed permerror.
|
|
1202
|
+
var rvXbad = await b.mail.dkim.verify(
|
|
1203
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1204
|
+
"d=x.example", "s=s", "h=from", "bh=AAA", "x=12abc", "b=AAA"]), stub);
|
|
1205
|
+
check("verify: unparseable x= → permerror (NumericDate required)",
|
|
1206
|
+
rvXbad[0] && rvXbad[0].result === "permerror" &&
|
|
1207
|
+
/x= present but unparseable/.test((rvXbad[0].errors || []).join(",")));
|
|
1208
|
+
|
|
1209
|
+
// t= present but not digits-only → fail-closed permerror.
|
|
1210
|
+
var rvTbad = await b.mail.dkim.verify(
|
|
1211
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1212
|
+
"d=t.example", "s=s", "h=from", "bh=AAA", "t=notanum", "b=AAA"]), stub);
|
|
1213
|
+
check("verify: unparseable t= → permerror (NumericDate required)",
|
|
1214
|
+
rvTbad[0] && rvTbad[0].result === "permerror" &&
|
|
1215
|
+
/t= present but unparseable/.test((rvTbad[0].errors || []).join(",")));
|
|
1216
|
+
|
|
1217
|
+
// t= more than 24h in the future → sanity permerror.
|
|
1218
|
+
var rvFuture = await b.mail.dkim.verify(
|
|
1219
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1220
|
+
"d=t.example", "s=s", "h=from", "bh=AAA", "t=" + (nowSec + 48 * 3600), "b=AAA"]), stub);
|
|
1221
|
+
check("verify: t= >24h in the future → permerror",
|
|
1222
|
+
rvFuture[0] && rvFuture[0].result === "permerror" &&
|
|
1223
|
+
/more than 24h in the future/.test((rvFuture[0].errors || []).join(",")));
|
|
1224
|
+
|
|
1225
|
+
// x= before t= (expiry earlier than signing time) → ordering permerror.
|
|
1226
|
+
var rvOrder = await b.mail.dkim.verify(
|
|
1227
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1228
|
+
"d=t.example", "s=s", "h=from", "bh=AAA",
|
|
1229
|
+
"t=" + (nowSec + 100), "x=" + (nowSec + 50), "b=AAA"]), stub);
|
|
1230
|
+
check("verify: x= before t= → permerror (x must be after t)",
|
|
1231
|
+
rvOrder[0] && rvOrder[0].result === "permerror" &&
|
|
1232
|
+
/x= must be after t=/.test((rvOrder[0].errors || []).join(",")));
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function testDkimVerifyValidDatesPassThrough() {
|
|
1236
|
+
// A fully-valid signature carrying non-expired t= + x= reaches the pass
|
|
1237
|
+
// path (exercises the accepted t=/x= branches, not the reject branches).
|
|
1238
|
+
var kp = _rsaKeypair();
|
|
1239
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1240
|
+
var nowSec = Math.floor(Date.now() / 1000);
|
|
1241
|
+
var signed = _buildRelaxedSigned(kp, "dates.example", "sd",
|
|
1242
|
+
["t=" + (nowSec - 60), "x=" + (nowSec + 3600)]);
|
|
1243
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1244
|
+
var rv = await b.mail.dkim.verify(signed,
|
|
1245
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; } });
|
|
1246
|
+
check("verify: valid non-expired t= + x= passes through to verification",
|
|
1247
|
+
rv[0] && rv[0].result === "pass");
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async function testDkimVerifyMissingDomainSelector() {
|
|
1251
|
+
// Signature with neither d= nor s= → permerror (missing d= or s=).
|
|
1252
|
+
var rv = await b.mail.dkim.verify(
|
|
1253
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1254
|
+
"h=from", "bh=AAA", "b=AAA"]),
|
|
1255
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=AAA"]]; } });
|
|
1256
|
+
check("verify: missing d=/s= → permerror",
|
|
1257
|
+
rv[0] && rv[0].result === "permerror" &&
|
|
1258
|
+
/missing d= or s=/.test((rv[0].errors || []).join(",")));
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// ---- verify(): key-resolution error paths ----
|
|
1262
|
+
|
|
1263
|
+
async function testDkimVerifyKeyLookupErrors() {
|
|
1264
|
+
// ENOTFOUND from the resolver → permerror key-not-found.
|
|
1265
|
+
var rvNx = await b.mail.dkim.verify(
|
|
1266
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1267
|
+
"d=nx.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1268
|
+
{ dnsLookup: async function () { var e = new Error("nx"); e.code = "ENOTFOUND"; throw e; } });
|
|
1269
|
+
check("verify: ENOTFOUND key lookup → permerror (no TXT record)",
|
|
1270
|
+
rvNx[0] && rvNx[0].result === "permerror" &&
|
|
1271
|
+
/no DKIM TXT record/.test((rvNx[0].errors || []).join(",")));
|
|
1272
|
+
|
|
1273
|
+
// A generic resolver failure (no ENOTFOUND/ENODATA code) → temperror.
|
|
1274
|
+
var rvTemp = await b.mail.dkim.verify(
|
|
1275
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1276
|
+
"d=temp.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1277
|
+
{ dnsLookup: async function () { throw new Error("connection reset"); } });
|
|
1278
|
+
check("verify: generic key-lookup failure → temperror",
|
|
1279
|
+
rvTemp[0] && rvTemp[0].result === "temperror" &&
|
|
1280
|
+
/lookup for .* failed/.test((rvTemp[0].errors || []).join(",")));
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async function testDkimVerifyKeyRecordShapes() {
|
|
1284
|
+
var kp = _rsaKeypair();
|
|
1285
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1286
|
+
|
|
1287
|
+
// A bare-string TXT record (not the [[chunk]] shape) still parses.
|
|
1288
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1289
|
+
var signed = await _signedMessage(kp);
|
|
1290
|
+
var rvStr = await b.mail.dkim.verify(signed,
|
|
1291
|
+
{ dnsLookup: async function () { return "v=DKIM1; k=rsa; p=" + b64; } });
|
|
1292
|
+
check("verify: accepts a bare-string TXT record shape",
|
|
1293
|
+
rvStr[0] && rvStr[0].result === "pass");
|
|
1294
|
+
|
|
1295
|
+
// An empty TXT record → key-not-found (empty) → permerror.
|
|
1296
|
+
var rvEmpty = await b.mail.dkim.verify(
|
|
1297
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1298
|
+
"d=e.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1299
|
+
{ dnsLookup: async function () { return ""; } });
|
|
1300
|
+
check("verify: empty TXT record → permerror (key record is empty)",
|
|
1301
|
+
rvEmpty[0] && rvEmpty[0].result === "permerror" &&
|
|
1302
|
+
/empty/.test((rvEmpty[0].errors || []).join(",")));
|
|
1303
|
+
|
|
1304
|
+
// A record present but missing p= → permerror (missing p=).
|
|
1305
|
+
var rvNoP = await b.mail.dkim.verify(
|
|
1306
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1307
|
+
"d=nop.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1308
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa"]]; } });
|
|
1309
|
+
check("verify: key record without p= → permerror (missing p=)",
|
|
1310
|
+
rvNoP[0] && rvNoP[0].result === "permerror" &&
|
|
1311
|
+
/missing p=/.test((rvNoP[0].errors || []).join(",")));
|
|
1312
|
+
|
|
1313
|
+
// An explicitly-revoked key (empty p=) → fail (well-formed, withdrawn).
|
|
1314
|
+
var rvRevoked = await b.mail.dkim.verify(
|
|
1315
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1316
|
+
"d=rev.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1317
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p="]]; } });
|
|
1318
|
+
check("verify: revoked key (empty p=) → fail",
|
|
1319
|
+
rvRevoked[0] && rvRevoked[0].result === "fail" &&
|
|
1320
|
+
/revoked/.test((rvRevoked[0].errors || []).join(",")));
|
|
1321
|
+
|
|
1322
|
+
// Key k= family disagrees with the signature a= family → permerror.
|
|
1323
|
+
var rvKMismatch = await b.mail.dkim.verify(
|
|
1324
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1325
|
+
"d=km.example", "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1326
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=ed25519; p=AAA"]]; } });
|
|
1327
|
+
check("verify: key k= family != signature a= family → permerror",
|
|
1328
|
+
rvKMismatch[0] && rvKMismatch[0].result === "permerror" &&
|
|
1329
|
+
/does not match signature a=/.test((rvKMismatch[0].errors || []).join(",")));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// ---- verify(): _verifySingleSignature body-hash / header / key guards ----
|
|
1333
|
+
|
|
1334
|
+
async function testDkimVerifySingleSignatureGuards() {
|
|
1335
|
+
var kp = _rsaKeypair();
|
|
1336
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1337
|
+
var goodKey = async function () { return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
1338
|
+
var bhBody = "guarded body\r\n";
|
|
1339
|
+
var bh = _relaxedBodyHash(bhBody);
|
|
1340
|
+
|
|
1341
|
+
// Signature with no bh= tag → permerror (missing bh=).
|
|
1342
|
+
var rvNoBh = await b.mail.dkim.verify(
|
|
1343
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1344
|
+
"d=nobh.example", "s=s", "h=from", "b=AAA"], bhBody),
|
|
1345
|
+
{ dnsLookup: goodKey });
|
|
1346
|
+
check("verify: signature missing bh= → permerror",
|
|
1347
|
+
rvNoBh[0] && rvNoBh[0].result === "permerror" &&
|
|
1348
|
+
/missing bh=/.test((rvNoBh[0].errors || []).join(",")));
|
|
1349
|
+
|
|
1350
|
+
// A tampered body (correct-length but different content) → body-hash fail.
|
|
1351
|
+
var rvBhMismatch = await b.mail.dkim.verify(
|
|
1352
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1353
|
+
"d=bhmm.example", "s=s", "h=from", "bh=" + bh, "b=AAA"], "TAMPERED body\r\n"),
|
|
1354
|
+
{ dnsLookup: goodKey });
|
|
1355
|
+
check("verify: body-hash mismatch → fail",
|
|
1356
|
+
rvBhMismatch[0] && rvBhMismatch[0].result === "fail" &&
|
|
1357
|
+
/body hash mismatch/.test((rvBhMismatch[0].errors || []).join(",")));
|
|
1358
|
+
|
|
1359
|
+
// Correct bh but h= omits 'from' → permerror (from not covered).
|
|
1360
|
+
var rvNoFrom = await b.mail.dkim.verify(
|
|
1361
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1362
|
+
"d=nofrom.example", "s=s", "h=to", "bh=" + bh, "b=AAA"], bhBody),
|
|
1363
|
+
{ dnsLookup: goodKey });
|
|
1364
|
+
check("verify: h= without 'from' → permerror",
|
|
1365
|
+
rvNoFrom[0] && rvNoFrom[0].result === "permerror" &&
|
|
1366
|
+
/does not include 'from'/.test((rvNoFrom[0].errors || []).join(",")));
|
|
1367
|
+
|
|
1368
|
+
// Correct bh + h=from but no b= tag → permerror (missing b=).
|
|
1369
|
+
var rvNoB = await b.mail.dkim.verify(
|
|
1370
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1371
|
+
"d=nob.example", "s=s", "h=from", "bh=" + bh], bhBody),
|
|
1372
|
+
{ dnsLookup: goodKey });
|
|
1373
|
+
check("verify: signature missing b= → permerror",
|
|
1374
|
+
rvNoB[0] && rvNoB[0].result === "permerror" &&
|
|
1375
|
+
/missing b=/.test((rvNoB[0].errors || []).join(",")));
|
|
1376
|
+
|
|
1377
|
+
// l= present but not digits-only → fail-closed permerror.
|
|
1378
|
+
var rvLbad = await b.mail.dkim.verify(
|
|
1379
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1380
|
+
"d=lbad.example", "s=s", "h=from", "bh=" + bh, "l=12x", "b=AAA"], bhBody),
|
|
1381
|
+
{ dnsLookup: goodKey });
|
|
1382
|
+
check("verify: unparseable l= → permerror (unsigned integer required)",
|
|
1383
|
+
rvLbad[0] && rvLbad[0].result === "permerror" &&
|
|
1384
|
+
/l= present but unparseable/.test((rvLbad[0].errors || []).join(",")));
|
|
1385
|
+
|
|
1386
|
+
// Correct bh + h=from + b= present, but the published key p= is garbage
|
|
1387
|
+
// that node:crypto cannot parse → permerror (key parse failed).
|
|
1388
|
+
var rvKeyParse = await b.mail.dkim.verify(
|
|
1389
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1390
|
+
"d=keyparse.example", "s=s", "h=from", "bh=" + bh, "b=AAAA"], bhBody),
|
|
1391
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=rsa; p=@@not-base64-der@@"]]; } });
|
|
1392
|
+
check("verify: unparseable key material → permerror (key parse failed)",
|
|
1393
|
+
rvKeyParse[0] && rvKeyParse[0].result === "permerror" &&
|
|
1394
|
+
/key parse failed/.test((rvKeyParse[0].errors || []).join(",")));
|
|
1395
|
+
|
|
1396
|
+
// a= names a supported family (rsa) but an unsupported full algorithm
|
|
1397
|
+
// (rsa-sha1) → permerror (unsupported DKIM algorithm). Take a valid
|
|
1398
|
+
// rsa-sha256 signature and re-label its a= so bh stays valid and the
|
|
1399
|
+
// run reaches the algorithm check before any crypto verify.
|
|
1400
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1401
|
+
var validSigned = _buildRelaxedSigned(kp, "alg.example", "sa");
|
|
1402
|
+
var relabeled = validSigned.replace("a=rsa-sha256", "a=rsa-sha1");
|
|
1403
|
+
var rvUnsupported = await b.mail.dkim.verify(relabeled, { dnsLookup: goodKey });
|
|
1404
|
+
check("verify: unsupported a= algorithm (rsa-sha1) → permerror",
|
|
1405
|
+
rvUnsupported[0] && rvUnsupported[0].result === "permerror" &&
|
|
1406
|
+
/unsupported DKIM algorithm/.test((rvUnsupported[0].errors || []).join(",")));
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
async function testDkimVerifyCryptoThrowsPath() {
|
|
1410
|
+
// a=rsa-sha256 (digest sha256) but the published key is an Ed25519 key
|
|
1411
|
+
// with no k= tag, so the k= guard is skipped and node:crypto.verify is
|
|
1412
|
+
// invoked with a digest the key type rejects → it THROWS, and the
|
|
1413
|
+
// verifier maps the throw to a permerror rather than crashing.
|
|
1414
|
+
var edKp = _ed25519Keypair();
|
|
1415
|
+
var edB64 = _spkiPemToB64(edKp.publicKey);
|
|
1416
|
+
var body = "throws body\r\n";
|
|
1417
|
+
var bh = _relaxedBodyHash(body);
|
|
1418
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1419
|
+
var rv = await b.mail.dkim.verify(
|
|
1420
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1421
|
+
"d=throws.example", "s=s", "h=from", "bh=" + bh, "b=AAAA"], body),
|
|
1422
|
+
{ dnsLookup: async function () { return [["v=DKIM1; p=" + edB64]]; } });
|
|
1423
|
+
check("verify: node:crypto.verify throw is caught → permerror",
|
|
1424
|
+
rv[0] && rv[0].result === "permerror" &&
|
|
1425
|
+
/verify threw/.test((rv[0].errors || []).join(",")));
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// ---- verify(): key cache expiry + eviction ----
|
|
1429
|
+
|
|
1430
|
+
async function testDkimKeyCacheExpiry() {
|
|
1431
|
+
// A cached key entry past its TTL is dropped and re-fetched. Advance the
|
|
1432
|
+
// clock via a scoped Date.now override (deterministic; no wall-clock wait).
|
|
1433
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1434
|
+
var kp = _rsaKeypair();
|
|
1435
|
+
var b64 = _spkiPemToB64(kp.publicKey);
|
|
1436
|
+
var signed = await _signedMessage(kp);
|
|
1437
|
+
var calls = 0;
|
|
1438
|
+
var dnsLookup = async function () { calls += 1; return [["v=DKIM1; k=rsa; p=" + b64]]; };
|
|
1439
|
+
var realNow = Date.now;
|
|
1440
|
+
try {
|
|
1441
|
+
await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup }); // fetch + cache
|
|
1442
|
+
var base = realNow();
|
|
1443
|
+
Date.now = function () { return base + 6 * 60 * 1000; }; // TTL is 5 min
|
|
1444
|
+
await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup }); // expired → re-fetch
|
|
1445
|
+
check("verify: key cache entry past TTL is evicted and re-fetched",
|
|
1446
|
+
calls === 2);
|
|
1447
|
+
} finally {
|
|
1448
|
+
Date.now = realNow;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
async function testDkimKeyCacheEvictionBound() {
|
|
1453
|
+
// Fill the key cache past its max-entries bound so the oldest entry is
|
|
1454
|
+
// evicted (the DoS-bounding eviction path). Each verify pins a distinct
|
|
1455
|
+
// domain; a k=-family mismatch short-circuits before any crypto verify,
|
|
1456
|
+
// so the fill is cheap. The first-inserted selector must be re-fetched
|
|
1457
|
+
// after the eviction wave, proving the oldest entry was dropped.
|
|
1458
|
+
b.mail.dkim._resetDkimKeyCacheForTest();
|
|
1459
|
+
var MAX = 1024; // lib DKIM_KEY_CACHE_MAX_ENTRIES (module-private)
|
|
1460
|
+
var fetches = Object.create(null);
|
|
1461
|
+
var dnsLookup = async function (qname) {
|
|
1462
|
+
fetches[qname] = (fetches[qname] || 0) + 1;
|
|
1463
|
+
return [["v=DKIM1; k=ed25519; p=AAA"]]; // mismatch vs a=rsa-sha256 → permerror pre-crypto
|
|
1464
|
+
};
|
|
1465
|
+
async function verifyDomain(dom) {
|
|
1466
|
+
return b.mail.dkim.verify(
|
|
1467
|
+
_messageWithSig(["v=1", "a=rsa-sha256", "c=relaxed/relaxed",
|
|
1468
|
+
"d=" + dom, "s=s", "h=from", "bh=AAA", "b=AAA"]),
|
|
1469
|
+
{ dnsLookup: dnsLookup });
|
|
1470
|
+
}
|
|
1471
|
+
var firstDomain = "g0.evict.example";
|
|
1472
|
+
await verifyDomain(firstDomain); // insert oldest
|
|
1473
|
+
for (var i = 1; i <= MAX; i += 1) { // MAX more → oldest evicted
|
|
1474
|
+
await verifyDomain("g" + i + ".evict.example");
|
|
1475
|
+
}
|
|
1476
|
+
var firstQname = "s._domainkey." + firstDomain;
|
|
1477
|
+
var before = fetches[firstQname];
|
|
1478
|
+
await verifyDomain(firstDomain); // must miss → re-fetch
|
|
1479
|
+
check("verify: oldest key-cache entry evicted past the max-entries bound",
|
|
1480
|
+
fetches[firstQname] === before + 1);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// ---- dualSigner() + bootstrap() validation branches ----
|
|
1484
|
+
|
|
1485
|
+
function testDualSignerValidation() {
|
|
1486
|
+
var kp = _rsaKeypair();
|
|
1487
|
+
function shouldThrow(label, opts, codeRe) {
|
|
1488
|
+
var threw = null;
|
|
1489
|
+
try { b.mail.dkim.dualSigner(opts); } catch (e) { threw = e; }
|
|
1490
|
+
check("dualSigner: " + label, threw && codeRe.test(threw.code || ""));
|
|
1491
|
+
}
|
|
1492
|
+
shouldThrow("missing rsa/eddsa → dual-signer-missing",
|
|
1493
|
+
{ domain: "example.com" }, /dkim\/dual-signer-missing/);
|
|
1494
|
+
shouldThrow("missing eddsa → dual-signer-missing",
|
|
1495
|
+
{ domain: "example.com", rsa: { selector: "r", privateKey: kp.privateKey } },
|
|
1496
|
+
/dkim\/dual-signer-missing/);
|
|
1497
|
+
shouldThrow("missing domain → dual-signer-missing-domain",
|
|
1498
|
+
{ rsa: { selector: "r", privateKey: kp.privateKey },
|
|
1499
|
+
eddsa: { selector: "e", privateKey: _ed25519Keypair().privateKey } },
|
|
1500
|
+
/dkim\/dual-signer-missing-domain/);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function testBootstrapMoreValidation() {
|
|
1504
|
+
function shouldThrow(label, opts, codeRe) {
|
|
1505
|
+
var threw = null;
|
|
1506
|
+
try { b.mail.dkim.bootstrap(opts); } catch (e) { threw = e; }
|
|
1507
|
+
check("bootstrap: " + label, threw && codeRe.test(threw.code || ""));
|
|
1508
|
+
}
|
|
1509
|
+
// Domain that fails the DNS-hostname shape (leading hyphen).
|
|
1510
|
+
shouldThrow("rejects malformed domain shape",
|
|
1511
|
+
{ domain: "-bad-.example", selector: "s1" }, /dkim\/bad-domain/);
|
|
1512
|
+
// Dual with a malformed explicit rsaSelector.
|
|
1513
|
+
shouldThrow("dual rejects malformed rsaSelector",
|
|
1514
|
+
{ domain: "example.com", selector: "s1", algorithm: "dual", rsaSelector: "bad/sel" },
|
|
1515
|
+
/dkim\/bad-selector/);
|
|
1516
|
+
// Dual with sub-floor rsaBits.
|
|
1517
|
+
shouldThrow("dual rejects sub-floor rsaBits",
|
|
1518
|
+
{ domain: "example.com", selector: "s1", algorithm: "dual", rsaBits: 512 },
|
|
1519
|
+
/dkim\/bad-rsa-bits/);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// RFC 8463 §3-4 — an ed25519 DKIM key is published as the RAW 32-byte key,
|
|
1523
|
+
// base64'd (44 chars), the form every conformant sender uses. The verifier must
|
|
1524
|
+
// accept it; it previously SPKI-wrapped the raw bytes so createPublicKey threw
|
|
1525
|
+
// and a valid ed25519 signature returned permerror.
|
|
1526
|
+
async function testDkimVerifyEd25519RawKeyRfc8463() {
|
|
1527
|
+
var kp = _ed25519Keypair();
|
|
1528
|
+
var signer = b.mail.dkim.create({
|
|
1529
|
+
domain: "example.com",
|
|
1530
|
+
selector: "ed1",
|
|
1531
|
+
privateKey: kp.privateKey,
|
|
1532
|
+
algorithm: "ed25519-sha256",
|
|
1533
|
+
});
|
|
1534
|
+
var rfc822 =
|
|
1535
|
+
"From: alice@example.com\r\n" +
|
|
1536
|
+
"To: bob@example.org\r\n" +
|
|
1537
|
+
"Subject: Test ed raw\r\n" +
|
|
1538
|
+
"Date: Mon, 5 May 2026 12:00:00 +0000\r\n" +
|
|
1539
|
+
"Message-ID: <ed-raw@example.com>\r\n" +
|
|
1540
|
+
"\r\n" +
|
|
1541
|
+
"Hello raw ed25519.\r\n";
|
|
1542
|
+
var signed = signer.sign(rfc822);
|
|
1543
|
+
var rawB64 = nodeCrypto.createPublicKey(kp.publicKey)
|
|
1544
|
+
.export({ type: "spki", format: "der" }).subarray(-32).toString("base64");
|
|
1545
|
+
check("ed25519 raw key is the 44-char RFC 8463 form (not 60-char SPKI)", rawB64.length === 44);
|
|
1546
|
+
var dnsLookup = async function () { return [["v=DKIM1; k=ed25519; p=" + rawB64]]; };
|
|
1547
|
+
var rv = await b.mail.dkim.verify(signed, { dnsLookup: dnsLookup });
|
|
1548
|
+
check("ed25519 raw-key (RFC 8463) DKIM signature verifies pass",
|
|
1549
|
+
Array.isArray(rv) && rv[0] && rv[0].result === "pass");
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// A record `p=<raw-32-byte-key>` with NO k= is an RSA record (RFC 6376 §3.6.1
|
|
1553
|
+
// default). It must NOT verify an a=ed25519-sha256 signature just because the
|
|
1554
|
+
// key is 32 bytes long — a key-family confusion the record never authorized.
|
|
1555
|
+
async function testDkimEd25519KFamilyConfusionRefused() {
|
|
1556
|
+
var kp = _ed25519Keypair();
|
|
1557
|
+
var rawB64 = nodeCrypto.createPublicKey(kp.publicKey)
|
|
1558
|
+
.export({ type: "spki", format: "der" }).subarray(-32).toString("base64");
|
|
1559
|
+
// Distinct selectors so the per-qname key cache does not carry one record's
|
|
1560
|
+
// key into the other's lookup.
|
|
1561
|
+
function signWith(sel) {
|
|
1562
|
+
return b.mail.dkim.create({
|
|
1563
|
+
domain: "example.com", selector: sel, privateKey: kp.privateKey, algorithm: "ed25519-sha256",
|
|
1564
|
+
}).sign("From: a@example.com\r\nTo: b@example.org\r\nSubject: x\r\n\r\nbody\r\n");
|
|
1565
|
+
}
|
|
1566
|
+
// (negative) record omits k= (defaults rsa) → the ed25519 signature is refused.
|
|
1567
|
+
var rvNo = await b.mail.dkim.verify(signWith("edkconfno"),
|
|
1568
|
+
{ dnsLookup: async function () { return [["v=DKIM1; p=" + rawB64]]; } });
|
|
1569
|
+
check("ed25519 sig against a k=-absent (RSA-default) raw-key record is refused",
|
|
1570
|
+
Array.isArray(rvNo) && rvNo[0] && rvNo[0].result === "permerror");
|
|
1571
|
+
// (positive control) with k=ed25519 it verifies.
|
|
1572
|
+
var rvOk = await b.mail.dkim.verify(signWith("edkconfok"),
|
|
1573
|
+
{ dnsLookup: async function () { return [["v=DKIM1; k=ed25519; p=" + rawB64]]; } });
|
|
1574
|
+
check("ed25519 sig against a k=ed25519 raw-key record verifies pass",
|
|
1575
|
+
Array.isArray(rvOk) && rvOk[0] && rvOk[0].result === "pass");
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// bootstrap must PUBLISH the raw 32-byte ed25519 key (44 base64 chars), not
|
|
1579
|
+
// SPKI DER (60 chars) — the SPKI form does not verify at a conformant receiver,
|
|
1580
|
+
// so the framework's own default-algorithm signatures would be undeliverable.
|
|
1581
|
+
function testDkimBootstrapEd25519PublishesRawKey() {
|
|
1582
|
+
var bs = b.mail.dkim.bootstrap({ domain: "example.com", selector: "ed1", algorithm: "ed25519-sha256" });
|
|
1583
|
+
var m = /p=([A-Za-z0-9+/=]+)/.exec(bs.dnsTxtValue);
|
|
1584
|
+
check("bootstrap ed25519: p= present", m !== null);
|
|
1585
|
+
check("bootstrap ed25519: p= is the 44-char raw key (RFC 8463), not 60-char SPKI",
|
|
1586
|
+
m !== null && m[1].length === 44);
|
|
1587
|
+
// And the published raw key round-trips: a signature from bootstrap's signer
|
|
1588
|
+
// verifies against the published p=.
|
|
1589
|
+
var rawB64 = m ? m[1] : "";
|
|
1590
|
+
check("bootstrap ed25519: published p= is a 32-byte key", Buffer.from(rawB64, "base64").length === 32);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
801
1593
|
async function run() {
|
|
802
1594
|
testDkimSurfaceAndValidation();
|
|
803
1595
|
testDkimCanonicalization();
|
|
@@ -807,6 +1599,10 @@ async function run() {
|
|
|
807
1599
|
testDkimSignerRejectsBadInput();
|
|
808
1600
|
testDkimRejectsLTagBodyLength();
|
|
809
1601
|
await testDkimVerifyHappyPath();
|
|
1602
|
+
await testDkimVerifyEd25519RawKeyRfc8463();
|
|
1603
|
+
await testDkimEd25519KFamilyConfusionRefused();
|
|
1604
|
+
testDkimBootstrapEd25519PublishesRawKey();
|
|
1605
|
+
await testDkimVerifyBareLfMessage();
|
|
810
1606
|
await testDkimVerifyKeyCacheHit();
|
|
811
1607
|
await testDkimVerifySmallKeyRejected();
|
|
812
1608
|
await testCalendarValidation();
|
|
@@ -827,6 +1623,28 @@ async function run() {
|
|
|
827
1623
|
await testDkimVerifyLTagCountsCanonicalizedOctets();
|
|
828
1624
|
await testDkimVerifyLTagAppendAfterSignatureRefused();
|
|
829
1625
|
await testArcAmsLTagAppendRefused();
|
|
1626
|
+
// Uncovered error / adversarial / defensive / option-default branches
|
|
1627
|
+
testDkimCreateInputBranches();
|
|
1628
|
+
testDkimSignerAuditDisabled();
|
|
1629
|
+
await testDkimEd25519VerifyRoundTrip();
|
|
1630
|
+
testDkimSimpleCanonDirect();
|
|
1631
|
+
await testDkimSimpleCanonRoundTrips();
|
|
1632
|
+
await testDkimSimpleCanonHonorsWireFieldName();
|
|
1633
|
+
await testDkimOpensslSimpleCanonInteropVector();
|
|
1634
|
+
await testDkimVerifyBadInputAndOptions();
|
|
1635
|
+
await testDkimVerifyNoSignatureHeaders();
|
|
1636
|
+
await testDkimVerifyBadVersion();
|
|
1637
|
+
await testDkimVerifyExpiredAndUnparseableDates();
|
|
1638
|
+
await testDkimVerifyValidDatesPassThrough();
|
|
1639
|
+
await testDkimVerifyMissingDomainSelector();
|
|
1640
|
+
await testDkimVerifyKeyLookupErrors();
|
|
1641
|
+
await testDkimVerifyKeyRecordShapes();
|
|
1642
|
+
await testDkimVerifySingleSignatureGuards();
|
|
1643
|
+
await testDkimVerifyCryptoThrowsPath();
|
|
1644
|
+
await testDkimKeyCacheExpiry();
|
|
1645
|
+
await testDkimKeyCacheEvictionBound();
|
|
1646
|
+
testDualSignerValidation();
|
|
1647
|
+
testBootstrapMoreValidation();
|
|
830
1648
|
}
|
|
831
1649
|
|
|
832
1650
|
module.exports = { run: run };
|