@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
|
@@ -16,11 +16,32 @@
|
|
|
16
16
|
* subjectaltname) and asserts: SAN-required when present, CN-fallback
|
|
17
17
|
* refusal, wildcard depth limits, partial-wildcard refusal, IP-SAN
|
|
18
18
|
* matching, and IPv6 canonicalization byte-equality.
|
|
19
|
+
*
|
|
20
|
+
* The sections after the PKIX tests drive the CA trust store, PQC
|
|
21
|
+
* key-share surface, OCSP parse/build/evaluate error paths,
|
|
22
|
+
* Certificate-Transparency SCT + Merkle-proof verifiers, the expiry /
|
|
23
|
+
* pinset-drift monitors, and the SNICallback wrapper through every
|
|
24
|
+
* wrong-state, malformed-input, and fault-injected branch reachable
|
|
25
|
+
* without opening a real socket — certificates are synthesised as DER
|
|
26
|
+
* via lib/asn1-der (shape-only; X509Certificate parses subject / issuer
|
|
27
|
+
* / validity / fingerprint / serial off the structure without verifying
|
|
28
|
+
* the signature) so the whole suite stays laptop-runnable.
|
|
19
29
|
*/
|
|
20
30
|
|
|
21
|
-
var helpers
|
|
22
|
-
var b
|
|
23
|
-
var check
|
|
31
|
+
var helpers = require("../helpers");
|
|
32
|
+
var b = helpers.b;
|
|
33
|
+
var check = helpers.check;
|
|
34
|
+
var nodeCrypto = require("node:crypto");
|
|
35
|
+
var nodeFs = require("node:fs");
|
|
36
|
+
var nodeOs = require("node:os");
|
|
37
|
+
var nodePath = require("node:path");
|
|
38
|
+
var nodeTls = require("node:tls");
|
|
39
|
+
var nodeNet = require("node:net");
|
|
40
|
+
var asn1 = require("../../lib/asn1-der");
|
|
41
|
+
var auditMod = require("../../lib/audit");
|
|
42
|
+
|
|
43
|
+
var nt = b.network.tls;
|
|
44
|
+
var C = b.constants;
|
|
24
45
|
|
|
25
46
|
// ---- ECHConfigList synthesis -------------------------------------
|
|
26
47
|
//
|
|
@@ -269,6 +290,58 @@ function testPkixSurface() {
|
|
|
269
290
|
typeof b.network.tls.checkServerIdentity9525 === "function");
|
|
270
291
|
}
|
|
271
292
|
|
|
293
|
+
function testPkixQuotedSanNoBypass() {
|
|
294
|
+
// Node renders a SAN value containing separators as a JSON-quoted string
|
|
295
|
+
// (CVE-2021-44531/44532). A naive split(",") would break `DNS:"x,
|
|
296
|
+
// DNS:victim.com, y"` — a SINGLE dNSName whose value is the whole quoted
|
|
297
|
+
// blob — into pieces and extract a clean `DNS:victim.com` the cert never
|
|
298
|
+
// asserts. The strict RFC 9525 verifier must be at least as strict as the
|
|
299
|
+
// Node function it replaces: REJECT, never accept the smuggled name.
|
|
300
|
+
var tls = require("node:tls");
|
|
301
|
+
var host = "victim.com";
|
|
302
|
+
var cert = _cert('DNS:"x, DNS:victim.com, y"', "attacker");
|
|
303
|
+
var bjsErr = b.network.tls.checkServerIdentity9525(host, cert);
|
|
304
|
+
var nodeErr = tls.checkServerIdentity(host, cert);
|
|
305
|
+
check("quoted-blob SAN does not smuggle a hostname (rejects, matching Node)",
|
|
306
|
+
!!bjsErr && !!nodeErr);
|
|
307
|
+
|
|
308
|
+
// Decoded control whitespace in a quoted dNSName must NOT be trimmed away:
|
|
309
|
+
// a quoted value that is a newline-escape then victim.com decodes to a
|
|
310
|
+
// leading LF + victim.com; a trim would reduce it to a bare victim.com that
|
|
311
|
+
// matches a name the certificate never asserts. The value is preserved
|
|
312
|
+
// verbatim, so it fails the exact match — as it does in Node. (Node may
|
|
313
|
+
// throw ERR_TLS_CERT_ALTNAME_FORMAT; either way it does not ACCEPT.)
|
|
314
|
+
function _nodeAccepts(h, c) { try { return tls.checkServerIdentity(h, c) === undefined; } catch (_e) { return false; } }
|
|
315
|
+
var leadCtl = _cert('DNS:"\\u000avictim.com"', "attacker");
|
|
316
|
+
check("leading control-char in quoted SAN is not trimmed into a match",
|
|
317
|
+
b.network.tls.checkServerIdentity9525("victim.com", leadCtl) !== undefined &&
|
|
318
|
+
_nodeAccepts("victim.com", leadCtl) === false);
|
|
319
|
+
var trailCtl = _cert('DNS:"victim.com\\u000a"', "attacker");
|
|
320
|
+
check("trailing control-char in quoted SAN is not trimmed into a match",
|
|
321
|
+
b.network.tls.checkServerIdentity9525("victim.com", trailCtl) !== undefined &&
|
|
322
|
+
_nodeAccepts("victim.com", trailCtl) === false);
|
|
323
|
+
|
|
324
|
+
// Same class on the IP path: a smuggled quoted IP value with a control char
|
|
325
|
+
// must not normalize (via a trim) into a clean IP that matches.
|
|
326
|
+
var ipCtl = _cert('IP Address:"198.51.100.1\\u000a"', "attacker");
|
|
327
|
+
check("control-char in quoted IP SAN does not match a clean IP",
|
|
328
|
+
b.network.tls.checkServerIdentity9525("198.51.100.1", ipCtl) !== undefined);
|
|
329
|
+
// Legitimate IP + bracketed IPv6 still match (no trim was needed for those).
|
|
330
|
+
check("legit IPv4 SAN still matches",
|
|
331
|
+
b.network.tls.checkServerIdentity9525("198.51.100.1",
|
|
332
|
+
_cert("IP Address:198.51.100.1")) === undefined);
|
|
333
|
+
// The genuine (whole-blob) dNSName is still matchable exactly.
|
|
334
|
+
var okErr = b.network.tls.checkServerIdentity9525("x, DNS:victim.com, y", cert);
|
|
335
|
+
check("the actual quoted dNSName value matches exactly", okErr === undefined);
|
|
336
|
+
// Legitimate unquoted multi-SAN + wildcard + IP remain accepted (parity).
|
|
337
|
+
check("legit multi-DNS still accepted",
|
|
338
|
+
b.network.tls.checkServerIdentity9525("b.example.com",
|
|
339
|
+
_cert("DNS:a.example.com, DNS:b.example.com")) === undefined);
|
|
340
|
+
check("legit wildcard still accepted",
|
|
341
|
+
b.network.tls.checkServerIdentity9525("foo.example.com",
|
|
342
|
+
_cert("DNS:*.example.com")) === undefined);
|
|
343
|
+
}
|
|
344
|
+
|
|
272
345
|
function testPkixSanRequiredWhenAbsent() {
|
|
273
346
|
// SAN missing entirely + no CN -> tls/pkix-san-required.
|
|
274
347
|
var err = b.network.tls.checkServerIdentity9525("foo.example.com",
|
|
@@ -486,6 +559,1732 @@ function testNetworkTlsErrorPermanentClassification() {
|
|
|
486
559
|
new TlsTrustError("tls/connect-failed", "x").permanent === true);
|
|
487
560
|
}
|
|
488
561
|
|
|
562
|
+
var OID_TLS_FEATURE = "1.3.6.1.5.5.7.1.24";
|
|
563
|
+
var OID_CT_SCT_LIST = "1.3.6.1.4.1.11129.2.4.2";
|
|
564
|
+
var OID_OCSP_NONCE = "1.3.6.1.5.5.7.48.1.2";
|
|
565
|
+
|
|
566
|
+
// ---- synthetic-cert builders --------------------------------------
|
|
567
|
+
|
|
568
|
+
function _synthCert(opts) {
|
|
569
|
+
opts = opts || {};
|
|
570
|
+
var serial = opts.serial || Buffer.from([0x12, 0x34]);
|
|
571
|
+
var cn = opts.cn || "Test CA";
|
|
572
|
+
var keyBytes = opts.keyBytes || Buffer.from("k-bytes-aaaaaaaaaaaaaaaaaaaaaaaa");
|
|
573
|
+
var notBefore = opts.notBefore || "260101000000Z";
|
|
574
|
+
var notAfter = opts.notAfter || "270101000000Z";
|
|
575
|
+
var algId = asn1.writeSequence([asn1.writeOid("1.2.840.113549.1.1.1"), asn1.writeNull()]);
|
|
576
|
+
var cnrdn = asn1.writeSequence([asn1.writeOid("2.5.4.3"), asn1.writeNode(0x0c, Buffer.from(cn, "ascii"))]);
|
|
577
|
+
var name = asn1.writeSequence([asn1.writeNode(0x31, cnrdn)]);
|
|
578
|
+
var validity = asn1.writeSequence([
|
|
579
|
+
asn1.writeNode(0x17, Buffer.from(notBefore, "ascii")),
|
|
580
|
+
asn1.writeNode(0x17, Buffer.from(notAfter, "ascii")),
|
|
581
|
+
]);
|
|
582
|
+
var spki = asn1.writeSequence([algId,
|
|
583
|
+
asn1.writeNode(0x03, Buffer.concat([Buffer.from([0]), keyBytes]))]);
|
|
584
|
+
var version = asn1.writeContextExplicit(0, asn1.writeInteger(Buffer.from([2])));
|
|
585
|
+
var tbsKids = [version, asn1.writeInteger(serial), algId, name, validity, name, spki];
|
|
586
|
+
if (opts.exts && opts.exts.length) {
|
|
587
|
+
tbsKids.push(asn1.writeContextExplicit(3, asn1.writeSequence(opts.exts)));
|
|
588
|
+
}
|
|
589
|
+
var tbs = asn1.writeSequence(tbsKids);
|
|
590
|
+
return asn1.writeSequence([tbs, algId, asn1.writeNode(0x03, Buffer.from([0, 0, 0, 0]))]);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function _toPem(der) {
|
|
594
|
+
return "-----BEGIN CERTIFICATE-----\n" +
|
|
595
|
+
der.toString("base64").replace(/(.{64})/g, "$1\n") +
|
|
596
|
+
"\n-----END CERTIFICATE-----\n";
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Build a REAL, handshake-valid self-signed EC leaf cert (P-256) so a
|
|
600
|
+
// localhost tls.createServer can complete a TLS handshake. Unlike
|
|
601
|
+
// _synthCert (fake [0,0,0,0] signature, shape-only for X509 field
|
|
602
|
+
// parsing), this embeds the true SPKI and an ECDSA-SHA256 signature over
|
|
603
|
+
// the tbsCertificate, so a client that connects with
|
|
604
|
+
// rejectUnauthorized:false completes 'secureConnect'. Serial defaults to
|
|
605
|
+
// _SERIAL so a _buildOcsp staple (default serial _SERIAL) binds to the
|
|
606
|
+
// connected peer's serialNumber in ocsp.requireGood.
|
|
607
|
+
function _makeRealSelfSignedCert(serial) {
|
|
608
|
+
var kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
609
|
+
var spkiDer = kp.publicKey.export({ type: "spki", format: "der" });
|
|
610
|
+
var sigAlgId = asn1.writeSequence([asn1.writeOid("1.2.840.10045.4.3.2")]); // ecdsa-with-SHA256
|
|
611
|
+
var cnrdn = asn1.writeSequence([asn1.writeOid("2.5.4.3"),
|
|
612
|
+
asn1.writeNode(0x0c, Buffer.from("localhost", "ascii"))]);
|
|
613
|
+
var name = asn1.writeSequence([asn1.writeNode(0x31, cnrdn)]);
|
|
614
|
+
var validity = asn1.writeSequence([
|
|
615
|
+
asn1.writeNode(0x17, Buffer.from("250101000000Z", "ascii")),
|
|
616
|
+
asn1.writeNode(0x17, Buffer.from("350101000000Z", "ascii")),
|
|
617
|
+
]);
|
|
618
|
+
var version = asn1.writeContextExplicit(0, asn1.writeInteger(Buffer.from([2])));
|
|
619
|
+
var tbs = asn1.writeSequence([version, asn1.writeInteger(serial || _SERIAL),
|
|
620
|
+
sigAlgId, name, validity, name, spkiDer]);
|
|
621
|
+
var sig = nodeCrypto.sign("sha256", tbs, kp.privateKey);
|
|
622
|
+
var certDer = asn1.writeSequence([tbs, sigAlgId, asn1.writeBitString(sig)]);
|
|
623
|
+
return {
|
|
624
|
+
certPem: _toPem(certDer),
|
|
625
|
+
keyPem: kp.privateKey.export({ type: "pkcs8", format: "pem" }),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Start a localhost TLS server presenting the real self-signed cert. When
|
|
630
|
+
// `staple` is a Buffer, the server answers the client's requestOCSP with
|
|
631
|
+
// it via the 'OCSPRequest' event. Returns { srv, port, close }.
|
|
632
|
+
function _startTlsServer(staple) {
|
|
633
|
+
return new Promise(function (resolve) {
|
|
634
|
+
var m = _makeRealSelfSignedCert(_SERIAL);
|
|
635
|
+
var srv = nodeTls.createServer(
|
|
636
|
+
{ key: m.keyPem, cert: m.certPem, minVersion: "TLSv1.2" },
|
|
637
|
+
function (sock) { sock.on("error", function () { /* peer reset */ }); });
|
|
638
|
+
if (Buffer.isBuffer(staple)) {
|
|
639
|
+
srv.on("OCSPRequest", function (_cert, _issuer, cb) { cb(null, staple); });
|
|
640
|
+
}
|
|
641
|
+
srv.on("error", function () { /* listen/accept best-effort */ });
|
|
642
|
+
srv.unref();
|
|
643
|
+
srv.listen(0, "127.0.0.1", function () {
|
|
644
|
+
resolve({
|
|
645
|
+
srv: srv,
|
|
646
|
+
port: srv.address().port,
|
|
647
|
+
close: function () { try { srv.close(); } catch (_e) { /* best-effort */ } },
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function _mustStapleExt() {
|
|
654
|
+
return asn1.writeSequence([
|
|
655
|
+
asn1.writeOid(OID_TLS_FEATURE),
|
|
656
|
+
asn1.writeOctetString(asn1.writeSequence([asn1.writeInteger(Buffer.from([5]))])),
|
|
657
|
+
]);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function _buildSctBytes(opts) {
|
|
661
|
+
opts = opts || {};
|
|
662
|
+
var logId = opts.logId || Buffer.alloc(32, 0xaa);
|
|
663
|
+
var ts = Buffer.alloc(8); ts.writeBigUInt64BE(BigInt(opts.timestamp || 1700000000000));
|
|
664
|
+
var extVec = Buffer.from([0x00, 0x00]);
|
|
665
|
+
var sig = opts.sig || Buffer.from("sigbytes!");
|
|
666
|
+
var sigLen = Buffer.alloc(2); sigLen.writeUInt16BE(sig.length);
|
|
667
|
+
return Buffer.concat([
|
|
668
|
+
Buffer.from([opts.version === undefined ? 0 : opts.version]),
|
|
669
|
+
logId, ts, extVec,
|
|
670
|
+
Buffer.from([opts.hashAlgo === undefined ? 4 : opts.hashAlgo,
|
|
671
|
+
opts.sigAlgo === undefined ? 3 : opts.sigAlgo]),
|
|
672
|
+
sigLen, sig,
|
|
673
|
+
]);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function _sctListRaw(sctBytesArr, opts) {
|
|
677
|
+
opts = opts || {};
|
|
678
|
+
var parts = [];
|
|
679
|
+
for (var i = 0; i < sctBytesArr.length; i += 1) {
|
|
680
|
+
var l = Buffer.alloc(2); l.writeUInt16BE(sctBytesArr[i].length);
|
|
681
|
+
parts.push(l, sctBytesArr[i]);
|
|
682
|
+
}
|
|
683
|
+
var body = Buffer.concat(parts);
|
|
684
|
+
var outer = Buffer.alloc(2);
|
|
685
|
+
outer.writeUInt16BE(opts.lieOuterLen === undefined ? body.length : opts.lieOuterLen);
|
|
686
|
+
return Buffer.concat([outer, body]);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function _sctExt(sctListRaw) {
|
|
690
|
+
return asn1.writeSequence([
|
|
691
|
+
asn1.writeOid(OID_CT_SCT_LIST),
|
|
692
|
+
asn1.writeOctetString(asn1.writeOctetString(sctListRaw)),
|
|
693
|
+
]);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Build a signed BasicOCSPResponse over a single (serial, issuer) CertID.
|
|
697
|
+
function _buildOcsp(o) {
|
|
698
|
+
o = o || {};
|
|
699
|
+
var serial = o.serial || Buffer.from([0x12, 0x34, 0x56, 0x78]);
|
|
700
|
+
var issuerDer = o.issuerDer;
|
|
701
|
+
var kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
702
|
+
var issuerPem = kp.publicKey.export({ type: "spki", format: "pem" });
|
|
703
|
+
var req = nt.ocsp.buildRequest({
|
|
704
|
+
leafCertDer: _synthCert({ serial: serial, cn: "Leaf", keyBytes: Buffer.from("leaf-key-bytes-aaaaaaaaaaaaaaaa") }),
|
|
705
|
+
issuerCertDer: issuerDer,
|
|
706
|
+
nonce: false,
|
|
707
|
+
});
|
|
708
|
+
var reqTop = asn1.readNode(req.requestDer);
|
|
709
|
+
var reqTbs = asn1.readSequence(reqTop.value)[0];
|
|
710
|
+
var reqList = asn1.readSequence(reqTbs.value)[0];
|
|
711
|
+
var reqOne = asn1.readSequence(reqList.value)[0];
|
|
712
|
+
var certId = asn1.readSequence(reqOne.value)[0];
|
|
713
|
+
|
|
714
|
+
var certStatus;
|
|
715
|
+
if (o.status === "revoked") {
|
|
716
|
+
certStatus = asn1.writeContextImplicit(1, asn1.writeNode(0x18, Buffer.from("20250101000000Z")));
|
|
717
|
+
} else if (o.status === "unknown") {
|
|
718
|
+
certStatus = asn1.writeContextImplicit(2, Buffer.alloc(0));
|
|
719
|
+
} else {
|
|
720
|
+
certStatus = asn1.writeContextImplicit(0, Buffer.alloc(0));
|
|
721
|
+
}
|
|
722
|
+
var timeTag = o.timeTag === undefined ? 0x18 : o.timeTag;
|
|
723
|
+
var thisU = asn1.writeNode(timeTag, Buffer.from(o.thisUpdate || "20250615000000Z"));
|
|
724
|
+
var srKids = [certId.raw, certStatus, thisU];
|
|
725
|
+
if (o.nextUpdate !== null) {
|
|
726
|
+
srKids.push(asn1.writeContextExplicit(0, asn1.writeNode(0x18,
|
|
727
|
+
Buffer.from(o.nextUpdate || "20991231000000Z"))));
|
|
728
|
+
}
|
|
729
|
+
var singleResponse = asn1.writeSequence(srKids);
|
|
730
|
+
var responderId = asn1.writeContextExplicit(2, asn1.writeOctetString(Buffer.alloc(20, 0xcc)));
|
|
731
|
+
var producedAt = asn1.writeNode(0x18, Buffer.from("20250615000000Z"));
|
|
732
|
+
var responses = asn1.writeSequence([singleResponse]);
|
|
733
|
+
var rdKids = [responderId, producedAt, responses];
|
|
734
|
+
if (o.nonce) {
|
|
735
|
+
var nonceExt = asn1.writeSequence([
|
|
736
|
+
asn1.writeOid(OID_OCSP_NONCE),
|
|
737
|
+
asn1.writeOctetString(asn1.writeOctetString(o.nonce)),
|
|
738
|
+
]);
|
|
739
|
+
rdKids.push(asn1.writeContextExplicit(1, asn1.writeSequence([nonceExt])));
|
|
740
|
+
}
|
|
741
|
+
var tbs = asn1.writeSequence(rdKids);
|
|
742
|
+
var sig = o.badSig
|
|
743
|
+
? Buffer.alloc(70, 0x00)
|
|
744
|
+
: nodeCrypto.sign("sha256", tbs, kp.privateKey);
|
|
745
|
+
var sigAlg = asn1.writeSequence([asn1.writeOid(o.sigAlgOid || "1.2.840.10045.4.3.2")]);
|
|
746
|
+
var basic = asn1.writeSequence([tbs, sigAlg, asn1.writeBitString(sig)]);
|
|
747
|
+
var rbInner = asn1.writeSequence([asn1.writeOid("1.3.6.1.5.5.7.48.1.1"), asn1.writeOctetString(basic)]);
|
|
748
|
+
var der = asn1.writeSequence([
|
|
749
|
+
asn1.writeNode(0x0a, Buffer.from([0])),
|
|
750
|
+
asn1.writeContextExplicit(0, rbInner),
|
|
751
|
+
]);
|
|
752
|
+
return { der: der, issuerPem: issuerPem };
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function _ctLeafHash(signedEntryDer, ts) {
|
|
756
|
+
var tsBuf = Buffer.alloc(8); tsBuf.writeBigUInt64BE(BigInt(Math.floor(ts)));
|
|
757
|
+
var lenBuf = Buffer.alloc(3); lenBuf.writeUIntBE(signedEntryDer.length, 0, 3);
|
|
758
|
+
var leafBytes = Buffer.concat([
|
|
759
|
+
Buffer.from([0]), Buffer.from([0]), tsBuf, Buffer.from([0, 0]),
|
|
760
|
+
lenBuf, signedEntryDer, Buffer.from([0, 0]),
|
|
761
|
+
]);
|
|
762
|
+
return nodeCrypto.createHash("sha256")
|
|
763
|
+
.update(Buffer.concat([Buffer.from([0]), leafBytes])).digest();
|
|
764
|
+
}
|
|
765
|
+
function _ctInner(left, right) {
|
|
766
|
+
return nodeCrypto.createHash("sha256")
|
|
767
|
+
.update(Buffer.concat([Buffer.from([1]), left, right])).digest();
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
var _SERIAL = Buffer.from([0x12, 0x34, 0x56, 0x78]);
|
|
771
|
+
var _NOW = Date.parse("2025-06-15T00:00:01Z");
|
|
772
|
+
|
|
773
|
+
// =====================================================================
|
|
774
|
+
// CA trust store
|
|
775
|
+
// =====================================================================
|
|
776
|
+
|
|
777
|
+
function testAddCaShapes() {
|
|
778
|
+
nt._resetForTest();
|
|
779
|
+
var der = _synthCert({ cn: "Alpha CA" });
|
|
780
|
+
var added = nt.addCa(_toPem(der), { label: "alpha" });
|
|
781
|
+
check("addCa(string PEM) returns one meta", Array.isArray(added) && added.length === 1);
|
|
782
|
+
check("addCa meta carries subject", added[0].subject === "CN=Alpha CA");
|
|
783
|
+
|
|
784
|
+
// Buffer input
|
|
785
|
+
var addedBuf = nt.addCa(Buffer.from(_toPem(_synthCert({ cn: "Beta CA" })), "utf8"), { label: "beta" });
|
|
786
|
+
check("addCa(Buffer PEM) works", addedBuf.length === 1 && addedBuf[0].subject === "CN=Beta CA");
|
|
787
|
+
|
|
788
|
+
// Bundle with two CERTIFICATE blocks -> two metas.
|
|
789
|
+
var bundle = _toPem(_synthCert({ cn: "Gamma CA", serial: Buffer.from([0x0a]) })) +
|
|
790
|
+
_toPem(_synthCert({ cn: "Delta CA", serial: Buffer.from([0x0b]) }));
|
|
791
|
+
var addedBundle = nt.addCaBundle(bundle, { label: "bundle" });
|
|
792
|
+
check("addCaBundle with two blocks returns two metas", addedBundle.length === 2);
|
|
793
|
+
|
|
794
|
+
check("getTrustStore reflects all four", nt.getTrustStore().length === 4);
|
|
795
|
+
check("getCaPems returns four PEMs", nt.getCaPems().length === 4);
|
|
796
|
+
var store = nt.getTrustStore();
|
|
797
|
+
check("getTrustStore entry exposes fingerprint256 + label",
|
|
798
|
+
typeof store[0].fingerprint256 === "string" && store[0].label === "alpha");
|
|
799
|
+
nt._resetForTest();
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function testAddCaRejections() {
|
|
803
|
+
nt._resetForTest();
|
|
804
|
+
// Non-string non-Buffer -> tls/bad-ca via _normalizePem.
|
|
805
|
+
var e1 = null;
|
|
806
|
+
try { nt.addCa(12345); } catch (e) { e1 = e; }
|
|
807
|
+
check("addCa(number) throws tls/bad-ca", e1 && e1.code === "tls/bad-ca");
|
|
808
|
+
|
|
809
|
+
// Path-like string that is not a readable path -> tls/empty-pem.
|
|
810
|
+
var e2 = null;
|
|
811
|
+
try { nt.addCa("/no/such/path/to/ca.pem"); } catch (e) { e2 = e; }
|
|
812
|
+
check("addCa(nonexistent path) throws tls/empty-pem", e2 && e2.code === "tls/empty-pem");
|
|
813
|
+
|
|
814
|
+
// Has a BEGIN marker (not path-like) but no CERTIFICATE block -> empty-pem.
|
|
815
|
+
var e3 = null;
|
|
816
|
+
try { nt.addCa("-----BEGIN PUBLIC KEY-----\nAAAA\n-----END PUBLIC KEY-----"); }
|
|
817
|
+
catch (e) { e3 = e; }
|
|
818
|
+
check("addCa(non-CERTIFICATE PEM) throws tls/empty-pem", e3 && e3.code === "tls/empty-pem");
|
|
819
|
+
|
|
820
|
+
// CERTIFICATE block with unparseable body -> tls/bad-ca-pem.
|
|
821
|
+
var e4 = null;
|
|
822
|
+
try { nt.addCa("-----BEGIN CERTIFICATE-----\nnot valid base64 @@@\n-----END CERTIFICATE-----"); }
|
|
823
|
+
catch (e) { e4 = e; }
|
|
824
|
+
check("addCa(garbage CERTIFICATE body) throws tls/bad-ca-pem", e4 && e4.code === "tls/bad-ca-pem");
|
|
825
|
+
|
|
826
|
+
// Unknown opt key -> validateOpts.
|
|
827
|
+
var e5 = null;
|
|
828
|
+
try { nt.addCa(_toPem(_synthCert({})), { nope: true }); } catch (e) { e5 = e; }
|
|
829
|
+
check("addCa unknown opt throws via validateOpts", e5 && /unknown option/.test(e5.message));
|
|
830
|
+
nt._resetForTest();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function testAddCaFromFileAndDir() {
|
|
834
|
+
nt._resetForTest();
|
|
835
|
+
var dir = nodeFs.mkdtempSync(nodePath.join(nodeOs.tmpdir(), "blamejs-tls-ca-"));
|
|
836
|
+
try {
|
|
837
|
+
var file = nodePath.join(dir, "single.pem");
|
|
838
|
+
nodeFs.writeFileSync(file, _toPem(_synthCert({ cn: "File CA" })));
|
|
839
|
+
var addedFile = nt.addCa(file, { label: "from-file" });
|
|
840
|
+
check("addCa(file path) reads + parses PEM", addedFile.length === 1 && addedFile[0].subject === "CN=File CA");
|
|
841
|
+
|
|
842
|
+
// Directory of certs (only .pem/.crt/.cer are read, sorted).
|
|
843
|
+
var certDir = nodePath.join(dir, "bundle");
|
|
844
|
+
nodeFs.mkdirSync(certDir);
|
|
845
|
+
nodeFs.writeFileSync(nodePath.join(certDir, "a.pem"), _toPem(_synthCert({ cn: "Dir A", serial: Buffer.from([0x21]) })));
|
|
846
|
+
nodeFs.writeFileSync(nodePath.join(certDir, "b.crt"), _toPem(_synthCert({ cn: "Dir B", serial: Buffer.from([0x22]) })));
|
|
847
|
+
nodeFs.writeFileSync(nodePath.join(certDir, "ignore.txt"), "not a cert");
|
|
848
|
+
var addedDir = nt.addCa(certDir, { label: "from-dir" });
|
|
849
|
+
check("addCa(directory) reads only .pem/.crt/.cer", addedDir.length === 2);
|
|
850
|
+
} finally {
|
|
851
|
+
nodeFs.rmSync(dir, { recursive: true, force: true });
|
|
852
|
+
nt._resetForTest();
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function testRemoveCa() {
|
|
857
|
+
nt._resetForTest();
|
|
858
|
+
var added = nt.addCa(_toPem(_synthCert({ cn: "Rm CA" })), { label: "rm" });
|
|
859
|
+
var fp = added[0].fingerprint256;
|
|
860
|
+
|
|
861
|
+
var eBad = null;
|
|
862
|
+
try { nt.removeCa(""); } catch (e) { eBad = e; }
|
|
863
|
+
check("removeCa('') throws tls/bad-fingerprint", eBad && eBad.code === "tls/bad-fingerprint");
|
|
864
|
+
var eBad2 = null;
|
|
865
|
+
try { nt.removeCa(12345); } catch (e) { eBad2 = e; }
|
|
866
|
+
check("removeCa(non-string) throws tls/bad-fingerprint", eBad2 && eBad2.code === "tls/bad-fingerprint");
|
|
867
|
+
|
|
868
|
+
check("removeCa(unknown fp) returns 0", nt.removeCa("AA:BB:CC") === 0);
|
|
869
|
+
// Lower-case + match on real fingerprint.
|
|
870
|
+
check("removeCa(known fp, case-insensitive) returns 1", nt.removeCa(fp.toLowerCase()) === 1);
|
|
871
|
+
check("store empty after remove", nt.getTrustStore().length === 0);
|
|
872
|
+
nt._resetForTest();
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function testRemoveCaByLabel() {
|
|
876
|
+
nt._resetForTest();
|
|
877
|
+
nt.addCa(_toPem(_synthCert({ cn: "L1" })), { label: "keep" });
|
|
878
|
+
nt.addCa(_toPem(_synthCert({ cn: "L2", serial: Buffer.from([0x31]) })), { label: "drop" });
|
|
879
|
+
nt.addCa(_toPem(_synthCert({ cn: "L3", serial: Buffer.from([0x32]) })), { label: "drop" });
|
|
880
|
+
|
|
881
|
+
var eBad = null;
|
|
882
|
+
try { nt.removeCaByLabel(""); } catch (e) { eBad = e; }
|
|
883
|
+
check("removeCaByLabel('') throws tls/bad-label", eBad && eBad.code === "tls/bad-label");
|
|
884
|
+
|
|
885
|
+
check("removeCaByLabel(unknown) returns 0", nt.removeCaByLabel("nope") === 0);
|
|
886
|
+
check("removeCaByLabel('drop') removes both", nt.removeCaByLabel("drop", { audit: false }) === 2);
|
|
887
|
+
check("one entry survives", nt.getTrustStore().length === 1);
|
|
888
|
+
nt._resetForTest();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function testClearAll() {
|
|
892
|
+
nt._resetForTest();
|
|
893
|
+
check("clearAll on empty store returns 0", nt.clearAll() === 0);
|
|
894
|
+
nt.addCa(_toPem(_synthCert({ cn: "C1" })), {});
|
|
895
|
+
nt.addCa(_toPem(_synthCert({ cn: "C2", serial: Buffer.from([0x41]) })), {});
|
|
896
|
+
check("clearAll returns removed count", nt.clearAll({ audit: false }) === 2);
|
|
897
|
+
check("store empty after clearAll", nt.getTrustStore().length === 0);
|
|
898
|
+
nt._resetForTest();
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function testPurgeExpired() {
|
|
902
|
+
nt._resetForTest();
|
|
903
|
+
nt.addCa(_toPem(_synthCert({ cn: "Fresh", notAfter: "270101000000Z" })), { label: "fresh" });
|
|
904
|
+
nt.addCa(_toPem(_synthCert({ cn: "Expired", serial: Buffer.from([0x51]), notBefore: "190101000000Z", notAfter: "200101000000Z" })), { label: "expired" });
|
|
905
|
+
var removed = nt.purgeExpired({ audit: false });
|
|
906
|
+
check("purgeExpired removes the expired cert only", removed === 1);
|
|
907
|
+
var store = nt.getTrustStore();
|
|
908
|
+
check("only the fresh cert survives purge", store.length === 1 && store[0].label === "fresh");
|
|
909
|
+
check("purgeExpired again returns 0 (nothing left to purge)", nt.purgeExpired() === 0);
|
|
910
|
+
nt._resetForTest();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function testExpiringSoon() {
|
|
914
|
+
nt._resetForTest();
|
|
915
|
+
var eBad = null;
|
|
916
|
+
try { nt.expiringSoon(-1); } catch (e) { eBad = e; }
|
|
917
|
+
check("expiringSoon(negative) throws tls/bad-window", eBad && eBad.code === "tls/bad-window");
|
|
918
|
+
var eInf = null;
|
|
919
|
+
try { nt.expiringSoon(Infinity); } catch (e) { eInf = e; }
|
|
920
|
+
check("expiringSoon(Infinity) throws tls/bad-window", eInf && eInf.code === "tls/bad-window");
|
|
921
|
+
|
|
922
|
+
nt.addCa(_toPem(_synthCert({ cn: "Soon", notAfter: "270101000000Z" })), { label: "soon" });
|
|
923
|
+
var big = nt.expiringSoon(C.TIME.days(3650));
|
|
924
|
+
check("expiringSoon with wide window lists the cert", big.length === 1 && big[0].label === "soon");
|
|
925
|
+
var none = nt.expiringSoon(0);
|
|
926
|
+
check("expiringSoon(0) lists nothing not-yet-past", none.length === 0);
|
|
927
|
+
nt._resetForTest();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function testSystemTrustAndApplyToContext() {
|
|
931
|
+
nt._resetForTest();
|
|
932
|
+
check("isSystemTrustEnabled false by default", nt.isSystemTrustEnabled() === false);
|
|
933
|
+
nt.useSystemTrust(true);
|
|
934
|
+
check("useSystemTrust(true) enables", nt.isSystemTrustEnabled() === true);
|
|
935
|
+
|
|
936
|
+
nt.addCa(_toPem(_synthCert({ cn: "Ctx CA" })), {});
|
|
937
|
+
var ctx = nt.applyToContext({ base: { rejectUnauthorized: true } });
|
|
938
|
+
check("applyToContext preserves base keys", ctx.rejectUnauthorized === true);
|
|
939
|
+
check("applyToContext sets ca array", Array.isArray(ctx.ca) && ctx.ca.length >= 1);
|
|
940
|
+
check("applyToContext sets groups from key shares",
|
|
941
|
+
typeof ctx.groups === "string" && ctx.groups.indexOf("X25519MLKEM768") === 0);
|
|
942
|
+
check("systemTrust folds in root certificates",
|
|
943
|
+
ctx.ca.length > 1 || nodeTlsHasNoRoots());
|
|
944
|
+
|
|
945
|
+
// Operator-supplied groups override is preserved.
|
|
946
|
+
var ctx2 = nt.applyToContext({ base: { groups: "X25519" } });
|
|
947
|
+
check("applyToContext keeps operator groups override", ctx2.groups === "X25519");
|
|
948
|
+
|
|
949
|
+
nt.useSystemTrust(false);
|
|
950
|
+
check("useSystemTrust(false) disables", nt.isSystemTrustEnabled() === false);
|
|
951
|
+
|
|
952
|
+
var eBad = null;
|
|
953
|
+
try { nt.applyToContext({ nope: 1 }); } catch (e) { eBad = e; }
|
|
954
|
+
check("applyToContext unknown opt throws via validateOpts", eBad && /unknown option/.test(eBad.message));
|
|
955
|
+
nt._resetForTest();
|
|
956
|
+
}
|
|
957
|
+
function nodeTlsHasNoRoots() {
|
|
958
|
+
return !Array.isArray(nodeTls.rootCertificates);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function testBaselineDrift() {
|
|
962
|
+
nt._resetForTest();
|
|
963
|
+
check("detectBaselineDrift null before capture", nt.detectBaselineDrift() === null);
|
|
964
|
+
nt.captureBaselineFingerprints();
|
|
965
|
+
var drift0 = nt.detectBaselineDrift();
|
|
966
|
+
check("no drift right after capture", drift0 && drift0.drifted === false);
|
|
967
|
+
|
|
968
|
+
var added = nt.addCa(_toPem(_synthCert({ cn: "Drift CA" })), {});
|
|
969
|
+
var drift1 = nt.detectBaselineDrift();
|
|
970
|
+
check("adding a CA registers as drift (added)",
|
|
971
|
+
drift1 && drift1.drifted === true && drift1.added.indexOf(added[0].fingerprint256) !== -1);
|
|
972
|
+
|
|
973
|
+
nt.captureBaselineFingerprints();
|
|
974
|
+
nt.removeCa(added[0].fingerprint256);
|
|
975
|
+
var drift2 = nt.detectBaselineDrift();
|
|
976
|
+
check("removing a CA registers as drift (removed)",
|
|
977
|
+
drift2 && drift2.drifted === true && drift2.removed.length === 1);
|
|
978
|
+
nt._resetForTest();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// =====================================================================
|
|
982
|
+
// expiry / pinset-drift monitors
|
|
983
|
+
// =====================================================================
|
|
984
|
+
|
|
985
|
+
function testMonitorValidation() {
|
|
986
|
+
var e1 = null;
|
|
987
|
+
try { nt.expiryMonitor({ intervalMs: 0, windowMs: 1000 }); } catch (e) { e1 = e; }
|
|
988
|
+
check("expiryMonitor bad intervalMs throws tls/bad-interval", e1 && e1.code === "tls/bad-interval");
|
|
989
|
+
var e2 = null;
|
|
990
|
+
try { nt.expiryMonitor({ intervalMs: 1000, windowMs: -1 }); } catch (e) { e2 = e; }
|
|
991
|
+
check("expiryMonitor bad windowMs throws tls/bad-window", e2 && e2.code === "tls/bad-window");
|
|
992
|
+
var e3 = null;
|
|
993
|
+
try { nt.pinsetDriftMonitor({ intervalMs: Infinity }); } catch (e) { e3 = e; }
|
|
994
|
+
check("pinsetDriftMonitor bad intervalMs throws tls/bad-interval", e3 && e3.code === "tls/bad-interval");
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function testExpiryMonitorTick() {
|
|
998
|
+
nt._resetForTest();
|
|
999
|
+
nt.addCa(_toPem(_synthCert({ cn: "Mon CA", notAfter: "270101000000Z" })), { label: "mon" });
|
|
1000
|
+
var seen = 0;
|
|
1001
|
+
var lastRows = null;
|
|
1002
|
+
var mon = nt.expiryMonitor({
|
|
1003
|
+
intervalMs: 15,
|
|
1004
|
+
windowMs: C.TIME.days(3650),
|
|
1005
|
+
onExpiring: function (rows) { seen += 1; lastRows = rows; },
|
|
1006
|
+
});
|
|
1007
|
+
try {
|
|
1008
|
+
await helpers.waitUntil(function () { return seen >= 1; },
|
|
1009
|
+
{ timeoutMs: 5000, label: "expiryMonitor: onExpiring fired" });
|
|
1010
|
+
check("expiryMonitor tick invoked onExpiring", seen >= 1);
|
|
1011
|
+
check("onExpiring received the expiring row", lastRows && lastRows.length === 1 && lastRows[0].label === "mon");
|
|
1012
|
+
} finally {
|
|
1013
|
+
mon.stop();
|
|
1014
|
+
mon.stop(); // idempotent second stop is a no-op
|
|
1015
|
+
nt._resetForTest();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
async function testPinsetDriftMonitorTick() {
|
|
1020
|
+
nt._resetForTest();
|
|
1021
|
+
nt.captureBaselineFingerprints(); // baseline = [] (empty store)
|
|
1022
|
+
nt.addCa(_toPem(_synthCert({ cn: "Drift Mon CA" })), {}); // now drifts vs baseline
|
|
1023
|
+
var seen = 0;
|
|
1024
|
+
var lastDrift = null;
|
|
1025
|
+
var mon = nt.pinsetDriftMonitor({
|
|
1026
|
+
intervalMs: 15,
|
|
1027
|
+
onDrift: function (d) { seen += 1; lastDrift = d; },
|
|
1028
|
+
});
|
|
1029
|
+
try {
|
|
1030
|
+
await helpers.waitUntil(function () { return seen >= 1; },
|
|
1031
|
+
{ timeoutMs: 5000, label: "pinsetDriftMonitor: onDrift fired" });
|
|
1032
|
+
check("pinsetDriftMonitor tick invoked onDrift", seen >= 1);
|
|
1033
|
+
check("onDrift reports the added fingerprint", lastDrift && lastDrift.added.length === 1);
|
|
1034
|
+
} finally {
|
|
1035
|
+
mon.stop();
|
|
1036
|
+
nt._resetForTest();
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// =====================================================================
|
|
1041
|
+
// PQC key shares
|
|
1042
|
+
// =====================================================================
|
|
1043
|
+
|
|
1044
|
+
function testPqcKeyShares() {
|
|
1045
|
+
nt._resetForTest();
|
|
1046
|
+
var def = nt.pqc.getKeyShares();
|
|
1047
|
+
check("getKeyShares returns default list", Array.isArray(def) && def[0] === "X25519MLKEM768");
|
|
1048
|
+
|
|
1049
|
+
var afterSet = nt.pqc.setKeyShares(["X25519MLKEM768", "X25519"]);
|
|
1050
|
+
check("setKeyShares narrows the list", afterSet.length === 2 && afterSet[1] === "X25519");
|
|
1051
|
+
|
|
1052
|
+
var eArr = null;
|
|
1053
|
+
try { nt.pqc.setKeyShares("X25519"); } catch (e) { eArr = e; }
|
|
1054
|
+
check("setKeyShares(non-array) throws tls/bad-key-shares", eArr && eArr.code === "tls/bad-key-shares");
|
|
1055
|
+
var eEmpty = null;
|
|
1056
|
+
try { nt.pqc.setKeyShares([]); } catch (e) { eEmpty = e; }
|
|
1057
|
+
check("setKeyShares([]) throws tls/bad-key-shares", eEmpty && eEmpty.code === "tls/bad-key-shares");
|
|
1058
|
+
var eEntry = null;
|
|
1059
|
+
try { nt.pqc.setKeyShares([""]); } catch (e) { eEntry = e; }
|
|
1060
|
+
check("setKeyShares(empty entry) throws tls/bad-key-share", eEntry && eEntry.code === "tls/bad-key-share");
|
|
1061
|
+
var eColon = null;
|
|
1062
|
+
try { nt.pqc.setKeyShares(["X25519:X25519"]); } catch (e) { eColon = e; }
|
|
1063
|
+
check("setKeyShares(entry with ':') throws tls/bad-key-share", eColon && eColon.code === "tls/bad-key-share");
|
|
1064
|
+
var eLong = null;
|
|
1065
|
+
try { nt.pqc.setKeyShares([new Array(66).join("a")]); } catch (e) { eLong = e; }
|
|
1066
|
+
check("setKeyShares(>64-char entry) throws tls/bad-key-share", eLong && eLong.code === "tls/bad-key-share");
|
|
1067
|
+
var eNum = null;
|
|
1068
|
+
try { nt.pqc.setKeyShares([123]); } catch (e) { eNum = e; }
|
|
1069
|
+
check("setKeyShares(non-string entry) throws tls/bad-key-share", eNum && eNum.code === "tls/bad-key-share");
|
|
1070
|
+
|
|
1071
|
+
var reset = nt.pqc.resetKeyShares();
|
|
1072
|
+
check("resetKeyShares restores default", reset.length === 4 && reset[0] === "X25519MLKEM768");
|
|
1073
|
+
|
|
1074
|
+
// preferredGroups alias surface.
|
|
1075
|
+
nt.preferredGroups.set(["X25519"]);
|
|
1076
|
+
check("preferredGroups.get reflects set", nt.preferredGroups.get()[0] === "X25519");
|
|
1077
|
+
nt.preferredGroups.reset();
|
|
1078
|
+
check("preferredGroups.reset restores default", nt.preferredGroups.get().length === 4);
|
|
1079
|
+
check("preferredGroups.DEFAULT is the frozen default", nt.preferredGroups.DEFAULT[0] === "X25519MLKEM768");
|
|
1080
|
+
check("pqc.DEFAULT_KEY_SHARES exposed", nt.pqc.DEFAULT_KEY_SHARES[0] === "X25519MLKEM768");
|
|
1081
|
+
nt._resetForTest();
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// =====================================================================
|
|
1085
|
+
// OCSP — parse / build / evaluate
|
|
1086
|
+
// =====================================================================
|
|
1087
|
+
|
|
1088
|
+
function testOcspParseShapeErrors() {
|
|
1089
|
+
var cases = [
|
|
1090
|
+
{ der: Buffer.from([0x30, 0x00]), label: "empty SEQUENCE (no responseStatus)" },
|
|
1091
|
+
// successful (0) but no responseBytes.
|
|
1092
|
+
{ der: asn1.writeSequence([asn1.writeNode(0x0a, Buffer.from([0]))]), label: "successful missing responseBytes" },
|
|
1093
|
+
];
|
|
1094
|
+
for (var i = 0; i < cases.length; i += 1) {
|
|
1095
|
+
var threw = null;
|
|
1096
|
+
try { nt.ocsp.parseResponse(cases[i].der); } catch (e) { threw = e; }
|
|
1097
|
+
check("parseResponse rejects " + cases[i].label + " with ocsp-bad-shape",
|
|
1098
|
+
threw && /ocsp-bad-shape/.test(threw.code || ""));
|
|
1099
|
+
}
|
|
1100
|
+
// Unknown responseStatus int -> "unknown:<n>".
|
|
1101
|
+
var rv = nt.ocsp.parseResponse(Buffer.from([0x30, 0x03, 0x0a, 0x01, 0x09]));
|
|
1102
|
+
check("parseResponse maps unknown status int to 'unknown:9'", rv.status === "unknown:9");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function testOcspParseUnsupportedResponseType() {
|
|
1106
|
+
// successful + responseBytes whose responseType OID is not id-pkix-ocsp-basic.
|
|
1107
|
+
var rbInner = asn1.writeSequence([asn1.writeOid("1.2.3.4"), asn1.writeOctetString(Buffer.from([0x30, 0x00]))]);
|
|
1108
|
+
var der = asn1.writeSequence([asn1.writeNode(0x0a, Buffer.from([0])), asn1.writeContextExplicit(0, rbInner)]);
|
|
1109
|
+
var threw = null;
|
|
1110
|
+
try { nt.ocsp.parseResponse(der); } catch (e) { threw = e; }
|
|
1111
|
+
check("parseResponse rejects non-basic responseType",
|
|
1112
|
+
threw && threw.code === "tls/ocsp-unsupported-response-type");
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function testOcspParseBadTime() {
|
|
1116
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "T CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1117
|
+
var fx = _buildOcsp({ issuerDer: issuer, status: "good", thisUpdate: "2025Z" }); // too short for either time form
|
|
1118
|
+
var threw = null;
|
|
1119
|
+
try { nt.ocsp.parseResponse(fx.der); } catch (e) { threw = e; }
|
|
1120
|
+
check("parseResponse rejects malformed time with ocsp-bad-time",
|
|
1121
|
+
threw && threw.code === "tls/ocsp-bad-time");
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function testOcspParseUtcTimeYear() {
|
|
1125
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "U CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1126
|
+
// UTCTime (0x17) YY>=50 -> 19xx.
|
|
1127
|
+
var fx = _buildOcsp({ issuerDer: issuer, status: "good", timeTag: 0x17, thisUpdate: "750101000000Z", nextUpdate: null });
|
|
1128
|
+
var parsed = nt.ocsp.parseResponse(fx.der);
|
|
1129
|
+
var ms = parsed.basic.responses[0].thisUpdate;
|
|
1130
|
+
check("parseResponse UTCTime YY>=50 maps to 19xx",
|
|
1131
|
+
ms === Date.UTC(1975, 0, 1, 0, 0, 0));
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function testOcspEvaluateBranches() {
|
|
1135
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "Eval CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1136
|
+
|
|
1137
|
+
var good = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL });
|
|
1138
|
+
var okRv = nt.ocsp.evaluate(good.der, { issuerPem: good.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1139
|
+
check("evaluate good response ok=true", okRv.ok === true && okRv.certStatus === "good");
|
|
1140
|
+
|
|
1141
|
+
// Missing serialHex -> fail closed.
|
|
1142
|
+
var noSer = nt.ocsp.evaluate(good.der, { issuerPem: good.issuerPem, now: _NOW });
|
|
1143
|
+
check("evaluate without serialHex fails closed", noSer.ok === false && noSer.signatureValid === true);
|
|
1144
|
+
|
|
1145
|
+
// Serial not present.
|
|
1146
|
+
var notFound = nt.ocsp.evaluate(good.der, { issuerPem: good.issuerPem, serialHex: "deadbeef", now: _NOW });
|
|
1147
|
+
check("evaluate serial-not-found fails closed", notFound.ok === false &&
|
|
1148
|
+
/no entry for the requested cert serial/.test((notFound.errors || []).join(" ")));
|
|
1149
|
+
|
|
1150
|
+
// Revoked.
|
|
1151
|
+
var rev = _buildOcsp({ issuerDer: issuer, status: "revoked", serial: _SERIAL });
|
|
1152
|
+
var revRv = nt.ocsp.evaluate(rev.der, { issuerPem: rev.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1153
|
+
check("evaluate revoked -> ok=false certStatus=revoked", revRv.ok === false && revRv.certStatus === "revoked");
|
|
1154
|
+
|
|
1155
|
+
// Unknown certStatus.
|
|
1156
|
+
var unk = _buildOcsp({ issuerDer: issuer, status: "unknown", serial: _SERIAL });
|
|
1157
|
+
var unkRv = nt.ocsp.evaluate(unk.der, { issuerPem: unk.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1158
|
+
check("evaluate unknown certStatus -> ok=false", unkRv.ok === false && unkRv.certStatus === "unknown");
|
|
1159
|
+
|
|
1160
|
+
// Bad signature -> signatureValid false.
|
|
1161
|
+
var bad = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL, badSig: true });
|
|
1162
|
+
var badRv = nt.ocsp.evaluate(bad.der, { issuerPem: bad.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1163
|
+
check("evaluate bad signature -> ok=false signatureValid=false", badRv.ok === false && badRv.signatureValid === false);
|
|
1164
|
+
|
|
1165
|
+
// Unsupported signature algorithm OID.
|
|
1166
|
+
var badAlg = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL, sigAlgOid: "1.2.3.999" });
|
|
1167
|
+
var badAlgRv = nt.ocsp.evaluate(badAlg.der, { issuerPem: badAlg.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1168
|
+
check("evaluate unsupported sig-alg -> ok=false signatureValid=false", badAlgRv.ok === false && badAlgRv.signatureValid === false);
|
|
1169
|
+
|
|
1170
|
+
// thisUpdate in the future.
|
|
1171
|
+
var fut = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL, thisUpdate: "20990101000000Z" });
|
|
1172
|
+
var futRv = nt.ocsp.evaluate(fut.der, { issuerPem: fut.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1173
|
+
check("evaluate future thisUpdate -> ok=false", futRv.ok === false && /future/.test((futRv.errors || []).join(" ")));
|
|
1174
|
+
|
|
1175
|
+
// Past nextUpdate.
|
|
1176
|
+
var past = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL, thisUpdate: "20200101000000Z", nextUpdate: "20200201000000Z" });
|
|
1177
|
+
var pastRv = nt.ocsp.evaluate(past.der, { issuerPem: past.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1178
|
+
check("evaluate past nextUpdate -> ok=false", pastRv.ok === false && /past nextUpdate/.test((pastRv.errors || []).join(" ")));
|
|
1179
|
+
|
|
1180
|
+
// Non-finite clockSkew falls back to default (does not disable the window).
|
|
1181
|
+
var futSkew = nt.ocsp.evaluate(fut.der, { issuerPem: fut.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, clockSkewMs: Infinity });
|
|
1182
|
+
check("evaluate non-finite clockSkew does not disable future-check", futSkew.ok === false);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function testOcspEvaluateNonce() {
|
|
1186
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "Nonce CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1187
|
+
var nonce = Buffer.from("0123456789abcdef");
|
|
1188
|
+
var fx = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL, nonce: nonce });
|
|
1189
|
+
|
|
1190
|
+
var match = nt.ocsp.evaluate(fx.der, { issuerPem: fx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, expectedNonce: nonce });
|
|
1191
|
+
check("evaluate nonce match -> ok=true nonce=matched", match.ok === true && match.nonce === "matched");
|
|
1192
|
+
|
|
1193
|
+
var mismatch = nt.ocsp.evaluate(fx.der, { issuerPem: fx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, expectedNonce: Buffer.from("ffffffffffffffff") });
|
|
1194
|
+
check("evaluate nonce mismatch -> ok=false", mismatch.ok === false && /nonce mismatch/.test((mismatch.errors || []).join(" ")));
|
|
1195
|
+
|
|
1196
|
+
// expectedNonce not a Buffer -> shape error.
|
|
1197
|
+
var badShape = nt.ocsp.evaluate(fx.der, { issuerPem: fx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, expectedNonce: "hex" });
|
|
1198
|
+
check("evaluate expectedNonce non-Buffer -> ok=false", badShape.ok === false && /must be a Buffer/.test((badShape.errors || []).join(" ")));
|
|
1199
|
+
|
|
1200
|
+
// Present but not checked (no expectedNonce).
|
|
1201
|
+
var present = nt.ocsp.evaluate(fx.der, { issuerPem: fx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW });
|
|
1202
|
+
check("evaluate nonce present-not-checked", present.nonce === "present-not-checked");
|
|
1203
|
+
|
|
1204
|
+
// expectedNonce supplied but response carries none.
|
|
1205
|
+
var noNonceFx = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL });
|
|
1206
|
+
var missing = nt.ocsp.evaluate(noNonceFx.der, { issuerPem: noNonceFx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, expectedNonce: nonce });
|
|
1207
|
+
check("evaluate expected nonce but response has none -> ok=false", missing.ok === false && /missing nonce/.test((missing.errors || []).join(" ")));
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function testOcspEvaluateIssuerBindShapeErrors() {
|
|
1211
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "Bind CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1212
|
+
var fx = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL });
|
|
1213
|
+
// issuerCertDer not a Buffer -> shape error.
|
|
1214
|
+
var rv = nt.ocsp.evaluate(fx.der, { issuerPem: fx.issuerPem, serialHex: _SERIAL.toString("hex"), now: _NOW, issuerCertDer: "not-a-buffer" });
|
|
1215
|
+
check("evaluate issuerCertDer non-Buffer -> ok=false", rv.ok === false && /must be a Buffer/.test((rv.errors || []).join(" ")));
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function testOcspBuildRequest() {
|
|
1219
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "Req CA", keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1220
|
+
var leaf = _synthCert({ serial: _SERIAL, cn: "Leaf", keyBytes: Buffer.from("leaf-key-bytes-aaaaaaaaaaaaaaaa") });
|
|
1221
|
+
|
|
1222
|
+
var withNonce = nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: issuer });
|
|
1223
|
+
check("buildRequest default nonce is 16 bytes", Buffer.isBuffer(withNonce.nonce) && withNonce.nonce.length === 16);
|
|
1224
|
+
check("buildRequest returns a DER buffer", Buffer.isBuffer(withNonce.requestDer) && withNonce.requestDer.length > 0);
|
|
1225
|
+
|
|
1226
|
+
var noNonce = nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: issuer, nonce: false });
|
|
1227
|
+
check("buildRequest nonce:false -> nonce null", noNonce.nonce === null);
|
|
1228
|
+
|
|
1229
|
+
var custom = nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: issuer, nonceLen: 32 });
|
|
1230
|
+
check("buildRequest nonceLen:32 honored", custom.nonce.length === 32);
|
|
1231
|
+
|
|
1232
|
+
var e1 = null;
|
|
1233
|
+
try { nt.ocsp.buildRequest({ leafCertDer: "x", issuerCertDer: issuer }); } catch (e) { e1 = e; }
|
|
1234
|
+
check("buildRequest bad leafCertDer throws ocsp-bad-input", e1 && e1.code === "tls/ocsp-bad-input");
|
|
1235
|
+
var e2 = null;
|
|
1236
|
+
try { nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: 42 }); } catch (e) { e2 = e; }
|
|
1237
|
+
check("buildRequest bad issuerCertDer throws ocsp-bad-input", e2 && e2.code === "tls/ocsp-bad-input");
|
|
1238
|
+
var e3 = null;
|
|
1239
|
+
try { nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: issuer, nonceLen: 99 }); } catch (e) { e3 = e; }
|
|
1240
|
+
check("buildRequest out-of-range nonceLen throws ocsp-bad-nonce-len", e3 && e3.code === "tls/ocsp-bad-nonce-len");
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
async function testOcspFetchGuards() {
|
|
1244
|
+
var e1 = null;
|
|
1245
|
+
try { await nt.ocsp.fetch({ leafPem: 123 }); } catch (e) { e1 = e; }
|
|
1246
|
+
check("ocsp.fetch bad input throws ocsp-bad-input", e1 && e1.code === "tls/ocsp-bad-input");
|
|
1247
|
+
|
|
1248
|
+
var e2 = null;
|
|
1249
|
+
try { await nt.ocsp.fetch({ leafPem: "not a cert", issuerPem: "also not a cert" }); } catch (e) { e2 = e; }
|
|
1250
|
+
check("ocsp.fetch unparseable PEM throws ocsp-bad-cert", e2 && e2.code === "tls/ocsp-bad-cert");
|
|
1251
|
+
|
|
1252
|
+
// Valid certs with no AIA responder URL -> ocsp-no-responder.
|
|
1253
|
+
var leafPem = _toPem(_synthCert({ cn: "Fetch Leaf", serial: _SERIAL }));
|
|
1254
|
+
var issuerPem = _toPem(_synthCert({ cn: "Fetch CA", serial: Buffer.from([0x01]) }));
|
|
1255
|
+
var e3 = null;
|
|
1256
|
+
try { await nt.ocsp.fetch({ leafPem: leafPem, issuerPem: issuerPem }); } catch (e) { e3 = e; }
|
|
1257
|
+
check("ocsp.fetch with no responder URL throws ocsp-no-responder", e3 && e3.code === "tls/ocsp-no-responder");
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function testOcspRequireGoodEmpty() {
|
|
1261
|
+
var e1 = null;
|
|
1262
|
+
try { await nt.ocsp.requireGood({}); } catch (e) { e1 = e; }
|
|
1263
|
+
check("requireGood without issuerPem throws ocsp-missing-issuer", e1 && e1.code === "tls/ocsp-missing-issuer");
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function testOcspMustStaple() {
|
|
1267
|
+
var e1 = null;
|
|
1268
|
+
try { nt.ocsp.inspectMustStaple("not a buffer"); } catch (e) { e1 = e; }
|
|
1269
|
+
check("inspectMustStaple bad input throws ocsp-bad-input", e1 && e1.code === "tls/ocsp-bad-input");
|
|
1270
|
+
|
|
1271
|
+
var msCert = _synthCert({ cn: "MS CA", exts: [_mustStapleExt()] });
|
|
1272
|
+
var ms = nt.ocsp.inspectMustStaple(msCert);
|
|
1273
|
+
check("inspectMustStaple detects must-staple", ms.mustStaple === true && ms.features.indexOf(5) !== -1);
|
|
1274
|
+
|
|
1275
|
+
var plainCert = _synthCert({ cn: "Plain CA" });
|
|
1276
|
+
check("inspectMustStaple on plain cert -> mustStaple false", nt.ocsp.inspectMustStaple(plainCert).mustStaple === false);
|
|
1277
|
+
|
|
1278
|
+
// requireMustStaple predicate.
|
|
1279
|
+
var predicate = nt.ocsp.requireMustStaple();
|
|
1280
|
+
check("requireMustStaple missing peer cert.raw -> error",
|
|
1281
|
+
predicate(null, {}) instanceof nt.TlsTrustError);
|
|
1282
|
+
var msViolation = predicate({ raw: msCert }, { ocspBytes: Buffer.alloc(0) });
|
|
1283
|
+
check("must-staple cert w/o staple -> ocsp-must-staple-violated",
|
|
1284
|
+
msViolation && msViolation.code === "tls/ocsp-must-staple-violated");
|
|
1285
|
+
var msOk = predicate({ raw: msCert }, { ocspBytes: Buffer.from([0x30, 0x00]) });
|
|
1286
|
+
check("must-staple cert with a staple -> null (permitted)", msOk === null);
|
|
1287
|
+
var plainOk = predicate({ raw: plainCert }, {});
|
|
1288
|
+
check("non-must-staple cert -> null under default policy", plainOk === null);
|
|
1289
|
+
|
|
1290
|
+
var strict = nt.ocsp.requireMustStaple({ enforceUnconditional: true });
|
|
1291
|
+
var strictViolation = strict({ raw: plainCert }, {});
|
|
1292
|
+
check("enforceUnconditional refuses plain cert w/o staple",
|
|
1293
|
+
strictViolation && strictViolation.code === "tls/ocsp-staple-required");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// =====================================================================
|
|
1297
|
+
// Certificate Transparency
|
|
1298
|
+
// =====================================================================
|
|
1299
|
+
|
|
1300
|
+
function testCtInspectAndParse() {
|
|
1301
|
+
var e1 = null;
|
|
1302
|
+
try { nt.ct.inspect("not a buffer"); } catch (e) { e1 = e; }
|
|
1303
|
+
check("ct.inspect bad input throws ct-bad-input", e1 && e1.code === "tls/ct-bad-input");
|
|
1304
|
+
|
|
1305
|
+
var plain = _synthCert({ cn: "No SCT" });
|
|
1306
|
+
check("ct.inspect no-SCT cert -> hasSctExtension false", nt.ct.inspect(plain).hasSctExtension === false);
|
|
1307
|
+
|
|
1308
|
+
var sct = _buildSctBytes({});
|
|
1309
|
+
var withSct = _synthCert({ cn: "SCT CA", exts: [_sctExt(_sctListRaw([sct]))] });
|
|
1310
|
+
var inspected = nt.ct.inspect(withSct);
|
|
1311
|
+
check("ct.inspect SCT cert -> hasSctExtension true", inspected.hasSctExtension === true);
|
|
1312
|
+
|
|
1313
|
+
var e2 = null;
|
|
1314
|
+
try { nt.ct.parseScts("nope"); } catch (e) { e2 = e; }
|
|
1315
|
+
check("ct.parseScts bad input throws ct-bad-input", e2 && e2.code === "tls/ct-bad-input");
|
|
1316
|
+
check("ct.parseScts no-SCT cert -> []", nt.ct.parseScts(plain).length === 0);
|
|
1317
|
+
var parsed = nt.ct.parseScts(withSct);
|
|
1318
|
+
check("ct.parseScts returns one SCT with hashAlgo/sigAlgo",
|
|
1319
|
+
parsed.length === 1 && parsed[0].hashAlgo === 4 && parsed[0].sigAlgo === 3);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function testCtVerifyScts() {
|
|
1323
|
+
var e1 = null;
|
|
1324
|
+
try { nt.ct.verifyScts("nope"); } catch (e) { e1 = e; }
|
|
1325
|
+
check("verifyScts bad input throws ct-bad-input", e1 && e1.code === "tls/ct-bad-input");
|
|
1326
|
+
|
|
1327
|
+
var plain = _synthCert({ cn: "No SCT" });
|
|
1328
|
+
check("verifyScts no-SCT cert -> reason no-sct-extension", nt.ct.verifyScts(plain).reason === "no-sct-extension");
|
|
1329
|
+
|
|
1330
|
+
// Cert with an SCT but no log keys -> insufficient-verified, per-sct log-key-missing.
|
|
1331
|
+
var sct = _buildSctBytes({});
|
|
1332
|
+
var withSct = _synthCert({ cn: "SCT CA", exts: [_sctExt(_sctListRaw([sct]))] });
|
|
1333
|
+
var rv = nt.ct.verifyScts(withSct, {});
|
|
1334
|
+
check("verifyScts with no log keys -> ok false insufficient-verified",
|
|
1335
|
+
rv.ok === false && rv.reason === "insufficient-verified" && rv.scts[0].reason === "log-key-missing");
|
|
1336
|
+
|
|
1337
|
+
// Parse-error: lie about the outer SCT-list length.
|
|
1338
|
+
var badList = _sctListRaw([sct], { lieOuterLen: 9999 });
|
|
1339
|
+
var badCert = _synthCert({ cn: "Bad SCT", exts: [_sctExt(badList)] });
|
|
1340
|
+
var badRv = nt.ct.verifyScts(badCert, {});
|
|
1341
|
+
check("verifyScts with malformed SCT list -> reason parse-error", badRv.reason === "parse-error");
|
|
1342
|
+
|
|
1343
|
+
// requireScts predicate.
|
|
1344
|
+
var predicate = nt.ct.requireScts({});
|
|
1345
|
+
check("requireScts missing peer cert.raw -> error", predicate(null) instanceof nt.TlsTrustError);
|
|
1346
|
+
var noExt = predicate({ raw: plain });
|
|
1347
|
+
check("requireScts no-SCT cert -> tls/ct-no-sct-extension", noExt && noExt.code === "tls/ct-no-sct-extension");
|
|
1348
|
+
var insuff = predicate({ raw: withSct });
|
|
1349
|
+
check("requireScts insufficient -> tls/ct-insufficient-verified", insuff && insuff.code === "tls/ct-insufficient-verified");
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function testCtVerifyInclusion() {
|
|
1353
|
+
var signedEntry = Buffer.from("fake-signed-entry-der-bytes");
|
|
1354
|
+
var ts = 1700000000000;
|
|
1355
|
+
var leafHash = _ctLeafHash(signedEntry, ts);
|
|
1356
|
+
|
|
1357
|
+
// Trivial single-leaf tree: computedRoot === leafHash.
|
|
1358
|
+
var trivial = nt.ct.verifyInclusion({
|
|
1359
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1360
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [],
|
|
1361
|
+
sthFromLog: { treeSize: 1, rootHash: leafHash },
|
|
1362
|
+
});
|
|
1363
|
+
check("verifyInclusion trivial tree valid", trivial.valid === true);
|
|
1364
|
+
|
|
1365
|
+
// 2-leaf tree, leafIndex 0.
|
|
1366
|
+
var sib = Buffer.alloc(32, 0x11);
|
|
1367
|
+
var root2 = _ctInner(leafHash, sib);
|
|
1368
|
+
var two = nt.ct.verifyInclusion({
|
|
1369
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1370
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [sib],
|
|
1371
|
+
sthFromLog: { treeSize: 2, rootHash: root2.toString("hex") },
|
|
1372
|
+
});
|
|
1373
|
+
check("verifyInclusion 2-leaf leafIndex0 valid (hex rootHash)", two.valid === true);
|
|
1374
|
+
|
|
1375
|
+
// Root mismatch.
|
|
1376
|
+
var mismatch = nt.ct.verifyInclusion({
|
|
1377
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1378
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [],
|
|
1379
|
+
sthFromLog: { treeSize: 1, rootHash: Buffer.alloc(32, 0x00) },
|
|
1380
|
+
});
|
|
1381
|
+
check("verifyInclusion root mismatch -> valid false root-mismatch", mismatch.valid === false && mismatch.reason === "root-mismatch");
|
|
1382
|
+
|
|
1383
|
+
// Shape errors.
|
|
1384
|
+
var errCases = [
|
|
1385
|
+
[undefined, "missing-opts"],
|
|
1386
|
+
[{}, "missing-sct"],
|
|
1387
|
+
[{ sct: {} }, "missing-leaf-certificate"],
|
|
1388
|
+
[{ sct: {}, leafCertificate: Buffer.from("x") }, "missing-sth"],
|
|
1389
|
+
[{ sct: {}, leafCertificate: Buffer.from("x"), sthFromLog: {}, leafIndex: -1 }, "bad-leaf-index"],
|
|
1390
|
+
[{ sct: {}, leafCertificate: Buffer.from("x"), sthFromLog: {}, leafIndex: 0, auditPath: "no" }, "bad-audit-path"],
|
|
1391
|
+
];
|
|
1392
|
+
for (var i = 0; i < errCases.length; i += 1) {
|
|
1393
|
+
var r = nt.ct.verifyInclusion(errCases[i][0]);
|
|
1394
|
+
check("verifyInclusion reason=" + errCases[i][1], r.valid === false && r.reason === errCases[i][1]);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Bad SCT timestamp.
|
|
1398
|
+
var badTs = nt.ct.verifyInclusion({
|
|
1399
|
+
sct: { logIdHex: "aa", timestamp: "nope", signedEntryDer: signedEntry },
|
|
1400
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [],
|
|
1401
|
+
sthFromLog: { treeSize: 1, rootHash: leafHash },
|
|
1402
|
+
});
|
|
1403
|
+
check("verifyInclusion bad timestamp -> bad-sct-timestamp", badTs.reason === "bad-sct-timestamp");
|
|
1404
|
+
|
|
1405
|
+
// Bad STH root length.
|
|
1406
|
+
var badRoot = nt.ct.verifyInclusion({
|
|
1407
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1408
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [],
|
|
1409
|
+
sthFromLog: { treeSize: 1, rootHash: Buffer.alloc(4) },
|
|
1410
|
+
});
|
|
1411
|
+
check("verifyInclusion bad-sth-root (short buffer)", badRoot.reason === "bad-sth-root");
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function testCtVerifyConsistency() {
|
|
1415
|
+
var X = Buffer.alloc(32, 0x22);
|
|
1416
|
+
var same = nt.ct.verifyConsistency({ firstSize: 1, secondSize: 1, proof: [], firstRoot: X, secondRoot: X });
|
|
1417
|
+
check("verifyConsistency m=n=1 valid", same.valid === true);
|
|
1418
|
+
|
|
1419
|
+
var sib = Buffer.alloc(32, 0x33);
|
|
1420
|
+
var second = _ctInner(X, sib);
|
|
1421
|
+
var grow = nt.ct.verifyConsistency({ firstSize: 1, secondSize: 2, proof: [sib], firstRoot: X, secondRoot: second });
|
|
1422
|
+
check("verifyConsistency m=1 n=2 valid", grow.valid === true);
|
|
1423
|
+
|
|
1424
|
+
var mismatch = nt.ct.verifyConsistency({ firstSize: 1, secondSize: 1, proof: [], firstRoot: X, secondRoot: Buffer.alloc(32, 0x99) });
|
|
1425
|
+
check("verifyConsistency root mismatch -> valid false", mismatch.valid === false && mismatch.reason === "root-mismatch");
|
|
1426
|
+
|
|
1427
|
+
// Empty proof but first tree not a complete subtree -> walk-failed.
|
|
1428
|
+
var incomplete = nt.ct.verifyConsistency({ firstSize: 3, secondSize: 4, proof: [], firstRoot: X, secondRoot: X });
|
|
1429
|
+
check("verifyConsistency incomplete-subtree empty proof -> walk-failed", incomplete.valid === false && incomplete.reason === "consistency-walk-failed");
|
|
1430
|
+
|
|
1431
|
+
// Shape errors.
|
|
1432
|
+
check("verifyConsistency missing-opts", nt.ct.verifyConsistency(undefined).reason === "missing-opts");
|
|
1433
|
+
check("verifyConsistency bad-first-root", nt.ct.verifyConsistency({ firstRoot: Buffer.alloc(4), secondRoot: X }).reason === "bad-first-root");
|
|
1434
|
+
check("verifyConsistency bad-second-root", nt.ct.verifyConsistency({ firstRoot: X, secondRoot: Buffer.alloc(4) }).reason === "bad-second-root");
|
|
1435
|
+
check("verifyConsistency bad sizes -> walk-failed",
|
|
1436
|
+
nt.ct.verifyConsistency({ firstSize: 0, secondSize: 1, proof: [], firstRoot: X, secondRoot: X }).reason === "consistency-walk-failed");
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// =====================================================================
|
|
1440
|
+
// parseEchConfigList extra framing branches
|
|
1441
|
+
// =====================================================================
|
|
1442
|
+
|
|
1443
|
+
function testEchExtraFraming() {
|
|
1444
|
+
// Single-byte buffer -> too short for outer prefix.
|
|
1445
|
+
var e1 = null;
|
|
1446
|
+
try { nt.parseEchConfigList(Buffer.from([0x00])); } catch (e) { e1 = e; }
|
|
1447
|
+
check("parseEchConfigList 1-byte buffer -> ech-config-malformed", e1 && e1.code === "tls/ech-config-malformed");
|
|
1448
|
+
|
|
1449
|
+
// Truncated ECHConfig header.
|
|
1450
|
+
var e2 = null;
|
|
1451
|
+
try { nt.parseEchConfigList(Buffer.from([0x00, 0x02, 0xfe, 0x0d])); } catch (e) { e2 = e; }
|
|
1452
|
+
check("parseEchConfigList truncated config header -> ech-config-malformed", e2 && e2.code === "tls/ech-config-malformed");
|
|
1453
|
+
|
|
1454
|
+
// Declared config length overflows list.
|
|
1455
|
+
var e3 = null;
|
|
1456
|
+
try { nt.parseEchConfigList(Buffer.from([0x00, 0x04, 0xfe, 0x0d, 0x00, 0xff])); } catch (e) { e3 = e; }
|
|
1457
|
+
check("parseEchConfigList config length overflow -> ech-config-malformed", e3 && e3.code === "tls/ech-config-malformed");
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// =====================================================================
|
|
1461
|
+
// wrapSNICallback
|
|
1462
|
+
// =====================================================================
|
|
1463
|
+
|
|
1464
|
+
function testWrapSniCallback() {
|
|
1465
|
+
check("wrapSNICallback(non-function) returns arg unchanged", nt.wrapSNICallback(42) === 42);
|
|
1466
|
+
|
|
1467
|
+
// Operator callback that throws synchronously -> wrapper surfaces via cb(err, null).
|
|
1468
|
+
var wrapped = nt.wrapSNICallback(function () { throw new Error("boom in SNI"); });
|
|
1469
|
+
var cbErr = "unset";
|
|
1470
|
+
var cbCtx = "unset";
|
|
1471
|
+
wrapped("evil.example.com", function (err, ctx) { cbErr = err; cbCtx = ctx; });
|
|
1472
|
+
check("throwing SNICallback surfaces the error to cb", cbErr instanceof Error && /boom in SNI/.test(cbErr.message));
|
|
1473
|
+
check("throwing SNICallback passes null ctx", cbCtx === null);
|
|
1474
|
+
|
|
1475
|
+
// Normal callback passes through untouched.
|
|
1476
|
+
var okWrapped = nt.wrapSNICallback(function (servername, cb) { cb(null, { servername: servername }); });
|
|
1477
|
+
var okCtx = null;
|
|
1478
|
+
okWrapped("good.example.com", function (_err, ctx) { okCtx = ctx; });
|
|
1479
|
+
check("non-throwing SNICallback passes through", okCtx && okCtx.servername === "good.example.com");
|
|
1480
|
+
|
|
1481
|
+
// Callback that throws AFTER already invoking cb (double-invoke) is swallowed.
|
|
1482
|
+
var didNotThrow = true;
|
|
1483
|
+
try {
|
|
1484
|
+
var dbl = nt.wrapSNICallback(function (servername, cb) { cb(null, null); throw new Error("late throw"); });
|
|
1485
|
+
dbl("x", function () {});
|
|
1486
|
+
} catch (_e) { didNotThrow = false; }
|
|
1487
|
+
check("SNICallback throwing after cb() does not escape the wrapper", didNotThrow === true);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// =====================================================================
|
|
1491
|
+
// buildOptions — TLS request-options builder (PQC groups + TLSv1.3 floor)
|
|
1492
|
+
// =====================================================================
|
|
1493
|
+
|
|
1494
|
+
function testBuildOptionsBranches() {
|
|
1495
|
+
nt._resetForTest();
|
|
1496
|
+
|
|
1497
|
+
// Defaults: TLSv1.3 floor + framework PQC group list; groups mirrors ecdhCurve.
|
|
1498
|
+
var def = nt.buildOptions();
|
|
1499
|
+
check("buildOptions default minVersion is TLSv1.3", def.minVersion === "TLSv1.3");
|
|
1500
|
+
check("buildOptions default ecdhCurve leads with the hybrid group",
|
|
1501
|
+
def.ecdhCurve.indexOf("X25519MLKEM768") === 0 && def.groups === def.ecdhCurve);
|
|
1502
|
+
|
|
1503
|
+
// opts must be a plain object — an array refuses at the config-time tier.
|
|
1504
|
+
var eArr = null;
|
|
1505
|
+
try { nt.buildOptions([1, 2]); } catch (e) { eArr = e; }
|
|
1506
|
+
check("buildOptions on an array refuses bad-tls-options",
|
|
1507
|
+
eArr && eArr.code === "network-tls/bad-tls-options");
|
|
1508
|
+
|
|
1509
|
+
// minVersion is locked to TLSv1.3.
|
|
1510
|
+
var eMin = null;
|
|
1511
|
+
try { nt.buildOptions({ minVersion: "TLSv1.2" }); } catch (e) { eMin = e; }
|
|
1512
|
+
check("buildOptions minVersion!=TLSv1.3 refuses", eMin && eMin.code === "network-tls/bad-tls-options");
|
|
1513
|
+
|
|
1514
|
+
// Narrowing the group list (array + string ecdhCurve + string groups) is accepted.
|
|
1515
|
+
check("buildOptions narrows groups[] to a subset",
|
|
1516
|
+
nt.buildOptions({ groups: ["X25519MLKEM768"] }).groups === "X25519MLKEM768");
|
|
1517
|
+
check("buildOptions narrows ecdhCurve string to a subset",
|
|
1518
|
+
nt.buildOptions({ ecdhCurve: "X25519MLKEM768:X25519" }).ecdhCurve === "X25519MLKEM768:X25519");
|
|
1519
|
+
check("buildOptions accepts a groups string",
|
|
1520
|
+
nt.buildOptions({ groups: "X25519" }).groups === "X25519");
|
|
1521
|
+
|
|
1522
|
+
// Widening to a group outside the framework preferred list refuses.
|
|
1523
|
+
var eWide = null;
|
|
1524
|
+
try { nt.buildOptions({ groups: ["kyber-nonsense"] }); } catch (e) { eWide = e; }
|
|
1525
|
+
check("buildOptions widening to a non-preferred group refuses",
|
|
1526
|
+
eWide && eWide.code === "network-tls/bad-tls-options");
|
|
1527
|
+
|
|
1528
|
+
// Empty group list refuses.
|
|
1529
|
+
var eEmpty = null;
|
|
1530
|
+
try { nt.buildOptions({ groups: [] }); } catch (e) { eEmpty = e; }
|
|
1531
|
+
check("buildOptions empty groups[] refuses", eEmpty && eEmpty.code === "network-tls/bad-tls-options");
|
|
1532
|
+
|
|
1533
|
+
// An empty-string entry inside the group list refuses.
|
|
1534
|
+
var eBadEntry = null;
|
|
1535
|
+
try { nt.buildOptions({ groups: [""] }); } catch (e) { eBadEntry = e; }
|
|
1536
|
+
check("buildOptions empty-string group entry refuses", eBadEntry && eBadEntry.code === "network-tls/bad-tls-options");
|
|
1537
|
+
|
|
1538
|
+
// groups that is neither string nor array (but defined) refuses.
|
|
1539
|
+
var eShape = null;
|
|
1540
|
+
try { nt.buildOptions({ groups: 123 }); } catch (e) { eShape = e; }
|
|
1541
|
+
check("buildOptions non-string non-array groups refuses", eShape && eShape.code === "network-tls/bad-tls-options");
|
|
1542
|
+
|
|
1543
|
+
// ca normalization: string passes through; Buffer → utf8; array joins with \n.
|
|
1544
|
+
var pem1 = "-----BEGIN CERTIFICATE-----\nAA\n-----END CERTIFICATE-----";
|
|
1545
|
+
var pem2 = "-----BEGIN CERTIFICATE-----\nBB\n-----END CERTIFICATE-----";
|
|
1546
|
+
check("buildOptions ca string passes through", nt.buildOptions({ ca: pem1 }).ca === pem1);
|
|
1547
|
+
check("buildOptions ca Buffer normalizes to utf8",
|
|
1548
|
+
nt.buildOptions({ ca: Buffer.from(pem1, "utf8") }).ca === pem1);
|
|
1549
|
+
check("buildOptions ca array joins with newline",
|
|
1550
|
+
nt.buildOptions({ ca: [pem1, Buffer.from(pem2, "utf8")] }).ca === pem1 + "\n" + pem2);
|
|
1551
|
+
check("buildOptions ca null → undefined", nt.buildOptions({ ca: null }).ca === undefined);
|
|
1552
|
+
|
|
1553
|
+
// ca of a wrong scalar type, and a wrong-typed array entry, both refuse.
|
|
1554
|
+
var eCa1 = null;
|
|
1555
|
+
try { nt.buildOptions({ ca: 42 }); } catch (e) { eCa1 = e; }
|
|
1556
|
+
check("buildOptions ca number refuses", eCa1 && eCa1.code === "network-tls/bad-tls-options");
|
|
1557
|
+
var eCa2 = null;
|
|
1558
|
+
try { nt.buildOptions({ ca: [pem1, 7] }); } catch (e) { eCa2 = e; }
|
|
1559
|
+
check("buildOptions ca array wrong-typed entry refuses", eCa2 && eCa2.code === "network-tls/bad-tls-options");
|
|
1560
|
+
|
|
1561
|
+
// cert / key pass-through + shape guards.
|
|
1562
|
+
check("buildOptions cert string passes through", nt.buildOptions({ cert: pem1 }).cert === pem1);
|
|
1563
|
+
check("buildOptions key Buffer passes through",
|
|
1564
|
+
Buffer.isBuffer(nt.buildOptions({ key: Buffer.from(pem2) }).key));
|
|
1565
|
+
var eCert = null;
|
|
1566
|
+
try { nt.buildOptions({ cert: 5 }); } catch (e) { eCert = e; }
|
|
1567
|
+
check("buildOptions non-string non-Buffer cert refuses", eCert && eCert.code === "network-tls/bad-tls-options");
|
|
1568
|
+
var eKey = null;
|
|
1569
|
+
try { nt.buildOptions({ key: {} }); } catch (e) { eKey = e; }
|
|
1570
|
+
check("buildOptions bad-shape key refuses", eKey && eKey.code === "network-tls/bad-tls-options");
|
|
1571
|
+
|
|
1572
|
+
// sni maps to servername; empty sni refuses.
|
|
1573
|
+
check("buildOptions sni maps to servername",
|
|
1574
|
+
nt.buildOptions({ sni: "internal.example.com" }).servername === "internal.example.com");
|
|
1575
|
+
var eSni = null;
|
|
1576
|
+
try { nt.buildOptions({ sni: "" }); } catch (e) { eSni = e; }
|
|
1577
|
+
check("buildOptions empty sni refuses", eSni && eSni.code === "network-tls/bad-tls-options");
|
|
1578
|
+
|
|
1579
|
+
// An operator-narrowed key-share set is honored as the preferred list.
|
|
1580
|
+
nt.pqc.setKeyShares(["X25519"]);
|
|
1581
|
+
check("buildOptions uses the operator-narrowed key-share set",
|
|
1582
|
+
nt.buildOptions().groups === "X25519");
|
|
1583
|
+
nt._resetForTest();
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// =====================================================================
|
|
1587
|
+
// OCSP over a real localhost TLS handshake — connect / requireStapled /
|
|
1588
|
+
// requireGood drive the _connectAndCheckOcsp socket path end-to-end.
|
|
1589
|
+
// =====================================================================
|
|
1590
|
+
|
|
1591
|
+
async function testOcspConnectRealPaths() {
|
|
1592
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "RG CA",
|
|
1593
|
+
keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1594
|
+
|
|
1595
|
+
// 1. connect, server does not staple → resolves with ocspBytes null.
|
|
1596
|
+
var s1 = await _startTlsServer(undefined);
|
|
1597
|
+
try {
|
|
1598
|
+
var r1 = await nt.ocsp.connect({ host: "127.0.0.1", port: s1.port,
|
|
1599
|
+
rejectUnauthorized: false, servername: "localhost" });
|
|
1600
|
+
check("ocsp.connect no-staple resolves with peerCert and ocspBytes null",
|
|
1601
|
+
r1 && r1.ocspBytes === null && r1.peerCert && !!r1.peerCert.serialNumber);
|
|
1602
|
+
} finally { s1.close(); }
|
|
1603
|
+
|
|
1604
|
+
// 2. requireStapled, no staple → refuses (TlsTrustError).
|
|
1605
|
+
var s2 = await _startTlsServer(undefined);
|
|
1606
|
+
var e2 = null;
|
|
1607
|
+
try {
|
|
1608
|
+
await nt.ocsp.requireStapled({ host: "127.0.0.1", port: s2.port,
|
|
1609
|
+
rejectUnauthorized: false, servername: "localhost" });
|
|
1610
|
+
} catch (e) { e2 = e; } finally { s2.close(); }
|
|
1611
|
+
check("ocsp.requireStapled with no staple refuses", e2 instanceof nt.TlsTrustError);
|
|
1612
|
+
|
|
1613
|
+
// 3. requireStapled with a non-empty staple → resolves carrying the bytes.
|
|
1614
|
+
var s3 = await _startTlsServer(Buffer.from([0x30, 0x00]));
|
|
1615
|
+
try {
|
|
1616
|
+
var r3 = await nt.ocsp.requireStapled({ host: "127.0.0.1", port: s3.port,
|
|
1617
|
+
rejectUnauthorized: false, servername: "localhost" });
|
|
1618
|
+
check("ocsp.requireStapled with a staple resolves ocspBytes",
|
|
1619
|
+
Buffer.isBuffer(r3.ocspBytes) && r3.ocspBytes.length === 2);
|
|
1620
|
+
} finally { s3.close(); }
|
|
1621
|
+
|
|
1622
|
+
// 4. connect to a closed port → the socket 'error' handler rejects.
|
|
1623
|
+
var e4 = null;
|
|
1624
|
+
try {
|
|
1625
|
+
await nt.ocsp.connect({ host: "127.0.0.1", port: 1,
|
|
1626
|
+
rejectUnauthorized: false, servername: "localhost" });
|
|
1627
|
+
} catch (e) { e4 = e; }
|
|
1628
|
+
check("ocsp.connect to a closed port rejects", e4 !== null);
|
|
1629
|
+
|
|
1630
|
+
// 5. requireGood — staple binds a DIFFERENT serial → evaluation fails,
|
|
1631
|
+
// requireGood throws tls/ocsp-not-good.
|
|
1632
|
+
var badFx = _buildOcsp({ issuerDer: issuer, status: "good", serial: Buffer.from([0x99, 0x99]) });
|
|
1633
|
+
var s5 = await _startTlsServer(badFx.der);
|
|
1634
|
+
var e5 = null;
|
|
1635
|
+
try {
|
|
1636
|
+
await nt.ocsp.requireGood({ host: "127.0.0.1", port: s5.port,
|
|
1637
|
+
rejectUnauthorized: false, servername: "localhost", issuerPem: badFx.issuerPem });
|
|
1638
|
+
} catch (e) { e5 = e; } finally { s5.close(); }
|
|
1639
|
+
check("ocsp.requireGood with a wrong-serial staple throws ocsp-not-good",
|
|
1640
|
+
e5 && e5.code === "tls/ocsp-not-good");
|
|
1641
|
+
|
|
1642
|
+
// 6. requireGood — staple binds the peer serial (_SERIAL), good + fresh →
|
|
1643
|
+
// resolves with a passing evaluation.
|
|
1644
|
+
var goodFx = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL });
|
|
1645
|
+
var s6 = await _startTlsServer(goodFx.der);
|
|
1646
|
+
try {
|
|
1647
|
+
var r6 = await nt.ocsp.requireGood({ host: "127.0.0.1", port: s6.port,
|
|
1648
|
+
rejectUnauthorized: false, servername: "localhost", issuerPem: goodFx.issuerPem });
|
|
1649
|
+
check("ocsp.requireGood with a good staple resolves ok",
|
|
1650
|
+
r6 && r6.ocspEvaluation && r6.ocspEvaluation.ok === true);
|
|
1651
|
+
} finally { s6.close(); }
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// =====================================================================
|
|
1655
|
+
// OCSP issuer/leaf cert-shape errors (buildRequest DER walk) + evaluate
|
|
1656
|
+
// deep issuer-binding (RFC 6960 §4.1.1) branches.
|
|
1657
|
+
// =====================================================================
|
|
1658
|
+
|
|
1659
|
+
function testOcspCertShapeErrors() {
|
|
1660
|
+
var leaf = _synthCert({ serial: _SERIAL, cn: "Leaf",
|
|
1661
|
+
keyBytes: Buffer.from("leaf-key-bytes-aaaaaaaaaaaaaaaa") });
|
|
1662
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "Shape CA",
|
|
1663
|
+
keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1664
|
+
|
|
1665
|
+
// issuerCertDer is a Buffer but not a SEQUENCE (an OCTET STRING).
|
|
1666
|
+
var e1 = null;
|
|
1667
|
+
try { nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: Buffer.from([0x04, 0x01, 0x00]) }); }
|
|
1668
|
+
catch (e) { e1 = e; }
|
|
1669
|
+
check("buildRequest non-SEQUENCE issuer cert throws ocsp-bad-issuer-cert",
|
|
1670
|
+
e1 && e1.code === "tls/ocsp-bad-issuer-cert");
|
|
1671
|
+
|
|
1672
|
+
// issuer cert is a SEQUENCE whose tbs lacks the SPKI field.
|
|
1673
|
+
var shortTbs = asn1.writeSequence([asn1.writeSequence([asn1.writeInteger(Buffer.from([1]))])]);
|
|
1674
|
+
var e2 = null;
|
|
1675
|
+
try { nt.ocsp.buildRequest({ leafCertDer: leaf, issuerCertDer: shortTbs }); }
|
|
1676
|
+
catch (e) { e2 = e; }
|
|
1677
|
+
check("buildRequest issuer cert lacking SPKI throws ocsp-bad-issuer-cert",
|
|
1678
|
+
e2 && e2.code === "tls/ocsp-bad-issuer-cert");
|
|
1679
|
+
|
|
1680
|
+
// leafCertDer is a Buffer but not a SEQUENCE (issuer walk succeeds first).
|
|
1681
|
+
var e3 = null;
|
|
1682
|
+
try { nt.ocsp.buildRequest({ leafCertDer: Buffer.from([0x04, 0x01, 0x00]), issuerCertDer: issuer }); }
|
|
1683
|
+
catch (e) { e3 = e; }
|
|
1684
|
+
check("buildRequest non-SEQUENCE leaf cert throws ocsp-bad-leaf-cert",
|
|
1685
|
+
e3 && e3.code === "tls/ocsp-bad-leaf-cert");
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function testOcspEvaluateDeepBinding() {
|
|
1689
|
+
var issuer = _synthCert({ serial: Buffer.from([0x01]), cn: "DeepBind CA",
|
|
1690
|
+
keyBytes: Buffer.from("real-ca-key-bytes-aaaaaaaaaaaaaa") });
|
|
1691
|
+
var other = _synthCert({ serial: Buffer.from([0x02]), cn: "Other CA",
|
|
1692
|
+
keyBytes: Buffer.from("other-ca-key-bytes-bbbbbbbbbbbb") });
|
|
1693
|
+
var fx = _buildOcsp({ issuerDer: issuer, status: "good", serial: _SERIAL });
|
|
1694
|
+
var serialHex = _SERIAL.toString("hex");
|
|
1695
|
+
|
|
1696
|
+
// Unparseable issuer public key PEM → verify throws, caught → ok:false.
|
|
1697
|
+
var badKey = nt.ocsp.evaluate(fx.der, {
|
|
1698
|
+
issuerPem: "-----BEGIN PUBLIC KEY-----\nbm90LWEta2V5\n-----END PUBLIC KEY-----\n",
|
|
1699
|
+
serialHex: serialHex, now: _NOW });
|
|
1700
|
+
check("evaluate with an unparseable issuer key -> ok false",
|
|
1701
|
+
badKey.ok === false && /issuer public key parse failed/.test((badKey.errors || []).join(" ")));
|
|
1702
|
+
|
|
1703
|
+
// issuerCertDer MATCHES the CertID issuer → the §4.1.1 name/key bind passes.
|
|
1704
|
+
var bound = nt.ocsp.evaluate(fx.der, {
|
|
1705
|
+
issuerPem: fx.issuerPem, serialHex: serialHex, now: _NOW, issuerCertDer: issuer });
|
|
1706
|
+
check("evaluate with a matching issuerCertDer binds and stays ok",
|
|
1707
|
+
bound.ok === true && bound.certStatus === "good");
|
|
1708
|
+
|
|
1709
|
+
// issuerCertDer is a DIFFERENT issuer → name/key hash mismatch, fail closed.
|
|
1710
|
+
var wrong = nt.ocsp.evaluate(fx.der, {
|
|
1711
|
+
issuerPem: fx.issuerPem, serialHex: serialHex, now: _NOW, issuerCertDer: other });
|
|
1712
|
+
check("evaluate with a wrong issuerCertDer -> ok false (wrong-issuer bind)",
|
|
1713
|
+
wrong.ok === false && /issuerNameHash|issuerKeyHash/.test((wrong.errors || []).join(" ")));
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// =====================================================================
|
|
1717
|
+
// CT SCT verification — per-SCT failure branches with a present log key.
|
|
1718
|
+
// =====================================================================
|
|
1719
|
+
|
|
1720
|
+
function testCtVerifyMoreScts() {
|
|
1721
|
+
var ecPub = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" })
|
|
1722
|
+
.publicKey.export({ type: "spki", format: "pem" });
|
|
1723
|
+
var logHex = "aa".repeat(32);
|
|
1724
|
+
function _logKeys(pem) { var m = {}; m[logHex] = pem; return m; }
|
|
1725
|
+
|
|
1726
|
+
// Unsupported SCT hash algorithm (not sha256/384/512) with a present key.
|
|
1727
|
+
var badHash = _buildSctBytes({ hashAlgo: 99 });
|
|
1728
|
+
var badHashCert = _synthCert({ cn: "BadHash", exts: [_sctExt(_sctListRaw([badHash]))] });
|
|
1729
|
+
var r1 = nt.ct.verifyScts(badHashCert, { logKeys: _logKeys(ecPub), minScts: 1 });
|
|
1730
|
+
check("verifyScts unsupported SCT hash algo -> per-sct unsupported-hash-algo",
|
|
1731
|
+
r1.scts[0].reason === "unsupported-hash-algo");
|
|
1732
|
+
|
|
1733
|
+
// Log key present but unparseable.
|
|
1734
|
+
var okSct = _buildSctBytes({});
|
|
1735
|
+
var okCert = _synthCert({ cn: "OkSct", exts: [_sctExt(_sctListRaw([okSct]))] });
|
|
1736
|
+
var r2 = nt.ct.verifyScts(okCert, { logKeys: _logKeys("not a pem"), minScts: 1 });
|
|
1737
|
+
check("verifyScts unparseable log key -> log-key-parse-failed",
|
|
1738
|
+
r2.scts[0].reason === "log-key-parse-failed");
|
|
1739
|
+
|
|
1740
|
+
// SCT claims RSA (sigAlgo 1) but the registered log key is EC → mismatch.
|
|
1741
|
+
var rsaClaim = _buildSctBytes({ sigAlgo: 1 });
|
|
1742
|
+
var rsaCert = _synthCert({ cn: "RsaClaim", exts: [_sctExt(_sctListRaw([rsaClaim]))] });
|
|
1743
|
+
var r3 = nt.ct.verifyScts(rsaCert, { logKeys: _logKeys(ecPub), minScts: 1 });
|
|
1744
|
+
check("verifyScts SCT-algo vs log-key-type mismatch -> log-key-algo-mismatch",
|
|
1745
|
+
r3.scts[0].reason === "log-key-algo-mismatch");
|
|
1746
|
+
|
|
1747
|
+
// Valid EC key + matching algo but a garbage signature → not verified
|
|
1748
|
+
// (verify returns false, or throws and is caught) → insufficient.
|
|
1749
|
+
var r4 = nt.ct.verifyScts(okCert, { logKeys: _logKeys(ecPub), minScts: 1 });
|
|
1750
|
+
check("verifyScts good key but bad signature -> not verified, insufficient",
|
|
1751
|
+
r4.ok === false && r4.scts[0].verified === false);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// =====================================================================
|
|
1755
|
+
// checkServerIdentity9525 — remaining refuse branches.
|
|
1756
|
+
// =====================================================================
|
|
1757
|
+
|
|
1758
|
+
function testPkixMoreBranches() {
|
|
1759
|
+
// cert argument not an object → hostname-mismatch (peer cert object missing).
|
|
1760
|
+
var e1 = nt.checkServerIdentity9525("foo.example.com", null);
|
|
1761
|
+
check("checkServerIdentity9525 null cert -> hostname-mismatch",
|
|
1762
|
+
e1 && e1.code === "tls/pkix-hostname-mismatch");
|
|
1763
|
+
|
|
1764
|
+
// Empty host string → hostname-mismatch.
|
|
1765
|
+
var e2 = nt.checkServerIdentity9525("", _cert("DNS:foo.example.com"));
|
|
1766
|
+
check("checkServerIdentity9525 empty host -> hostname-mismatch",
|
|
1767
|
+
e2 && e2.code === "tls/pkix-hostname-mismatch");
|
|
1768
|
+
|
|
1769
|
+
// Non-ASCII (U-label) host is refused — operators pre-convert via punycode.
|
|
1770
|
+
var e3 = nt.checkServerIdentity9525("héllo.example.com", _cert("DNS:xn--hllo-bpa.example.com"));
|
|
1771
|
+
check("checkServerIdentity9525 non-ASCII host refuses",
|
|
1772
|
+
e3 && e3.code === "tls/pkix-hostname-mismatch");
|
|
1773
|
+
|
|
1774
|
+
// DNS host but the cert SAN carries only iPAddress entries → mismatch.
|
|
1775
|
+
var e4 = nt.checkServerIdentity9525("foo.example.com", _cert("IP Address:198.51.100.7"));
|
|
1776
|
+
check("checkServerIdentity9525 DNS host vs IP-only SAN -> mismatch",
|
|
1777
|
+
e4 && e4.code === "tls/pkix-hostname-mismatch");
|
|
1778
|
+
|
|
1779
|
+
// A SAN entry without a "kind:value" colon is skipped; a following DNS
|
|
1780
|
+
// entry still matches.
|
|
1781
|
+
check("checkServerIdentity9525 skips colon-less SAN entries",
|
|
1782
|
+
nt.checkServerIdentity9525("foo.example.com",
|
|
1783
|
+
_cert("bare-entry, DNS:foo.example.com")) === undefined);
|
|
1784
|
+
|
|
1785
|
+
// The short "IP" SAN kind (not only "IP Address") is honored for IP hosts.
|
|
1786
|
+
check("checkServerIdentity9525 honors the short IP: SAN kind",
|
|
1787
|
+
nt.checkServerIdentity9525("198.51.100.9", _cert("IP:198.51.100.9")) === undefined);
|
|
1788
|
+
|
|
1789
|
+
// A malformed iPAddress SAN cannot match a valid IP host.
|
|
1790
|
+
var e5 = nt.checkServerIdentity9525("2001:db8::1", _cert("IP Address:2001:db8::xyz"));
|
|
1791
|
+
check("checkServerIdentity9525 malformed IPv6 SAN -> mismatch",
|
|
1792
|
+
e5 && e5.code === "tls/pkix-hostname-mismatch");
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// =====================================================================
|
|
1796
|
+
// connectWithEch — real localhost handshake, error + timeout branches.
|
|
1797
|
+
// =====================================================================
|
|
1798
|
+
|
|
1799
|
+
async function testConnectWithEchRealConnect() {
|
|
1800
|
+
var validEch = _buildEchConfigDraft22({});
|
|
1801
|
+
|
|
1802
|
+
// alpn wrong shape → config-time throw.
|
|
1803
|
+
var eAlpn = null;
|
|
1804
|
+
try { nt.connectWithEch({ host: "127.0.0.1", alpn: "h2" }); } catch (e) { eAlpn = e; }
|
|
1805
|
+
check("connectWithEch non-array alpn refuses",
|
|
1806
|
+
eAlpn instanceof nt.NetworkTlsError && eAlpn.code === "tls/ech-bad-opts");
|
|
1807
|
+
|
|
1808
|
+
// Real connect over a localhost TLS server with an operator echOverride +
|
|
1809
|
+
// rejectUnauthorized:false → drives _doConnect, the insecure-TLS audit,
|
|
1810
|
+
// and the ECH attach/degrade branch to secureConnect.
|
|
1811
|
+
var s1 = await _startTlsServer(undefined);
|
|
1812
|
+
try {
|
|
1813
|
+
var sock = await nt.connectWithEch({
|
|
1814
|
+
host: "127.0.0.1", port: s1.port, servername: "localhost",
|
|
1815
|
+
alpn: ["h2"], echOverride: validEch, rejectUnauthorized: false,
|
|
1816
|
+
});
|
|
1817
|
+
check("connectWithEch resolves a secured socket over localhost",
|
|
1818
|
+
sock && sock.encrypted === true);
|
|
1819
|
+
try { sock.destroy(); } catch (_e) { /* best-effort */ }
|
|
1820
|
+
} finally { s1.close(); }
|
|
1821
|
+
|
|
1822
|
+
// Error path — connect to a closed port rejects.
|
|
1823
|
+
var eDead = null;
|
|
1824
|
+
try {
|
|
1825
|
+
await nt.connectWithEch({ host: "127.0.0.1", port: 1, servername: "localhost",
|
|
1826
|
+
echOverride: validEch, rejectUnauthorized: false, timeoutMs: C.TIME.seconds(5) });
|
|
1827
|
+
} catch (e) { eDead = e; }
|
|
1828
|
+
check("connectWithEch to a closed port rejects", eDead !== null);
|
|
1829
|
+
|
|
1830
|
+
// Timeout path — a plain TCP server that accepts but never speaks TLS.
|
|
1831
|
+
var hung = await new Promise(function (resolve) {
|
|
1832
|
+
var srv = nodeNet.createServer(function (sock) { sock.on("error", function () {}); });
|
|
1833
|
+
srv.on("error", function () {});
|
|
1834
|
+
srv.unref();
|
|
1835
|
+
srv.listen(0, "127.0.0.1", function () { resolve({ srv: srv, port: srv.address().port }); });
|
|
1836
|
+
});
|
|
1837
|
+
var eTo = null;
|
|
1838
|
+
try {
|
|
1839
|
+
await nt.connectWithEch({ host: "127.0.0.1", port: hung.port, servername: "localhost",
|
|
1840
|
+
echOverride: validEch, rejectUnauthorized: false, timeoutMs: 150 });
|
|
1841
|
+
} catch (e) { eTo = e; } finally { try { hung.srv.close(); } catch (_e) { /* best-effort */ } }
|
|
1842
|
+
check("connectWithEch handshake timeout rejects tls/ech-timeout",
|
|
1843
|
+
eTo && eTo.code === "tls/ech-timeout");
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// =====================================================================
|
|
1847
|
+
// CT SCT-list / single-SCT parse rejections (ct.parseScts throws) +
|
|
1848
|
+
// verifyInclusion strip + optional-consistency-proof branches.
|
|
1849
|
+
// =====================================================================
|
|
1850
|
+
|
|
1851
|
+
function testCtParseSctErrors() {
|
|
1852
|
+
// parseScts propagates a malformed outer length (verifyScts would swallow
|
|
1853
|
+
// it as reason:"parse-error"; the raw parse surface throws).
|
|
1854
|
+
var badOuter = _synthCert({ cn: "BadOuter",
|
|
1855
|
+
exts: [_sctExt(_sctListRaw([_buildSctBytes({})], { lieOuterLen: 9999 }))] });
|
|
1856
|
+
var e1 = null;
|
|
1857
|
+
try { nt.ct.parseScts(badOuter); } catch (e) { e1 = e; }
|
|
1858
|
+
check("ct.parseScts malformed outer length throws ct-bad-list",
|
|
1859
|
+
e1 && e1.code === "tls/ct-bad-list");
|
|
1860
|
+
|
|
1861
|
+
// A single SCT shorter than the minimum v1 layout.
|
|
1862
|
+
var shortSct = _synthCert({ cn: "ShortSct",
|
|
1863
|
+
exts: [_sctExt(_sctListRaw([Buffer.alloc(10)]))] });
|
|
1864
|
+
var e2 = null;
|
|
1865
|
+
try { nt.ct.parseScts(shortSct); } catch (e) { e2 = e; }
|
|
1866
|
+
check("ct.parseScts too-short SCT throws ct-sct-too-short",
|
|
1867
|
+
e2 && e2.code === "tls/ct-sct-too-short");
|
|
1868
|
+
|
|
1869
|
+
// An SCT with a non-zero (unsupported) version byte.
|
|
1870
|
+
var badVer = _synthCert({ cn: "BadVer",
|
|
1871
|
+
exts: [_sctExt(_sctListRaw([_buildSctBytes({ version: 1 })]))] });
|
|
1872
|
+
var e3 = null;
|
|
1873
|
+
try { nt.ct.parseScts(badVer); } catch (e) { e3 = e; }
|
|
1874
|
+
check("ct.parseScts non-v1 SCT throws ct-sct-bad-version",
|
|
1875
|
+
e3 && e3.code === "tls/ct-sct-bad-version");
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// ocsp.fetch composes buildRequest + httpClient; a transport rejection
|
|
1879
|
+
// (the framework's https-only outbound allowlist refuses the responder URL)
|
|
1880
|
+
// surfaces as tls/ocsp-fetch-failed. Drives the buildRequest + request +
|
|
1881
|
+
// catch path without a live responder.
|
|
1882
|
+
async function testOcspFetchRequestPath() {
|
|
1883
|
+
var leafPem = _toPem(_synthCert({ cn: "FetchReq Leaf", serial: _SERIAL }));
|
|
1884
|
+
var issuerPem = _toPem(_synthCert({ cn: "FetchReq CA", serial: Buffer.from([0x01]) }));
|
|
1885
|
+
var e1 = null;
|
|
1886
|
+
try {
|
|
1887
|
+
await nt.ocsp.fetch({ leafPem: leafPem, issuerPem: issuerPem,
|
|
1888
|
+
responderUrl: "http://127.0.0.1:1/ocsp", nonce: false });
|
|
1889
|
+
} catch (e) { e1 = e; }
|
|
1890
|
+
check("ocsp.fetch responder transport failure throws ocsp-fetch-failed",
|
|
1891
|
+
e1 && e1.code === "tls/ocsp-fetch-failed");
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function testCtInclusionExtra() {
|
|
1895
|
+
var signedEntry = Buffer.from("fake-signed-entry-der-bytes");
|
|
1896
|
+
var ts = 1700000000000;
|
|
1897
|
+
var leafHash = _ctLeafHash(signedEntry, ts);
|
|
1898
|
+
|
|
1899
|
+
// sct without signedEntryDer -> verifyInclusion strips the SCT extension
|
|
1900
|
+
// from the supplied leaf cert to derive the signed entry itself.
|
|
1901
|
+
var sctCert = _synthCert({ cn: "InclLeaf",
|
|
1902
|
+
exts: [_sctExt(_sctListRaw([_buildSctBytes({})]))] });
|
|
1903
|
+
var stripPath = nt.ct.verifyInclusion({
|
|
1904
|
+
sct: { logIdHex: "aa", timestamp: ts }, // no signedEntryDer
|
|
1905
|
+
leafCertificate: sctCert, leafIndex: 0, auditPath: [],
|
|
1906
|
+
sthFromLog: { treeSize: 1, rootHash: Buffer.alloc(32, 0x00) },
|
|
1907
|
+
});
|
|
1908
|
+
check("verifyInclusion derives the signed entry by stripping the SCT ext",
|
|
1909
|
+
stripPath.valid === false && stripPath.reason === "root-mismatch");
|
|
1910
|
+
|
|
1911
|
+
// Inclusion reconciles, but the optional consistency proof does not.
|
|
1912
|
+
var incl = nt.ct.verifyInclusion({
|
|
1913
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1914
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [],
|
|
1915
|
+
sthFromLog: { treeSize: 1, rootHash: leafHash },
|
|
1916
|
+
consistency: { firstSize: 1, firstRoot: Buffer.alloc(32, 0x55), proof: [] },
|
|
1917
|
+
});
|
|
1918
|
+
check("verifyInclusion with a non-reconciling consistency proof fails closed",
|
|
1919
|
+
incl.valid === false &&
|
|
1920
|
+
(incl.reason === "consistency-mismatch" || incl.reason === "consistency-walk-failed"));
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// The SCT + TLS-Feature cert-extension extractors are deliberately tolerant
|
|
1924
|
+
// of malformed ASN.1 — they return "no extension" rather than throwing so a
|
|
1925
|
+
// broken peer cert can't crash the CT / must-staple checks.
|
|
1926
|
+
function testSctAndTlsFeatureTolerance() {
|
|
1927
|
+
var malformed = [
|
|
1928
|
+
Buffer.from([0x30, 0x82, 0x01, 0x00]), // SEQUENCE, long-form len, no content
|
|
1929
|
+
Buffer.from([0x04, 0x01, 0x00]), // OCTET STRING (top not SEQUENCE)
|
|
1930
|
+
Buffer.from([0x30, 0x00]), // empty SEQUENCE (no children)
|
|
1931
|
+
asn1.writeSequence([asn1.writeInteger(Buffer.from([1]))]), // tbs is INTEGER, not SEQUENCE
|
|
1932
|
+
asn1.writeSequence([asn1.writeSequence([asn1.writeInteger(Buffer.from([1]))])]), // tbs SEQUENCE, no [3] extensions
|
|
1933
|
+
asn1.writeSequence([asn1.writeSequence([ // [3] wrapping a non-SEQUENCE
|
|
1934
|
+
asn1.writeContextExplicit(3, asn1.writeInteger(Buffer.from([1]))),
|
|
1935
|
+
])]),
|
|
1936
|
+
];
|
|
1937
|
+
var tolerated = true;
|
|
1938
|
+
for (var i = 0; i < malformed.length; i += 1) {
|
|
1939
|
+
try {
|
|
1940
|
+
if (nt.ct.parseScts(malformed[i]).length !== 0) tolerated = false;
|
|
1941
|
+
if (nt.ocsp.inspectMustStaple(malformed[i]).mustStaple !== false) tolerated = false;
|
|
1942
|
+
} catch (_e) { tolerated = false; }
|
|
1943
|
+
}
|
|
1944
|
+
check("SCT + TLS-Feature extractors tolerate malformed cert buffers", tolerated);
|
|
1945
|
+
check("ct.inspect on a non-cert buffer -> hasSctExtension false",
|
|
1946
|
+
nt.ct.inspect(Buffer.from([0x30, 0x00])).hasSctExtension === false);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// RFC 9162 §2.1.3/§2.1.4 Merkle inclusion + consistency walks — multi-level
|
|
1950
|
+
// audit paths (siblings supplied as opaque 32-byte hashes; the expected root
|
|
1951
|
+
// is recomputed with the same inner-hash the module uses).
|
|
1952
|
+
function testCtMerklePaths() {
|
|
1953
|
+
var ts = 1700000000000;
|
|
1954
|
+
var signedEntry = Buffer.from("merkle-leaf-entry");
|
|
1955
|
+
var h1 = _ctLeafHash(signedEntry, ts);
|
|
1956
|
+
var s0 = Buffer.alloc(32, 0x11);
|
|
1957
|
+
var s1 = Buffer.alloc(32, 0x22);
|
|
1958
|
+
|
|
1959
|
+
// 4-leaf tree, leafIndex 1 (left child then combined on the right).
|
|
1960
|
+
var root4 = _ctInner(_ctInner(s0, h1), s1);
|
|
1961
|
+
check("verifyInclusion 4-leaf index1 climbs the audit path",
|
|
1962
|
+
nt.ct.verifyInclusion({
|
|
1963
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1964
|
+
leafCertificate: Buffer.from("x"), leafIndex: 1, auditPath: [s0, s1],
|
|
1965
|
+
sthFromLog: { treeSize: 4, rootHash: root4 },
|
|
1966
|
+
}).valid === true);
|
|
1967
|
+
|
|
1968
|
+
// 3-leaf tree, right-most leaf (the fn===sn branch).
|
|
1969
|
+
var root3 = _ctInner(s0, h1);
|
|
1970
|
+
check("verifyInclusion 3-leaf right-most leaf (fn===sn)",
|
|
1971
|
+
nt.ct.verifyInclusion({
|
|
1972
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1973
|
+
leafCertificate: Buffer.from("x"), leafIndex: 2, auditPath: [s0],
|
|
1974
|
+
sthFromLog: { treeSize: 3, rootHash: root3 },
|
|
1975
|
+
}).valid === true);
|
|
1976
|
+
|
|
1977
|
+
// Audit path exhausted before the root.
|
|
1978
|
+
check("verifyInclusion exhausted audit path -> inclusion-walk-failed",
|
|
1979
|
+
nt.ct.verifyInclusion({
|
|
1980
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1981
|
+
leafCertificate: Buffer.from("x"), leafIndex: 1, auditPath: [],
|
|
1982
|
+
sthFromLog: { treeSize: 4, rootHash: root4 },
|
|
1983
|
+
}).reason === "inclusion-walk-failed");
|
|
1984
|
+
|
|
1985
|
+
// Audit path entry that is not a 32-byte hash.
|
|
1986
|
+
check("verifyInclusion non-32-byte audit entry -> inclusion-walk-failed",
|
|
1987
|
+
nt.ct.verifyInclusion({
|
|
1988
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1989
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [Buffer.alloc(4)],
|
|
1990
|
+
sthFromLog: { treeSize: 2, rootHash: Buffer.alloc(32, 0x00) },
|
|
1991
|
+
}).reason === "inclusion-walk-failed");
|
|
1992
|
+
|
|
1993
|
+
// Audit path with trailing entries beyond the root.
|
|
1994
|
+
check("verifyInclusion trailing audit entries -> inclusion-walk-failed",
|
|
1995
|
+
nt.ct.verifyInclusion({
|
|
1996
|
+
sct: { logIdHex: "aa", timestamp: ts, signedEntryDer: signedEntry },
|
|
1997
|
+
leafCertificate: Buffer.from("x"), leafIndex: 0, auditPath: [s0, s1],
|
|
1998
|
+
sthFromLog: { treeSize: 2, rootHash: Buffer.alloc(32, 0x00) },
|
|
1999
|
+
}).reason === "inclusion-walk-failed");
|
|
2000
|
+
|
|
2001
|
+
// Consistency m=2 → n=4 (the odd-index skip loop runs; first tree complete).
|
|
2002
|
+
var firstHash = Buffer.alloc(32, 0x33);
|
|
2003
|
+
var c0 = Buffer.alloc(32, 0x44);
|
|
2004
|
+
check("verifyConsistency m=2 n=4 valid",
|
|
2005
|
+
nt.ct.verifyConsistency({ firstSize: 2, secondSize: 4, proof: [c0],
|
|
2006
|
+
firstRoot: firstHash, secondRoot: _ctInner(firstHash, c0) }).valid === true);
|
|
2007
|
+
|
|
2008
|
+
// Consistency m=3 → n=4 (first tree NOT a complete subtree; proof shifted).
|
|
2009
|
+
var p0 = Buffer.alloc(32, 0x55), p1 = Buffer.alloc(32, 0x66), p2 = Buffer.alloc(32, 0x77);
|
|
2010
|
+
check("verifyConsistency m=3 n=4 incomplete-subtree valid",
|
|
2011
|
+
nt.ct.verifyConsistency({ firstSize: 3, secondSize: 4, proof: [p0, p1, p2],
|
|
2012
|
+
firstRoot: Buffer.alloc(32, 0x88),
|
|
2013
|
+
secondRoot: _ctInner(p2, _ctInner(p0, p1)) }).valid === true);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// =====================================================================
|
|
2017
|
+
// Trust-store audit-emit paths + no-argument config-time guards
|
|
2018
|
+
//
|
|
2019
|
+
// The CA-store tests above pass { audit: false } to keep the emit side
|
|
2020
|
+
// quiet, so the DEFAULT-audit emit branch of removeCaByLabel / clearAll /
|
|
2021
|
+
// purgeExpired never runs, and the no-argument `opts || {}` defaulting of
|
|
2022
|
+
// applyToContext / the monitors is never taken. Drive those here through
|
|
2023
|
+
// the real consumer surface.
|
|
2024
|
+
// =====================================================================
|
|
2025
|
+
|
|
2026
|
+
function testTrustStoreAuditEmitAndGuards() {
|
|
2027
|
+
nt._resetForTest();
|
|
2028
|
+
|
|
2029
|
+
// removeCaByLabel WITHOUT { audit: false } — the emit branch runs.
|
|
2030
|
+
nt.addCa(_toPem(_synthCert({ cn: "AE1" })), { label: "ae" });
|
|
2031
|
+
nt.addCa(_toPem(_synthCert({ cn: "AE2", serial: Buffer.from([0x61]) })), { label: "ae" });
|
|
2032
|
+
check("removeCaByLabel default-audit removes both", nt.removeCaByLabel("ae") === 2);
|
|
2033
|
+
|
|
2034
|
+
// clearAll WITHOUT { audit: false } — the emit branch runs.
|
|
2035
|
+
nt.addCa(_toPem(_synthCert({ cn: "AE3", serial: Buffer.from([0x62]) })), {});
|
|
2036
|
+
check("clearAll default-audit clears the store", nt.clearAll() === 1);
|
|
2037
|
+
|
|
2038
|
+
// purgeExpired WITHOUT { audit: false } with a genuinely-expired cert.
|
|
2039
|
+
nt.addCa(_toPem(_synthCert({ cn: "Fresh AE", notAfter: "270101000000Z" })), { label: "fresh" });
|
|
2040
|
+
nt.addCa(_toPem(_synthCert({ cn: "Old AE", serial: Buffer.from([0x63]),
|
|
2041
|
+
notBefore: "190101000000Z", notAfter: "200101000000Z" })), { label: "old" });
|
|
2042
|
+
check("purgeExpired default-audit drops the expired cert", nt.purgeExpired() === 1);
|
|
2043
|
+
|
|
2044
|
+
// applyToContext() with NO argument — opts || {} and opts.base || {}
|
|
2045
|
+
// both default; PQC groups are still folded in.
|
|
2046
|
+
var ctx = nt.applyToContext();
|
|
2047
|
+
check("applyToContext() no-arg returns an object", ctx !== null && typeof ctx === "object");
|
|
2048
|
+
check("applyToContext() no-arg still sets PQC groups",
|
|
2049
|
+
typeof ctx.groups === "string" && ctx.groups.indexOf("X25519MLKEM768") === 0);
|
|
2050
|
+
|
|
2051
|
+
// No-argument monitors default their opts, then throw on the missing
|
|
2052
|
+
// intervalMs at the config-time tier.
|
|
2053
|
+
var eExp = null;
|
|
2054
|
+
try { nt.expiryMonitor(); } catch (e) { eExp = e; }
|
|
2055
|
+
check("expiryMonitor() no-arg throws tls/bad-interval", eExp && eExp.code === "tls/bad-interval");
|
|
2056
|
+
var eDrift = null;
|
|
2057
|
+
try { nt.pinsetDriftMonitor(); } catch (e) { eDrift = e; }
|
|
2058
|
+
check("pinsetDriftMonitor() no-arg throws tls/bad-interval", eDrift && eDrift.code === "tls/bad-interval");
|
|
2059
|
+
|
|
2060
|
+
nt._resetForTest();
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// _isPathLike short-circuits: a string that is too long (> 1 KiB) or that
|
|
2064
|
+
// carries a CRLF is NOT treated as a filesystem path, so addCa parses it as
|
|
2065
|
+
// PEM and it fails as empty rather than being stat()'d as a path.
|
|
2066
|
+
function testCertPathLikeRejections() {
|
|
2067
|
+
nt._resetForTest();
|
|
2068
|
+
var big = new Array(1600).join("a"); // ~1599 bytes > 1 KiB, no PEM marker
|
|
2069
|
+
var eBig = null;
|
|
2070
|
+
try { nt.addCa(big); } catch (e) { eBig = e; }
|
|
2071
|
+
check("addCa(>1KiB non-PEM string) is not path-like -> tls/empty-pem",
|
|
2072
|
+
eBig && eBig.code === "tls/empty-pem");
|
|
2073
|
+
|
|
2074
|
+
var eCrlf = null;
|
|
2075
|
+
try { nt.addCa("some\r\ntext"); } catch (e) { eCrlf = e; }
|
|
2076
|
+
check("addCa(CRLF non-PEM string) is not path-like -> tls/empty-pem",
|
|
2077
|
+
eCrlf && eCrlf.code === "tls/empty-pem");
|
|
2078
|
+
nt._resetForTest();
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
// RFC 9525 wildcard label-walk branches not reached by the happy-path
|
|
2082
|
+
// wildcard tests: an embedded '*' in a non-left-most label, a mismatched
|
|
2083
|
+
// non-left-most label under a valid left-most wildcard, and an empty
|
|
2084
|
+
// left-most host label.
|
|
2085
|
+
function testPkixWildcardLabelWalk() {
|
|
2086
|
+
var e1 = b.network.tls.checkServerIdentity9525("a.example.com",
|
|
2087
|
+
_cert("DNS:*.exa*ple.com"));
|
|
2088
|
+
check("wildcard left label + embedded '*' in a later label refuses",
|
|
2089
|
+
e1 && e1.code === "tls/pkix-hostname-mismatch");
|
|
2090
|
+
|
|
2091
|
+
var e2 = b.network.tls.checkServerIdentity9525("foo.example.net",
|
|
2092
|
+
_cert("DNS:*.example.com"));
|
|
2093
|
+
check("wildcard match with a mismatched later label refuses",
|
|
2094
|
+
e2 && e2.code === "tls/pkix-hostname-mismatch");
|
|
2095
|
+
|
|
2096
|
+
var e3 = b.network.tls.checkServerIdentity9525(".example.com",
|
|
2097
|
+
_cert("DNS:*.example.com"));
|
|
2098
|
+
check("wildcard refuses an empty left-most host label",
|
|
2099
|
+
e3 && e3.code === "tls/pkix-hostname-mismatch");
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// _refuseCnFallback second operand (subjectaltname.length === 0) + the
|
|
2103
|
+
// _checkServerIdentityStrict delegation-to-9525 tail on a clean SAN.
|
|
2104
|
+
function testStrictCombinerAndEmptySanCn() {
|
|
2105
|
+
var errEmpty = b.network.tls.checkServerIdentity9525("x.example.com",
|
|
2106
|
+
{ subject: { CN: "x.example.com" }, subjectaltname: "" });
|
|
2107
|
+
check("empty-string SAN + CN refuses with tls/pkix-cn-fallback-refused",
|
|
2108
|
+
errEmpty && errEmpty.code === "tls/pkix-cn-fallback-refused");
|
|
2109
|
+
|
|
2110
|
+
var ok = b.network.tls._checkServerIdentityStrict("foo.example.com",
|
|
2111
|
+
_cert("DNS:foo.example.com"));
|
|
2112
|
+
check("strict combiner delegates to the 9525 verifier on a clean SAN", ok === undefined);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// wrapSNICallback audit-metadata fallbacks: a NON-string servername maps to
|
|
2116
|
+
// null and a thrown NON-Error value maps through String(err).
|
|
2117
|
+
function testWrapSniCallbackNonStringServername() {
|
|
2118
|
+
// A NON-Error throw value (no .message field) drives reason -> String(err);
|
|
2119
|
+
// a NON-string servername drives servername -> null.
|
|
2120
|
+
var thrown = { note: "no-message-field" };
|
|
2121
|
+
var wrapped = nt.wrapSNICallback(function () { throw thrown; });
|
|
2122
|
+
var cbErr = "unset";
|
|
2123
|
+
wrapped(12345, function (err) { cbErr = err; });
|
|
2124
|
+
check("throwing SNICallback with non-string servername surfaces the raw throw",
|
|
2125
|
+
cbErr === thrown);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
// =====================================================================
|
|
2129
|
+
// Monitor tick branches — the "nothing expiring" / "no drift" / no-callback
|
|
2130
|
+
// / throwing-callback / baseline-uncaptured paths the happy-path monitor
|
|
2131
|
+
// tests don't reach. Driven through the real timer (safeAsync.repeating);
|
|
2132
|
+
// audit emissions captured through the documented b.audit.safeEmit test
|
|
2133
|
+
// seam and restored in a finally.
|
|
2134
|
+
// =====================================================================
|
|
2135
|
+
|
|
2136
|
+
function _captureAudit(bucket) {
|
|
2137
|
+
var orig = auditMod.safeEmit;
|
|
2138
|
+
auditMod.safeEmit = function (evt) { bucket.push(evt); };
|
|
2139
|
+
return function restore() { auditMod.safeEmit = orig; };
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
async function testExpiryMonitorOkTick() {
|
|
2143
|
+
nt._resetForTest();
|
|
2144
|
+
// Fresh cert + a tiny window -> every tick reports "ok" (nothing expiring).
|
|
2145
|
+
nt.addCa(_toPem(_synthCert({ cn: "OK Mon", notAfter: "270101000000Z" })), { label: "ok" });
|
|
2146
|
+
var events = [];
|
|
2147
|
+
var restore = _captureAudit(events);
|
|
2148
|
+
var mon = nt.expiryMonitor({ intervalMs: 15, windowMs: 1 });
|
|
2149
|
+
try {
|
|
2150
|
+
await helpers.waitUntil(function () {
|
|
2151
|
+
return events.some(function (e) {
|
|
2152
|
+
return e.action === "network.tls.ca.expiry_check" && e.outcome === "ok";
|
|
2153
|
+
});
|
|
2154
|
+
}, { timeoutMs: 5000, label: "expiryMonitor: ok tick emitted" });
|
|
2155
|
+
check("expiryMonitor emits an 'ok' expiry_check when nothing is expiring", true);
|
|
2156
|
+
} finally {
|
|
2157
|
+
mon.stop();
|
|
2158
|
+
restore();
|
|
2159
|
+
nt._resetForTest();
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
async function testExpiryMonitorNoCallbackWarnTick() {
|
|
2164
|
+
nt._resetForTest();
|
|
2165
|
+
// Two certs inside a wide window, NO onExpiring -> the warn path runs
|
|
2166
|
+
// (emit + earliest-validTo reduce over multiple rows) and the onExpiring
|
|
2167
|
+
// dispatch takes its "no callback" side.
|
|
2168
|
+
nt.addCa(_toPem(_synthCert({ cn: "W1", notAfter: "270101000000Z" })), { label: "w1" });
|
|
2169
|
+
nt.addCa(_toPem(_synthCert({ cn: "W2", serial: Buffer.from([0x71]),
|
|
2170
|
+
notAfter: "280101000000Z" })), { label: "w2" });
|
|
2171
|
+
var events = [];
|
|
2172
|
+
var restore = _captureAudit(events);
|
|
2173
|
+
var mon = nt.expiryMonitor({ intervalMs: 15, windowMs: C.TIME.days(3650) });
|
|
2174
|
+
try {
|
|
2175
|
+
await helpers.waitUntil(function () {
|
|
2176
|
+
return events.some(function (e) { return e.action === "network.tls.ca.expiring"; });
|
|
2177
|
+
}, { timeoutMs: 5000, label: "expiryMonitor: warn tick with no callback" });
|
|
2178
|
+
var ev = events.filter(function (e) { return e.action === "network.tls.ca.expiring"; })[0];
|
|
2179
|
+
check("expiryMonitor warn tick reports both expiring certs", ev.metadata.count === 2);
|
|
2180
|
+
} finally {
|
|
2181
|
+
mon.stop();
|
|
2182
|
+
restore();
|
|
2183
|
+
nt._resetForTest();
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
async function testExpiryMonitorThrowingCallback() {
|
|
2188
|
+
nt._resetForTest();
|
|
2189
|
+
nt.addCa(_toPem(_synthCert({ cn: "T Mon", notAfter: "270101000000Z" })), { label: "t" });
|
|
2190
|
+
var called = 0;
|
|
2191
|
+
var mon = nt.expiryMonitor({
|
|
2192
|
+
intervalMs: 15,
|
|
2193
|
+
windowMs: C.TIME.days(3650),
|
|
2194
|
+
onExpiring: function () { called += 1; throw new Error("operator hook boom"); },
|
|
2195
|
+
});
|
|
2196
|
+
try {
|
|
2197
|
+
await helpers.waitUntil(function () { return called >= 1; },
|
|
2198
|
+
{ timeoutMs: 5000, label: "expiryMonitor: throwing onExpiring invoked" });
|
|
2199
|
+
check("expiryMonitor swallows a throwing onExpiring (never escapes the tick)", called >= 1);
|
|
2200
|
+
} finally {
|
|
2201
|
+
mon.stop();
|
|
2202
|
+
nt._resetForTest();
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
async function testPinsetDriftMonitorOkTick() {
|
|
2207
|
+
nt._resetForTest();
|
|
2208
|
+
nt.addCa(_toPem(_synthCert({ cn: "Stable CA" })), {});
|
|
2209
|
+
nt.captureBaselineFingerprints(); // baseline == current -> no drift
|
|
2210
|
+
var events = [];
|
|
2211
|
+
var restore = _captureAudit(events);
|
|
2212
|
+
var mon = nt.pinsetDriftMonitor({ intervalMs: 15 });
|
|
2213
|
+
try {
|
|
2214
|
+
await helpers.waitUntil(function () {
|
|
2215
|
+
return events.some(function (e) {
|
|
2216
|
+
return e.action === "network.tls.pinset.drift_check" && e.outcome === "ok";
|
|
2217
|
+
});
|
|
2218
|
+
}, { timeoutMs: 5000, label: "pinsetDriftMonitor: ok tick" });
|
|
2219
|
+
check("pinsetDriftMonitor emits an 'ok' drift_check when the pinset is stable", true);
|
|
2220
|
+
} finally {
|
|
2221
|
+
mon.stop();
|
|
2222
|
+
restore();
|
|
2223
|
+
nt._resetForTest();
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
async function testPinsetDriftMonitorNoCallback() {
|
|
2228
|
+
nt._resetForTest();
|
|
2229
|
+
nt.captureBaselineFingerprints(); // baseline == [] (empty store)
|
|
2230
|
+
nt.addCa(_toPem(_synthCert({ cn: "Drift NoCb CA" })), {}); // now drifts
|
|
2231
|
+
var events = [];
|
|
2232
|
+
var restore = _captureAudit(events);
|
|
2233
|
+
var mon = nt.pinsetDriftMonitor({ intervalMs: 15 }); // NO onDrift
|
|
2234
|
+
try {
|
|
2235
|
+
await helpers.waitUntil(function () {
|
|
2236
|
+
return events.some(function (e) { return e.action === "network.tls.pinset.drifted"; });
|
|
2237
|
+
}, { timeoutMs: 5000, label: "pinsetDriftMonitor: drifted tick, no callback" });
|
|
2238
|
+
check("pinsetDriftMonitor drift tick runs with no onDrift callback", true);
|
|
2239
|
+
} finally {
|
|
2240
|
+
mon.stop();
|
|
2241
|
+
restore();
|
|
2242
|
+
nt._resetForTest();
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
async function testPinsetDriftMonitorThrowingCallback() {
|
|
2247
|
+
nt._resetForTest();
|
|
2248
|
+
nt.captureBaselineFingerprints();
|
|
2249
|
+
nt.addCa(_toPem(_synthCert({ cn: "Drift Throw CA" })), {});
|
|
2250
|
+
var called = 0;
|
|
2251
|
+
var mon = nt.pinsetDriftMonitor({
|
|
2252
|
+
intervalMs: 15,
|
|
2253
|
+
onDrift: function () { called += 1; throw new Error("drift hook boom"); },
|
|
2254
|
+
});
|
|
2255
|
+
try {
|
|
2256
|
+
await helpers.waitUntil(function () { return called >= 1; },
|
|
2257
|
+
{ timeoutMs: 5000, label: "pinsetDriftMonitor: throwing onDrift invoked" });
|
|
2258
|
+
check("pinsetDriftMonitor swallows a throwing onDrift", called >= 1);
|
|
2259
|
+
} finally {
|
|
2260
|
+
mon.stop();
|
|
2261
|
+
nt._resetForTest();
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
async function testPinsetDriftMonitorNoBaseline() {
|
|
2266
|
+
nt._resetForTest();
|
|
2267
|
+
nt.addCa(_toPem(_synthCert({ cn: "No Baseline CA" })), {});
|
|
2268
|
+
// No captureBaselineFingerprints() -> detectBaselineDrift() returns null,
|
|
2269
|
+
// so every tick early-returns and nothing is emitted.
|
|
2270
|
+
var events = [];
|
|
2271
|
+
var restore = _captureAudit(events);
|
|
2272
|
+
var mon = nt.pinsetDriftMonitor({ intervalMs: 15 });
|
|
2273
|
+
try {
|
|
2274
|
+
await helpers.passiveObserve(300, "pinsetDriftMonitor: no baseline -> silent");
|
|
2275
|
+
check("pinsetDriftMonitor with no captured baseline emits nothing",
|
|
2276
|
+
events.filter(function (e) {
|
|
2277
|
+
return /^network\.tls\.pinset/.test(e.action || "");
|
|
2278
|
+
}).length === 0);
|
|
2279
|
+
} finally {
|
|
2280
|
+
mon.stop();
|
|
2281
|
+
restore();
|
|
2282
|
+
nt._resetForTest();
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// =====================================================================
|
|
2287
|
+
|
|
489
2288
|
async function run() {
|
|
490
2289
|
testNetworkTlsErrorPermanentClassification();
|
|
491
2290
|
testInsecureTlsAudit();
|
|
@@ -508,7 +2307,81 @@ async function run() {
|
|
|
508
2307
|
testPkixIpSanIpv6Canonicalization();
|
|
509
2308
|
testPkixIpSanCrossFamilyRefuses();
|
|
510
2309
|
testPkixSanWithMultipleEntries();
|
|
2310
|
+
testPkixQuotedSanNoBypass();
|
|
511
2311
|
testPkixHostShape();
|
|
2312
|
+
|
|
2313
|
+
nt._resetForTest();
|
|
2314
|
+
try {
|
|
2315
|
+
// CA store
|
|
2316
|
+
testAddCaShapes();
|
|
2317
|
+
testAddCaRejections();
|
|
2318
|
+
testAddCaFromFileAndDir();
|
|
2319
|
+
testRemoveCa();
|
|
2320
|
+
testRemoveCaByLabel();
|
|
2321
|
+
testClearAll();
|
|
2322
|
+
testPurgeExpired();
|
|
2323
|
+
testExpiringSoon();
|
|
2324
|
+
testSystemTrustAndApplyToContext();
|
|
2325
|
+
testBaselineDrift();
|
|
2326
|
+
// monitors
|
|
2327
|
+
testMonitorValidation();
|
|
2328
|
+
await testExpiryMonitorTick();
|
|
2329
|
+
await testPinsetDriftMonitorTick();
|
|
2330
|
+
// trust-store audit-emit + no-arg guards, cert path-like rejection,
|
|
2331
|
+
// PKIX wildcard-walk / strict-combiner, SNI metadata fallbacks
|
|
2332
|
+
testTrustStoreAuditEmitAndGuards();
|
|
2333
|
+
testCertPathLikeRejections();
|
|
2334
|
+
testPkixWildcardLabelWalk();
|
|
2335
|
+
testStrictCombinerAndEmptySanCn();
|
|
2336
|
+
testWrapSniCallbackNonStringServername();
|
|
2337
|
+
// monitor tick branches (ok / no-callback / throwing-callback / no-baseline)
|
|
2338
|
+
await testExpiryMonitorOkTick();
|
|
2339
|
+
await testExpiryMonitorNoCallbackWarnTick();
|
|
2340
|
+
await testExpiryMonitorThrowingCallback();
|
|
2341
|
+
await testPinsetDriftMonitorOkTick();
|
|
2342
|
+
await testPinsetDriftMonitorNoCallback();
|
|
2343
|
+
await testPinsetDriftMonitorThrowingCallback();
|
|
2344
|
+
await testPinsetDriftMonitorNoBaseline();
|
|
2345
|
+
// PQC
|
|
2346
|
+
testPqcKeyShares();
|
|
2347
|
+
// buildOptions
|
|
2348
|
+
testBuildOptionsBranches();
|
|
2349
|
+
// OCSP
|
|
2350
|
+
testOcspParseShapeErrors();
|
|
2351
|
+
testOcspParseUnsupportedResponseType();
|
|
2352
|
+
testOcspParseBadTime();
|
|
2353
|
+
testOcspParseUtcTimeYear();
|
|
2354
|
+
testOcspEvaluateBranches();
|
|
2355
|
+
testOcspEvaluateNonce();
|
|
2356
|
+
testOcspEvaluateIssuerBindShapeErrors();
|
|
2357
|
+
testOcspBuildRequest();
|
|
2358
|
+
testOcspCertShapeErrors();
|
|
2359
|
+
testOcspEvaluateDeepBinding();
|
|
2360
|
+
await testOcspFetchGuards();
|
|
2361
|
+
await testOcspFetchRequestPath();
|
|
2362
|
+
await testOcspRequireGoodEmpty();
|
|
2363
|
+
await testOcspConnectRealPaths();
|
|
2364
|
+
testOcspMustStaple();
|
|
2365
|
+
// CT
|
|
2366
|
+
testCtInspectAndParse();
|
|
2367
|
+
testCtVerifyScts();
|
|
2368
|
+
testCtVerifyMoreScts();
|
|
2369
|
+
testCtParseSctErrors();
|
|
2370
|
+
testSctAndTlsFeatureTolerance();
|
|
2371
|
+
testCtVerifyInclusion();
|
|
2372
|
+
testCtInclusionExtra();
|
|
2373
|
+
testCtMerklePaths();
|
|
2374
|
+
testCtVerifyConsistency();
|
|
2375
|
+
// ECH extra framing + real connect
|
|
2376
|
+
testEchExtraFraming();
|
|
2377
|
+
await testConnectWithEchRealConnect();
|
|
2378
|
+
// PKIX extra branches
|
|
2379
|
+
testPkixMoreBranches();
|
|
2380
|
+
// SNI
|
|
2381
|
+
testWrapSniCallback();
|
|
2382
|
+
} finally {
|
|
2383
|
+
nt._resetForTest();
|
|
2384
|
+
}
|
|
512
2385
|
}
|
|
513
2386
|
|
|
514
2387
|
module.exports = { run: run };
|