@blamejs/blamejs-shop 0.5.12 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (522) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/sales-tax-filings.js +143 -0
  4. package/lib/vendor/MANIFEST.json +632 -416
  5. package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
  6. package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
  7. package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
  8. package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
  9. package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
  10. package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
  11. package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
  12. package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
  13. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
  14. package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
  15. package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
  16. package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
  17. package/lib/vendor/blamejs/.gitignore +2 -0
  18. package/lib/vendor/blamejs/CHANGELOG.md +122 -0
  19. package/lib/vendor/blamejs/NOTICE +26 -1
  20. package/lib/vendor/blamejs/SECURITY.md +1 -1
  21. package/lib/vendor/blamejs/api-snapshot.json +207 -5
  22. package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
  23. package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
  24. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
  25. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
  26. package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
  27. package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
  28. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
  29. package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
  30. package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
  31. package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
  32. package/lib/vendor/blamejs/index.js +6 -0
  33. package/lib/vendor/blamejs/lib/a2a.js +4 -4
  34. package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
  35. package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
  36. package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
  37. package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
  38. package/lib/vendor/blamejs/lib/archive-read.js +8 -0
  39. package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
  40. package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
  41. package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
  42. package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
  43. package/lib/vendor/blamejs/lib/audit.js +71 -8
  44. package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
  45. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
  46. package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
  47. package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
  48. package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
  49. package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
  50. package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
  51. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
  52. package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
  53. package/lib/vendor/blamejs/lib/backup/index.js +16 -5
  54. package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
  55. package/lib/vendor/blamejs/lib/base32.js +21 -0
  56. package/lib/vendor/blamejs/lib/break-glass.js +19 -10
  57. package/lib/vendor/blamejs/lib/bundler.js +5 -1
  58. package/lib/vendor/blamejs/lib/cache-status.js +3 -7
  59. package/lib/vendor/blamejs/lib/calendar.js +21 -1
  60. package/lib/vendor/blamejs/lib/cbor.js +12 -1
  61. package/lib/vendor/blamejs/lib/cert.js +29 -11
  62. package/lib/vendor/blamejs/lib/cli.js +94 -24
  63. package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
  64. package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
  65. package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
  66. package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
  67. package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
  68. package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
  69. package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
  70. package/lib/vendor/blamejs/lib/content-digest.js +2 -1
  71. package/lib/vendor/blamejs/lib/cookies.js +13 -3
  72. package/lib/vendor/blamejs/lib/cose.js +31 -16
  73. package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
  74. package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
  75. package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
  76. package/lib/vendor/blamejs/lib/crypto.js +314 -19
  77. package/lib/vendor/blamejs/lib/csp.js +11 -3
  78. package/lib/vendor/blamejs/lib/daemon.js +270 -24
  79. package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
  80. package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
  81. package/lib/vendor/blamejs/lib/db-query.js +34 -7
  82. package/lib/vendor/blamejs/lib/db.js +18 -6
  83. package/lib/vendor/blamejs/lib/dbsc.js +17 -0
  84. package/lib/vendor/blamejs/lib/dev.js +21 -2
  85. package/lib/vendor/blamejs/lib/dsr.js +157 -102
  86. package/lib/vendor/blamejs/lib/external-db.js +41 -2
  87. package/lib/vendor/blamejs/lib/file-upload.js +25 -7
  88. package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
  89. package/lib/vendor/blamejs/lib/forms.js +113 -27
  90. package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
  91. package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
  92. package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
  93. package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
  94. package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
  95. package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
  96. package/lib/vendor/blamejs/lib/guard-html.js +26 -53
  97. package/lib/vendor/blamejs/lib/guard-image.js +24 -5
  98. package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
  99. package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
  100. package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
  101. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
  102. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
  103. package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
  104. package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
  105. package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
  106. package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
  107. package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
  108. package/lib/vendor/blamejs/lib/guard-time.js +1 -1
  109. package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
  110. package/lib/vendor/blamejs/lib/html-balance.js +3 -3
  111. package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
  112. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
  113. package/lib/vendor/blamejs/lib/http-client.js +39 -7
  114. package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
  115. package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
  116. package/lib/vendor/blamejs/lib/i18n.js +35 -21
  117. package/lib/vendor/blamejs/lib/incident-report.js +8 -1
  118. package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
  119. package/lib/vendor/blamejs/lib/json-path.js +23 -1
  120. package/lib/vendor/blamejs/lib/jwk.js +20 -3
  121. package/lib/vendor/blamejs/lib/link-header.js +2 -1
  122. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
  123. package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
  124. package/lib/vendor/blamejs/lib/log-stream.js +8 -3
  125. package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
  126. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  127. package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
  128. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
  129. package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
  130. package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
  131. package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
  132. package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
  133. package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
  134. package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
  135. package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
  136. package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
  137. package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
  138. package/lib/vendor/blamejs/lib/mail.js +15 -5
  139. package/lib/vendor/blamejs/lib/metrics.js +268 -63
  140. package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
  141. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
  142. package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
  143. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
  144. package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
  145. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
  146. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
  147. package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
  148. package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
  149. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
  150. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
  151. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
  152. package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
  153. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
  154. package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
  155. package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
  156. package/lib/vendor/blamejs/lib/network-dns.js +21 -2
  157. package/lib/vendor/blamejs/lib/network-tls.js +69 -7
  158. package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
  160. package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
  161. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
  162. package/lib/vendor/blamejs/lib/outbox.js +1 -1
  163. package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
  164. package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
  165. package/lib/vendor/blamejs/lib/permissions.js +6 -3
  166. package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
  167. package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
  168. package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
  169. package/lib/vendor/blamejs/lib/pubsub.js +20 -9
  170. package/lib/vendor/blamejs/lib/queue-local.js +8 -1
  171. package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
  172. package/lib/vendor/blamejs/lib/redact.js +54 -0
  173. package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
  174. package/lib/vendor/blamejs/lib/restore.js +19 -0
  175. package/lib/vendor/blamejs/lib/retention.js +12 -4
  176. package/lib/vendor/blamejs/lib/router.js +60 -6
  177. package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
  178. package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
  179. package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
  180. package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
  181. package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
  182. package/lib/vendor/blamejs/lib/safe-object.js +80 -0
  183. package/lib/vendor/blamejs/lib/safe-path.js +52 -4
  184. package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
  185. package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
  186. package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
  187. package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
  188. package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
  189. package/lib/vendor/blamejs/lib/scheduler.js +8 -1
  190. package/lib/vendor/blamejs/lib/security-assert.js +6 -6
  191. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
  192. package/lib/vendor/blamejs/lib/self-update.js +517 -87
  193. package/lib/vendor/blamejs/lib/server-timing.js +2 -4
  194. package/lib/vendor/blamejs/lib/session.js +34 -12
  195. package/lib/vendor/blamejs/lib/sql.js +15 -1
  196. package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
  197. package/lib/vendor/blamejs/lib/static.js +55 -13
  198. package/lib/vendor/blamejs/lib/storage.js +8 -2
  199. package/lib/vendor/blamejs/lib/subject.js +6 -1
  200. package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
  201. package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
  202. package/lib/vendor/blamejs/lib/tsa.js +34 -13
  203. package/lib/vendor/blamejs/lib/uri-template.js +8 -1
  204. package/lib/vendor/blamejs/lib/vault/index.js +13 -0
  205. package/lib/vendor/blamejs/lib/vc.js +44 -5
  206. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
  207. package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
  208. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
  209. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
  210. package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
  211. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
  212. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
  213. package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
  214. package/lib/vendor/blamejs/lib/watcher.js +89 -17
  215. package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
  216. package/lib/vendor/blamejs/lib/webhook.js +12 -2
  217. package/lib/vendor/blamejs/lib/ws-client.js +32 -1
  218. package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
  219. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
  220. package/lib/vendor/blamejs/package-lock.json +2 -2
  221. package/lib/vendor/blamejs/package.json +1 -1
  222. package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
  223. package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
  224. package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
  225. package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
  226. package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
  227. package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
  228. package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
  229. package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
  230. package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
  231. package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
  232. package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
  233. package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
  234. package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
  235. package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
  236. package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
  237. package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
  238. package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
  239. package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
  240. package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
  241. package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
  242. package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
  243. package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
  244. package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
  245. package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
  246. package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
  247. package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
  248. package/lib/vendor/blamejs/socket.yml +94 -0
  249. package/lib/vendor/blamejs/test/00-primitives.js +605 -0
  250. package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
  251. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
  252. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
  253. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
  254. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
  255. package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
  256. package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
  257. package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
  258. package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
  259. package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
  260. package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
  263. package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
  269. package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
  271. package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
  283. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
  285. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
  294. package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
  295. package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
  303. package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
  308. package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
  315. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
  317. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
  318. package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
  319. package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
  320. package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
  321. package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
  322. package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
  323. package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
  324. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
  325. package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
  326. package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
  327. package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
  328. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
  329. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
  330. package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
  331. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
  332. package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
  333. package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
  334. package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
  335. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
  336. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
  337. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
  338. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
  339. package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
  340. package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
  341. package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
  342. package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
  343. package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
  344. package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
  345. package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
  346. package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
  347. package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
  348. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
  349. package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
  350. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
  351. package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
  352. package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
  353. package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
  354. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
  355. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
  356. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
  357. package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
  358. package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
  359. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
  360. package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
  361. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
  362. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
  363. package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
  364. package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
  365. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
  366. package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
  367. package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
  368. package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
  369. package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
  370. package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
  371. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
  372. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
  373. package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
  374. package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
  375. package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
  376. package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
  377. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
  378. package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
  379. package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
  380. package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
  381. package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
  382. package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
  383. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
  384. package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
  385. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
  386. package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
  387. package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
  388. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
  389. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
  390. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
  391. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
  392. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
  393. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
  394. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
  395. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
  396. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
  397. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
  398. package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
  399. package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
  400. package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
  401. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
  402. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
  403. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
  404. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
  405. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
  406. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
  407. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
  408. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
  409. package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
  410. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
  411. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
  412. package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
  413. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
  414. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
  415. package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
  416. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
  417. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
  418. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
  419. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
  420. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
  421. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
  422. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
  423. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
  424. package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
  425. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
  426. package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
  427. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
  428. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
  429. package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
  430. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
  431. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
  432. package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
  433. package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
  434. package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
  435. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
  436. package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
  437. package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
  438. package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
  439. package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
  440. package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
  441. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
  442. package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
  443. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
  444. package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
  445. package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
  446. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
  447. package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
  448. package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
  449. package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
  450. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
  451. package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
  452. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
  453. package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
  454. package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
  455. package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
  456. package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
  457. package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
  458. package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
  459. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
  460. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
  461. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
  462. package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
  463. package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
  464. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
  465. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
  466. package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
  467. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
  468. package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
  469. package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
  470. package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
  471. package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
  472. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
  473. package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
  474. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
  475. package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
  476. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
  477. package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
  478. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
  479. package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
  480. package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
  481. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
  482. package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
  483. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
  484. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
  485. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
  486. package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
  487. package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
  488. package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
  489. package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
  490. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
  491. package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
  492. package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
  493. package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
  494. package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
  495. package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
  496. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
  497. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
  498. package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
  499. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
  500. package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
  501. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
  502. package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
  503. package/lib/vendor/blamejs/test/smoke.js +2 -0
  504. package/package.json +1 -1
  505. package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
  506. package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
  507. package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
  508. package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
  509. package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
  510. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
  511. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
  512. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
  513. package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
  514. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
  515. package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
  516. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
  517. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
  518. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
  519. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
  520. package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
  521. package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
  522. package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
@@ -0,0 +1,1809 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * b.auth.saml.sp — validation, adversarial, and error-branch suite for the
6
+ * SAML 2.0 SP primitive.
7
+ *
8
+ * The sibling SAML suites cover the SubjectConfirmation NotBefore /
9
+ * NotOnOrAfter fail-closed windows, the SLO HTTP-Redirect signing round-trip,
10
+ * and the MDQ signature-wrapping defense. This file exercises the remaining
11
+ * unit-testable branches driven through the shipped consumer path
12
+ * (b.auth.saml.sp.create + the returned SP methods, b.auth.saml.fetchMdq):
13
+ *
14
+ * - create() config-time validation (missing required fields, unknown opt,
15
+ * bad clockSkew) — each throws through the AuthError codes it advertises;
16
+ * - buildAuthnRequest / metadata XML-attribute escaping (RFC 3741) so a
17
+ * hostile ACS / entityId / nameIdFormat cannot break out of its context;
18
+ * - verifyResponse pre-signature structural refusals: non-string / non-XML
19
+ * input, wrong root, non-Success Status, the XSW duplicate-element shapes
20
+ * (Status / StatusCode / Assertion / EncryptedAssertion), the unsigned
21
+ * refusal, and EncryptedAssertion-without-SP-key;
22
+ * - verifyResponse signed-path refusals that fire only after XMLDSig
23
+ * verification succeeds (wrong Issuer, audience-confusion, expired
24
+ * Conditions, the duplicate-Subject XSW), plus the happy path returning
25
+ * nameId / sessionIndex / attributes;
26
+ * - the SLO redirect + HTTP-POST + SOAP build/parse validation surface.
27
+ *
28
+ * It also drives the deeper adversarial surface: the _verifyXmldsig and
29
+ * embedded-XMLDSig structural refusals, Response-level signatures and the
30
+ * signed-different-element wrapping guard, the Bearer and holder-of-key
31
+ * SubjectConfirmation fail-closed paths, EncryptedAssertion decryption
32
+ * (RSA-OAEP + AES-256-GCM plus the ML-KEM-1024 / XChaCha20-Poly1305 paths),
33
+ * the SLO classical + Ed25519 signing round trips, and the fetchMdq
34
+ * transport / verification branches.
35
+ *
36
+ * Signed fixtures mint a self-signed RSA IdP cert and compute the digest +
37
+ * SignatureValue through the framework's own b.xmlC14n so verifyResponse's
38
+ * recomputation matches exactly — there is no test bypass of the signature
39
+ * check (a wrong-key negative control proves the signature gate is live).
40
+ */
41
+
42
+ var helpers = require("../helpers");
43
+ var check = helpers.check;
44
+ var b = helpers.b;
45
+ var nodeCrypto = require("node:crypto");
46
+ var zlib = require("node:zlib");
47
+ var c14n = require("../../lib/xml-c14n");
48
+ var pq = require("../../lib/pqc-software");
49
+
50
+ var C = b.constants;
51
+
52
+ var DS = "http://www.w3.org/2000/09/xmldsig#";
53
+ var EXC = "http://www.w3.org/2001/10/xml-exc-c14n#";
54
+ var SAML_A = "urn:oasis:names:tc:SAML:2.0:assertion";
55
+ var SAML_P = "urn:oasis:names:tc:SAML:2.0:protocol";
56
+ var XENC = "http://www.w3.org/2001/04/xmlenc#";
57
+
58
+ var IDP_ENTITY_ID = "https://idp.example";
59
+ var SP_ENTITY_ID = "https://sp.example";
60
+ var ACS_URL = "https://sp.example/saml/acs";
61
+ var IDP_SSO_URL = "https://idp.example/sso";
62
+ var IDP_SLO_URL = "https://idp.example/slo";
63
+ var FAKE_CERT = "-----BEGIN CERTIFICATE-----\nx\n-----END CERTIFICATE-----";
64
+
65
+ var BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer";
66
+ var SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";
67
+ var HOK = "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key";
68
+ var EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
69
+ var RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
70
+ var ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
71
+ var MLDSA65_URN = "urn:blamejs:experimental:saml-sig-alg:ml-dsa-65";
72
+ var SHA3_512 = "http://www.w3.org/2007/05/xmldsig-more#sha3-512";
73
+ var XCHACHA_URN = "urn:blamejs:experimental:xmlenc:xchacha20-poly1305";
74
+
75
+ function iso(ms) { return new Date(Date.now() + ms).toISOString(); }
76
+ function b64(xml) { return Buffer.from(xml, "utf8").toString("base64"); }
77
+
78
+ // Mint a self-signed RSA cert via the vendored @peculiar/x509 bundle — the
79
+ // same shape the SubjectConfirmation / MDQ suites use. verifyResponse parses
80
+ // idpCertPem with nodeCrypto.createPublicKey and verifies an rsa-sha256 PKCS1
81
+ // signature, so a real RSA keypair is required.
82
+ async function _mintRsaCert(cn) {
83
+ var pki = require("../../lib/vendor/pki.cjs");
84
+ var x509 = pki.x509;
85
+ var keys = await nodeCrypto.webcrypto.subtle.generateKey(
86
+ { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
87
+ publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
88
+ true, ["sign", "verify"]);
89
+ var now = new Date();
90
+ var cert = await x509.X509CertificateGenerator.createSelfSigned({
91
+ serialNumber: "01",
92
+ name: "CN=" + cn,
93
+ notBefore: now,
94
+ notAfter: new Date(now.getTime() + 365 * 24 * 3600 * 1000), // allow:raw-time-literal — 1y fixture validity
95
+ signingAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
96
+ keys: keys,
97
+ });
98
+ var pkcs8 = await nodeCrypto.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
99
+ var keyPem = "-----BEGIN PRIVATE KEY-----\n" +
100
+ Buffer.from(pkcs8).toString("base64").match(/.{1,64}/g).join("\n") +
101
+ "\n-----END PRIVATE KEY-----\n";
102
+ return { certPem: cert.toString("pem"), keyPem: keyPem };
103
+ }
104
+
105
+ // An SP with a placeholder cert — for branches that throw BEFORE any real
106
+ // signature verification (all the input-validation / XSW-structural refusals),
107
+ // where idpCertPem is stored but never parsed.
108
+ function _fakeSp(extra) {
109
+ var opts = {
110
+ entityId: SP_ENTITY_ID,
111
+ assertionConsumerServiceUrl: ACS_URL,
112
+ idpEntityId: IDP_ENTITY_ID,
113
+ idpSsoUrl: IDP_SSO_URL,
114
+ idpSloUrl: IDP_SLO_URL,
115
+ idpCertPem: FAKE_CERT,
116
+ };
117
+ if (extra) { for (var k in extra) { opts[k] = extra[k]; } }
118
+ return b.auth.saml.sp.create(opts);
119
+ }
120
+
121
+ // An SP whose trust anchor is a minted RSA cert — for the signed-path branches.
122
+ function _realSp(idp, extra) {
123
+ var opts = {
124
+ entityId: SP_ENTITY_ID,
125
+ assertionConsumerServiceUrl: ACS_URL,
126
+ idpEntityId: IDP_ENTITY_ID,
127
+ idpSsoUrl: IDP_SSO_URL,
128
+ idpCertPem: idp.certPem,
129
+ };
130
+ if (extra) { for (var k in extra) { opts[k] = extra[k]; } }
131
+ return b.auth.saml.sp.create(opts);
132
+ }
133
+
134
+ function _codeOf(fn) {
135
+ try { fn(); return "NO-THROW"; }
136
+ catch (e) { return e.code || e.message; }
137
+ }
138
+ function _verifyCode(sp, xmlB64, vopts) {
139
+ return _codeOf(function () { sp.verifyResponse(xmlB64, vopts || {}); });
140
+ }
141
+
142
+ // Build a SAML Response with an Assertion-level enveloped XMLDSig signature.
143
+ // The Assertion's Signature child is stripped before the digest (the
144
+ // enveloped-signature transform); both the digest and the SignedInfo are
145
+ // canonicalized through b.xmlC14n so the verifier's recomputation matches.
146
+ // `o` overrides: issuer, subjectXml, conditions, audience, attrStmt, scd.
147
+ function _buildSignedResponse(idp, o) {
148
+ o = o || {};
149
+ var assertionId = "_assertion-" + o.tag;
150
+ var responseId = "_response-" + o.tag;
151
+ var issueInstant = iso(0);
152
+
153
+ var issuer = o.issuer !== undefined ? o.issuer : IDP_ENTITY_ID;
154
+ var issuerXml = issuer === null ? "" : "<saml:Issuer>" + issuer + "</saml:Issuer>";
155
+
156
+ var scd = o.scd !== undefined ? o.scd :
157
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(5 * 60 * 1000) + "\"" + // allow:raw-time-literal — 5m otherwise-valid window
158
+ " Recipient=\"" + ACS_URL + "\"" +
159
+ (o.inResponseTo ? " InResponseTo=\"" + o.inResponseTo + "\"" : "") + "/>";
160
+ var subjectXml = o.subjectXml !== undefined ? o.subjectXml :
161
+ "<saml:Subject>" +
162
+ "<saml:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">" +
163
+ (o.nameId || "alice@example.com") + "</saml:NameID>" +
164
+ "<saml:SubjectConfirmation Method=\"" + BEARER + "\">" + scd +
165
+ "</saml:SubjectConfirmation></saml:Subject>";
166
+
167
+ var conditions = o.conditions !== undefined ? o.conditions :
168
+ "<saml:Conditions NotBefore=\"" + iso(-5 * 60 * 1000) + // allow:raw-time-literal — 5m skew window
169
+ "\" NotOnOrAfter=\"" + iso(5 * 60 * 1000) + "\">" + // allow:raw-time-literal — 5m skew window
170
+ "<saml:AudienceRestriction><saml:Audience>" + (o.audience || SP_ENTITY_ID) +
171
+ "</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
172
+
173
+ var authnStmt = "<saml:AuthnStatement SessionIndex=\"_sess-1\" AuthnInstant=\"" + issueInstant +
174
+ "\"><saml:AuthnContext><saml:AuthnContextClassRef>" +
175
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" +
176
+ "</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
177
+ var attrStmt = o.attrStmt || "";
178
+
179
+ var open = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " +
180
+ "ID=\"" + assertionId + "\" Version=\"2.0\" IssueInstant=\"" + issueInstant + "\">";
181
+ var close = "</saml:Assertion>";
182
+ var inner = issuerXml + "SIGNATURE_PLACEHOLDER" + subjectXml + conditions + authnStmt + attrStmt;
183
+
184
+ var noSig = open + inner.replace("SIGNATURE_PLACEHOLDER", "") + close;
185
+ var digest = nodeCrypto.createHash("sha256").update(c14n.canonicalize(noSig)).digest("base64");
186
+
187
+ var signedInfo =
188
+ "<ds:SignedInfo xmlns:ds=\"" + DS + "\">" +
189
+ "<ds:CanonicalizationMethod Algorithm=\"" + EXC + "\"></ds:CanonicalizationMethod>" +
190
+ "<ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"></ds:SignatureMethod>" +
191
+ "<ds:Reference URI=\"#" + assertionId + "\">" +
192
+ "<ds:Transforms>" +
193
+ "<ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></ds:Transform>" +
194
+ "<ds:Transform Algorithm=\"" + EXC + "\"></ds:Transform>" +
195
+ "</ds:Transforms>" +
196
+ "<ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod>" +
197
+ "<ds:DigestValue>" + digest + "</ds:DigestValue>" +
198
+ "</ds:Reference></ds:SignedInfo>";
199
+ var priv = nodeCrypto.createPrivateKey({ key: idp.keyPem, format: "pem" });
200
+ var sigValue = nodeCrypto.sign("sha256", c14n.canonicalize(signedInfo),
201
+ { key: priv, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }).toString("base64");
202
+ var signatureXml = "<ds:Signature xmlns:ds=\"" + DS + "\">" + signedInfo +
203
+ "<ds:SignatureValue>" + sigValue + "</ds:SignatureValue></ds:Signature>";
204
+
205
+ var assertionFull = open + inner.replace("SIGNATURE_PLACEHOLDER", signatureXml) + close;
206
+ var response =
207
+ "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " +
208
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " +
209
+ "ID=\"" + responseId + "\" Version=\"2.0\" IssueInstant=\"" + issueInstant + "\" " +
210
+ "Destination=\"" + ACS_URL + "\">" +
211
+ "<saml:Issuer>" + IDP_ENTITY_ID + "</saml:Issuer>" +
212
+ "<samlp:Status><samlp:StatusCode Value=\"" + SUCCESS + "\"/></samlp:Status>" +
213
+ assertionFull + "</samlp:Response>";
214
+ return b64(response);
215
+ }
216
+
217
+ // A bare (unsigned) Response envelope for the pre-signature structural cases.
218
+ var P_NS = "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " +
219
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"";
220
+ var STATUS_OK = "<samlp:Status><samlp:StatusCode Value=\"" + SUCCESS + "\"/></samlp:Status>";
221
+ function _response(inner) { return "<samlp:Response " + P_NS + " ID=\"_r\">" + inner + "</samlp:Response>"; }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // create() config-time validation
225
+ // ---------------------------------------------------------------------------
226
+
227
+ function testCreateRequiredFields() {
228
+ var base = {
229
+ entityId: SP_ENTITY_ID, assertionConsumerServiceUrl: ACS_URL,
230
+ idpEntityId: IDP_ENTITY_ID, idpSsoUrl: IDP_SSO_URL, idpCertPem: FAKE_CERT,
231
+ };
232
+ function omit(k) { var c = Object.assign({}, base); delete c[k]; return _codeOf(function () { b.auth.saml.sp.create(c); }); }
233
+ check("create: missing entityId → no-entity-id", omit("entityId") === "auth-saml/no-entity-id");
234
+ check("create: missing ACS → no-acs", omit("assertionConsumerServiceUrl") === "auth-saml/no-acs");
235
+ check("create: missing idpEntityId → no-idp-entity-id", omit("idpEntityId") === "auth-saml/no-idp-entity-id");
236
+ check("create: missing idpSsoUrl → no-idp-sso", omit("idpSsoUrl") === "auth-saml/no-idp-sso");
237
+ check("create: missing idpCertPem → no-idp-cert", omit("idpCertPem") === "auth-saml/no-idp-cert");
238
+ }
239
+
240
+ function testCreateRejectsBadOpts() {
241
+ var base = {
242
+ entityId: SP_ENTITY_ID, assertionConsumerServiceUrl: ACS_URL,
243
+ idpEntityId: IDP_ENTITY_ID, idpSsoUrl: IDP_SSO_URL, idpCertPem: FAKE_CERT,
244
+ };
245
+ function withOpt(o) { return _codeOf(function () { b.auth.saml.sp.create(Object.assign({}, base, o)); }); }
246
+
247
+ var unknown = null;
248
+ try { b.auth.saml.sp.create(Object.assign({}, base, { bogusKey: 1 })); }
249
+ catch (e) { unknown = e; }
250
+ check("create: unknown opt is refused", unknown !== null && unknown.code === "BAD_OPT");
251
+ check("create: unknown-opt message names the key", unknown && /bogusKey/.test(unknown.message));
252
+
253
+ // clockSkewSec must be a finite, non-negative number — a negative, Infinity,
254
+ // or string value is refused at config time (an Infinity that slipped
255
+ // through would disable the freshness windows downstream).
256
+ check("create: negative clockSkewSec refused", withOpt({ clockSkewSec: -1 }) === "BAD_OPT");
257
+ check("create: Infinity clockSkewSec refused", withOpt({ clockSkewSec: Infinity }) === "BAD_OPT");
258
+ check("create: string clockSkewSec refused", withOpt({ clockSkewSec: "60" }) === "BAD_OPT");
259
+
260
+ check("create: non-object opts refused", _codeOf(function () { b.auth.saml.sp.create("nope"); }) === "BAD_OPT");
261
+ check("create: null opts refused", _codeOf(function () { b.auth.saml.sp.create(null); }) === "BAD_OPT");
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // buildAuthnRequest — shape + RFC 3741 XML escaping + redirect assembly
266
+ // ---------------------------------------------------------------------------
267
+
268
+ function testAuthnRequestShapeAndRelayState() {
269
+ var sp = _fakeSp();
270
+ var ar = sp.buildAuthnRequest({ relayState: "/dash&x=1" });
271
+ check("authn: id is an underscore-prefixed token", typeof ar.id === "string" && ar.id.charAt(0) === "_");
272
+ check("authn: redirectUrl starts at the IdP SSO URL", ar.redirectUrl.indexOf(IDP_SSO_URL) === 0);
273
+ check("authn: redirectUrl carries SAMLRequest param", ar.redirectUrl.indexOf("SAMLRequest=") !== -1);
274
+ check("authn: RelayState is URL-encoded",
275
+ ar.redirectUrl.indexOf("RelayState=" + encodeURIComponent("/dash&x=1")) !== -1);
276
+ check("authn: raw is a samlp:AuthnRequest", ar.raw.indexOf("<samlp:AuthnRequest") === 0);
277
+ }
278
+
279
+ function testAuthnRequestNameIdPolicy() {
280
+ var without = _fakeSp().buildAuthnRequest().raw;
281
+ check("authn: no NameIDPolicy when nameIdFormat unset", without.indexOf("NameIDPolicy") === -1);
282
+ var withFmt = _fakeSp({ nameIdFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" })
283
+ .buildAuthnRequest().raw;
284
+ check("authn: NameIDPolicy present when nameIdFormat set", withFmt.indexOf("<samlp:NameIDPolicy") !== -1);
285
+ }
286
+
287
+ function testAuthnRequestQuerySeparator() {
288
+ // When the IdP SSO URL already carries a query, the SAMLRequest param is
289
+ // appended with & (not a second ?), so the redirect stays a valid URL.
290
+ var sp = _fakeSp({ idpSsoUrl: IDP_SSO_URL + "?foo=1" });
291
+ var url = sp.buildAuthnRequest().redirectUrl;
292
+ check("authn: pre-existing query gets &SAMLRequest", url.indexOf("?foo=1&SAMLRequest=") !== -1);
293
+ }
294
+
295
+ function testAuthnRequestAttributeEscaping() {
296
+ // A hostile ACS carrying a quote + angle brackets must be escaped inside the
297
+ // AssertionConsumerServiceURL attribute — it cannot break out of the
298
+ // attribute and inject unsigned markup (RFC 3741 §1.3.2).
299
+ var sp = _fakeSp({
300
+ entityId: "https://sp.example/e<vil",
301
+ assertionConsumerServiceUrl: "https://sp.example/acs\"><evil>&",
302
+ });
303
+ var raw = sp.buildAuthnRequest().raw;
304
+ check("authn: hostile ACS cannot break out of the attribute", raw.indexOf("\"><evil>") === -1);
305
+ check("authn: quote in ACS is escaped to &quot;", raw.indexOf("&quot;") !== -1);
306
+ check("authn: '<' in entityId Issuer text is escaped",
307
+ raw.indexOf("e&lt;vil") !== -1 && raw.indexOf("e<vil") === -1);
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // metadata()
312
+ // ---------------------------------------------------------------------------
313
+
314
+ function testMetadata() {
315
+ var sp = _fakeSp();
316
+ var meta = sp.metadata();
317
+ check("metadata: is an EntityDescriptor", meta.indexOf("<md:EntityDescriptor") !== -1);
318
+ check("metadata: carries the SP entityID", meta.indexOf("entityID=\"" + SP_ENTITY_ID + "\"") !== -1);
319
+ check("metadata: advertises the ACS location", meta.indexOf(ACS_URL) !== -1);
320
+ check("metadata: WantAssertionsSigned is true", meta.indexOf("WantAssertionsSigned=\"true\"") !== -1);
321
+ check("metadata: no SingleLogoutService when unconfigured", meta.indexOf("SingleLogoutService") === -1);
322
+ }
323
+
324
+ function testMetadataSlo() {
325
+ var configured = _fakeSp({ singleLogoutServiceUrl: "https://sp.example/slo" }).metadata();
326
+ check("metadata: SLO service emitted when configured on create",
327
+ configured.indexOf("SingleLogoutService") !== -1 && configured.indexOf("https://sp.example/slo") !== -1);
328
+ var override = _fakeSp().metadata({ singleLogoutServiceUrl: "https://sp.example/slo2" });
329
+ check("metadata: metaOpts singleLogoutServiceUrl override honored",
330
+ override.indexOf("https://sp.example/slo2") !== -1);
331
+ // Hostile SLO URL is attribute-escaped.
332
+ var hostile = _fakeSp().metadata({ singleLogoutServiceUrl: "https://sp.example/x\"><evil>" });
333
+ check("metadata: hostile SLO URL cannot break out of the attribute",
334
+ hostile.indexOf("\"><evil>") === -1 && hostile.indexOf("&quot;") !== -1);
335
+ }
336
+
337
+ // ---------------------------------------------------------------------------
338
+ // verifyResponse — pre-signature input validation + XSW structural refusals
339
+ // ---------------------------------------------------------------------------
340
+
341
+ function testVerifyResponseInputValidation() {
342
+ var sp = _fakeSp();
343
+ check("verify: non-string SAMLResponse → no-response",
344
+ _verifyCode(sp, 12345) === "auth-saml/no-response");
345
+ check("verify: empty SAMLResponse → no-response",
346
+ _verifyCode(sp, "") === "auth-saml/no-response");
347
+ check("verify: base64 that decodes to non-XML → bad-response-decode",
348
+ _verifyCode(sp, b64("no-angle-brackets-here")) === "auth-saml/bad-response-decode");
349
+ check("verify: wrong root element → wrong-root",
350
+ _verifyCode(sp, b64("<samlp:Foo " + P_NS + "/>")) === "auth-saml/wrong-root");
351
+ }
352
+
353
+ function testVerifyResponseStatus() {
354
+ var sp = _fakeSp();
355
+ var reqStatus = "<samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\"/></samlp:Status>";
356
+ check("verify: non-Success Status → bad-status",
357
+ _verifyCode(sp, b64(_response(reqStatus))) === "auth-saml/bad-status");
358
+ check("verify: absent Status → bad-status",
359
+ _verifyCode(sp, b64(_response(""))) === "auth-saml/bad-status");
360
+ }
361
+
362
+ function testVerifyResponseXswStructural() {
363
+ var sp = _fakeSp();
364
+ check("verify: duplicate <Status> → duplicate-status (XSW)",
365
+ _verifyCode(sp, b64(_response(STATUS_OK + STATUS_OK))) === "auth-saml/duplicate-status");
366
+ check("verify: duplicate <StatusCode> → duplicate-status-code (XSW)",
367
+ _verifyCode(sp, b64(_response(
368
+ "<samlp:Status><samlp:StatusCode Value=\"" + SUCCESS + "\"/><samlp:StatusCode Value=\"x\"/></samlp:Status>"
369
+ ))) === "auth-saml/duplicate-status-code");
370
+ check("verify: duplicate <Assertion> → duplicate-assertion (XSW)",
371
+ _verifyCode(sp, b64(_response(STATUS_OK + "<saml:Assertion ID=\"_a1\"/><saml:Assertion ID=\"_a2\"/>")))
372
+ === "auth-saml/duplicate-assertion");
373
+ check("verify: duplicate <EncryptedAssertion> → duplicate-encrypted-assertion (XSW)",
374
+ _verifyCode(sp, b64(_response(STATUS_OK + "<saml:EncryptedAssertion/><saml:EncryptedAssertion/>")))
375
+ === "auth-saml/duplicate-encrypted-assertion");
376
+ }
377
+
378
+ function testVerifyResponseNoAssertionAndUnsigned() {
379
+ var sp = _fakeSp();
380
+ check("verify: Success but no Assertion → no-assertion",
381
+ _verifyCode(sp, b64(_response(STATUS_OK))) === "auth-saml/no-assertion");
382
+ check("verify: single Assertion with no Signature → unsigned",
383
+ _verifyCode(sp, b64(_response(STATUS_OK +
384
+ "<saml:Assertion ID=\"_a1\"><saml:Issuer>" + IDP_ENTITY_ID + "</saml:Issuer></saml:Assertion>")))
385
+ === "auth-saml/unsigned");
386
+ }
387
+
388
+ function testVerifyResponseEncryptedWithoutKey() {
389
+ var sp = _fakeSp();
390
+ check("verify: EncryptedAssertion without spPrivateKeyPem → encrypted-no-sp-key",
391
+ _verifyCode(sp, b64(_response(STATUS_OK +
392
+ "<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\"/></saml:EncryptedAssertion>")))
393
+ === "auth-saml/encrypted-no-sp-key");
394
+ }
395
+
396
+ // ---------------------------------------------------------------------------
397
+ // verifyResponse — signed-path branches (fire only after XMLDSig verify)
398
+ // ---------------------------------------------------------------------------
399
+
400
+ async function testVerifyResponseHappyPath() {
401
+ var idp = await _mintRsaCert("idp.example");
402
+ var sp = _realSp(idp);
403
+ var attrStmt =
404
+ "<saml:AttributeStatement>" +
405
+ "<saml:Attribute Name=\"email\"><saml:AttributeValue>alice@example.com</saml:AttributeValue></saml:Attribute>" +
406
+ "<saml:Attribute Name=\"roles\">" +
407
+ "<saml:AttributeValue>admin</saml:AttributeValue><saml:AttributeValue>ops</saml:AttributeValue></saml:Attribute>" +
408
+ "</saml:AttributeStatement>";
409
+ var info = sp.verifyResponse(_buildSignedResponse(idp, { tag: "happy", attrStmt: attrStmt }));
410
+ check("verify happy: nameId returned", info.nameId === "alice@example.com");
411
+ check("verify happy: nameIdFormat returned",
412
+ info.nameIdFormat === "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
413
+ check("verify happy: sessionIndex from AuthnStatement", info.sessionIndex === "_sess-1");
414
+ check("verify happy: issuer echoed", info.issuer === IDP_ENTITY_ID);
415
+ check("verify happy: single-valued attribute is a scalar", info.attributes.email === "alice@example.com");
416
+ check("verify happy: multi-valued attribute is an array",
417
+ Array.isArray(info.attributes.roles) && info.attributes.roles.join(",") === "admin,ops");
418
+ check("verify happy: audience defaults to entityId", info.audience === SP_ENTITY_ID);
419
+ }
420
+
421
+ async function testVerifyResponseInResponseTo() {
422
+ var idp = await _mintRsaCert("idp.example");
423
+ var sp = _realSp(idp);
424
+ var rt = "_req-abc";
425
+ var okB64 = _buildSignedResponse(idp, { tag: "irt-ok", inResponseTo: rt });
426
+ var info = sp.verifyResponse(okB64, { expectedInResponseTo: rt });
427
+ check("verify: matching InResponseTo is captured", info.inResponseTo === rt);
428
+ var mismatchCode = _verifyCode(sp,
429
+ _buildSignedResponse(idp, { tag: "irt-bad", inResponseTo: "_other" }),
430
+ { expectedInResponseTo: rt });
431
+ check("verify: InResponseTo mismatch → bad-in-response-to (replay defense)",
432
+ mismatchCode === "auth-saml/bad-in-response-to");
433
+ }
434
+
435
+ async function testVerifyResponseWrongIssuer() {
436
+ var idp = await _mintRsaCert("idp.example");
437
+ var sp = _realSp(idp);
438
+ check("verify: Issuer != configured idpEntityId → wrong-issuer",
439
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "wi", issuer: "https://evil.example" }))
440
+ === "auth-saml/wrong-issuer");
441
+ }
442
+
443
+ async function testVerifyResponseAudience() {
444
+ var idp = await _mintRsaCert("idp.example");
445
+ var sp = _realSp(idp);
446
+ var noRestriction = "<saml:Conditions NotBefore=\"" + iso(-5 * 60 * 1000) + // allow:raw-time-literal — 5m skew window
447
+ "\" NotOnOrAfter=\"" + iso(5 * 60 * 1000) + "\"></saml:Conditions>"; // allow:raw-time-literal — 5m skew window
448
+ check("verify: no AudienceRestriction → no-audience-restriction (audience-confusion defense)",
449
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "na", conditions: noRestriction }))
450
+ === "auth-saml/no-audience-restriction");
451
+ check("verify: AudienceRestriction for a different SP → wrong-audience",
452
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "wa", audience: "https://other.example" }))
453
+ === "auth-saml/wrong-audience");
454
+
455
+ // requireAudienceRestriction:false opts out of the binding requirement.
456
+ var info = sp.verifyResponse(
457
+ _buildSignedResponse(idp, { tag: "opt", conditions: noRestriction }),
458
+ { requireAudienceRestriction: false });
459
+ check("verify: requireAudienceRestriction:false accepts a missing binding", info.nameId === "alice@example.com");
460
+ }
461
+
462
+ async function testVerifyResponseConditionsExpired() {
463
+ var idp = await _mintRsaCert("idp.example");
464
+ var sp = _realSp(idp);
465
+ var expired = "<saml:Conditions NotOnOrAfter=\"" + iso(-60 * 60 * 1000) + "\">" + // allow:raw-time-literal — 1h in the past
466
+ "<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID +
467
+ "</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
468
+ check("verify: expired Conditions/NotOnOrAfter → conditions-expired",
469
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "ce", conditions: expired }))
470
+ === "auth-saml/conditions-expired");
471
+ var future = "<saml:Conditions NotBefore=\"" + iso(60 * 60 * 1000) + "\">" + // allow:raw-time-literal — 1h in the future
472
+ "<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID +
473
+ "</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
474
+ check("verify: future Conditions/NotBefore → conditions-not-yet-valid",
475
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "cf", conditions: future }))
476
+ === "auth-saml/conditions-not-yet-valid");
477
+ }
478
+
479
+ async function testVerifyResponseDuplicateSubjectXsw() {
480
+ var idp = await _mintRsaCert("idp.example");
481
+ var sp = _realSp(idp);
482
+ var twoSubjects =
483
+ "<saml:Subject>" +
484
+ "<saml:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">alice@example.com</saml:NameID>" +
485
+ "<saml:SubjectConfirmation Method=\"" + BEARER + "\">" +
486
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(5 * 60 * 1000) + // allow:raw-time-literal — 5m window
487
+ "\" Recipient=\"" + ACS_URL + "\"/></saml:SubjectConfirmation></saml:Subject>" +
488
+ "<saml:Subject><saml:NameID>attacker@evil.example</saml:NameID></saml:Subject>";
489
+ check("verify: duplicate <Subject> in a signed Assertion → duplicate-subject (XSW)",
490
+ _verifyCode(sp, _buildSignedResponse(idp, { tag: "ds", subjectXml: twoSubjects }))
491
+ === "auth-saml/duplicate-subject");
492
+ }
493
+
494
+ async function testVerifyResponseTamperedDigest() {
495
+ // A validly-signed assertion whose signed bytes are then mutated must be
496
+ // refused: the recomputed digest no longer matches the signed DigestValue.
497
+ var idp = await _mintRsaCert("idp.example");
498
+ var sp = _realSp(idp);
499
+ var goodB64 = _buildSignedResponse(idp, { tag: "tamper", nameId: "alice@example.com" });
500
+ var xml = Buffer.from(goodB64, "base64").toString("utf8").replace("alice@example.com", "mallory@evil.example");
501
+ var code = _verifyCode(sp, b64(xml));
502
+ check("verify: tampering signed content → digest-mismatch",
503
+ code === "auth-saml/digest-mismatch");
504
+ }
505
+
506
+ // ---------------------------------------------------------------------------
507
+ // SLO — HTTP-Redirect build/parse validation
508
+ // ---------------------------------------------------------------------------
509
+
510
+ function testLogoutRequestValidation() {
511
+ var sp = _fakeSp();
512
+ check("buildLogoutRequest: missing nameId → no-nameid",
513
+ _codeOf(function () { sp.buildLogoutRequest({ sessionIndex: "_s" }); }) === "auth-saml/no-nameid");
514
+ check("buildLogoutRequest: non-object opts → bad-opts",
515
+ _codeOf(function () { sp.buildLogoutRequest("nope"); }) === "auth-saml/bad-opts");
516
+ var unknownCode = _codeOf(function () { sp.buildLogoutRequest({ nameId: "a", bogus: 1 }); });
517
+ check("buildLogoutRequest: unknown opt is refused", /unknown option/.test(unknownCode));
518
+ }
519
+
520
+ function testLogoutResponseValidation() {
521
+ var sp = _fakeSp();
522
+ check("buildLogoutResponse: missing inResponseTo → no-in-response-to",
523
+ _codeOf(function () { sp.buildLogoutResponse({ destination: IDP_SLO_URL }); }) === "auth-saml/no-in-response-to");
524
+ check("buildLogoutResponse: missing destination → no-destination",
525
+ _codeOf(function () { sp.buildLogoutResponse({ inResponseTo: "_x" }); }) === "auth-saml/no-destination");
526
+ }
527
+
528
+ function testParseLogoutRequestValidation() {
529
+ var sp = _fakeSp();
530
+ check("parseLogoutRequest: non-string input → no-saml-request",
531
+ _codeOf(function () { sp.parseLogoutRequest(123); }) === "auth-saml/no-saml-request");
532
+ check("parseLogoutRequest: undeflatable base64 → bad-saml-request",
533
+ _codeOf(function () { sp.parseLogoutRequest(b64("this is not deflate-raw data")); }) === "auth-saml/bad-saml-request");
534
+ }
535
+
536
+ function testLogoutResponseRoundTrip() {
537
+ var sp = _fakeSp();
538
+ // buildLogoutResponse produces the redirect; extract + parse the SAMLResponse.
539
+ var built = sp.buildLogoutResponse({ inResponseTo: "_orig-1", destination: IDP_SLO_URL });
540
+ var samlResp = decodeURIComponent(built.redirectUrl.split("?")[1].split("&")[0].slice("SAMLResponse=".length));
541
+ var parsed = sp.parseLogoutResponse(samlResp, { expectedInResponseTo: "_orig-1" });
542
+ check("parseLogoutResponse: default status → success true", parsed.success === true);
543
+ check("parseLogoutResponse: InResponseTo round-trips", parsed.inResponseTo === "_orig-1");
544
+ check("parseLogoutResponse: issuer is the SP entityId", parsed.issuer === SP_ENTITY_ID);
545
+
546
+ check("parseLogoutResponse: non-string input → no-saml-response",
547
+ _codeOf(function () { sp.parseLogoutResponse(null); }) === "auth-saml/no-saml-response");
548
+ check("parseLogoutResponse: expectedInResponseTo mismatch → inresponseto-mismatch",
549
+ _codeOf(function () { sp.parseLogoutResponse(samlResp, { expectedInResponseTo: "_different" }); })
550
+ === "auth-saml/inresponseto-mismatch");
551
+
552
+ // A non-Success statusCode surfaces success=false.
553
+ var failBuilt = sp.buildLogoutResponse({
554
+ inResponseTo: "_orig-2", destination: IDP_SLO_URL,
555
+ statusCode: "urn:oasis:names:tc:SAML:2.0:status:Requester",
556
+ });
557
+ var failResp = decodeURIComponent(failBuilt.redirectUrl.split("?")[1].split("&")[0].slice("SAMLResponse=".length));
558
+ check("parseLogoutResponse: non-Success statusCode → success false",
559
+ sp.parseLogoutResponse(failResp).success === false);
560
+ }
561
+
562
+ // ---------------------------------------------------------------------------
563
+ // SLO — HTTP-POST + SOAP bindings (embedded XMLDSig)
564
+ // ---------------------------------------------------------------------------
565
+
566
+ function testLogoutRequestPostRoundTrip() {
567
+ var sp = _fakeSp();
568
+ var kp = pq.ml_dsa_65.keygen();
569
+ var post = sp.buildLogoutRequestPost({
570
+ nameId: "alice@idp", sessionIndex: "_s-9", signingKey: kp.secretKey, signingAlg: "ml-dsa-65",
571
+ });
572
+ check("buildLogoutRequestPost: action is the IdP SLO URL", post.action === IDP_SLO_URL);
573
+ check("buildLogoutRequestPost: formHtml auto-submits SAMLRequest",
574
+ post.formHtml.indexOf("name=\"SAMLRequest\"") !== -1 && post.formHtml.indexOf("document.forms[0].submit()") !== -1);
575
+ var parsed = sp.parseLogoutRequestPost(post.samlRequest, {
576
+ idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65",
577
+ });
578
+ check("POST SLO round-trip: nameId recovered", parsed.nameId === "alice@idp");
579
+ check("POST SLO round-trip: sessionIndex recovered", parsed.sessionIndex === "_s-9");
580
+ check("POST SLO round-trip: id recovered", parsed.id === post.id);
581
+
582
+ // Negative control — a different verify key refuses (the embedded XMLDSig
583
+ // gate is live), proving the round-trip pass above is a real verification.
584
+ check("POST SLO: wrong verify key → bad-signature",
585
+ _codeOf(function () {
586
+ sp.parseLogoutRequestPost(post.samlRequest, {
587
+ idpVerifyKey: pq.ml_dsa_65.keygen().publicKey, idpVerifyAlg: "ml-dsa-65",
588
+ });
589
+ }) === "auth-saml/bad-signature");
590
+ }
591
+
592
+ function testParseLogoutRequestPostValidation() {
593
+ var sp = _fakeSp();
594
+ check("parseLogoutRequestPost: non-string input → bad-input",
595
+ _codeOf(function () { sp.parseLogoutRequestPost(42); }) === "auth-saml/bad-input");
596
+ check("parseLogoutRequestPost: wrong root element → wrong-root",
597
+ _codeOf(function () { sp.parseLogoutRequestPost(b64("<samlp:Foo " + P_NS + "/>")); }) === "auth-saml/wrong-root");
598
+ }
599
+
600
+ function testLogoutRequestSoapShape() {
601
+ var sp = _fakeSp();
602
+ var soap = sp.buildLogoutRequestSoap({ nameId: "a@idp" });
603
+ check("buildLogoutRequestSoap: action is the IdP SLO URL", soap.action === IDP_SLO_URL);
604
+ check("buildLogoutRequestSoap: body is a SOAP envelope wrapping the LogoutRequest",
605
+ soap.body.indexOf("soapenv:Envelope") !== -1 &&
606
+ soap.body.indexOf("soapenv:Body") !== -1 &&
607
+ soap.body.indexOf("<samlp:LogoutRequest") !== -1);
608
+ }
609
+
610
+ function testParseLogoutResponseSoap() {
611
+ var sp = _fakeSp();
612
+ // Build a LogoutResponse XML (via buildLogoutResponse.raw) and wrap it in a
613
+ // SOAP envelope, then drive the public parse path.
614
+ var lr = sp.buildLogoutResponse({ inResponseTo: "_orig-soap", destination: IDP_SLO_URL });
615
+ var envelope = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
616
+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
617
+ "<soapenv:Body>" + lr.raw + "</soapenv:Body></soapenv:Envelope>";
618
+ var parsed = sp.parseLogoutResponseSoap(envelope);
619
+ check("parseLogoutResponseSoap: unwraps Body + reports success", parsed.success === true);
620
+ check("parseLogoutResponseSoap: InResponseTo recovered", parsed.inResponseTo === "_orig-soap");
621
+
622
+ check("parseLogoutResponseSoap: non-string input → bad-input",
623
+ _codeOf(function () { sp.parseLogoutResponseSoap(0); }) === "auth-saml/bad-input");
624
+ check("parseLogoutResponseSoap: non-Envelope root → bad-soap",
625
+ _codeOf(function () { sp.parseLogoutResponseSoap("<foo/>"); }) === "auth-saml/bad-soap");
626
+ check("parseLogoutResponseSoap: empty Body → bad-soap",
627
+ _codeOf(function () {
628
+ sp.parseLogoutResponseSoap("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
629
+ "<soapenv:Body></soapenv:Body></soapenv:Envelope>");
630
+ }) === "auth-saml/bad-soap");
631
+ }
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // fetchMdq — synchronous input validation (throws before any network I/O)
635
+ // ---------------------------------------------------------------------------
636
+
637
+ async function testFetchMdqInputValidation() {
638
+ async function mdqCode(o) {
639
+ try { await b.auth.saml.fetchMdq(o); return "NO-THROW"; }
640
+ catch (e) { return e.code || e.message; }
641
+ }
642
+ check("fetchMdq: non-object opts refused", (await mdqCode(null)) === "BAD_OPT");
643
+ check("fetchMdq: missing baseUrl → no-mdq-base", (await mdqCode({ entityId: IDP_ENTITY_ID })) === "auth-saml/no-mdq-base");
644
+ check("fetchMdq: missing entityId → no-mdq-entity", (await mdqCode({ baseUrl: "https://mdq.example" })) === "auth-saml/no-mdq-entity");
645
+ }
646
+
647
+ // ---------------------------------------------------------------------------
648
+ // Signed-fixture builders for the deeper adversarial surface below: each
649
+ // assertion body is assembled ONCE with a signature placeholder, so the
650
+ // signed bytes and the verified bytes are identical; the IdP signature +
651
+ // digest + SignedInfo are computed through the framework's own b.xmlC14n so
652
+ // verifyResponse's recomputation matches with no test bypass of the crypto
653
+ // gate.
654
+ // ---------------------------------------------------------------------------
655
+
656
+ function _certBody(pem) {
657
+ return pem.replace(/-----BEGIN CERTIFICATE-----/, "").replace(/-----END CERTIFICATE-----/, "").replace(/\s+/g, "");
658
+ }
659
+
660
+ // Mint a self-signed cert via the vendored @peculiar/x509 bundle — the same
661
+ // shape the sibling SAML suites use. verifyResponse parses idpCertPem with
662
+ // nodeCrypto.createPublicKey and verifies the assertion signature against it.
663
+ async function _mint(cn, alg) {
664
+ var pki = require("../../lib/vendor/pki.cjs");
665
+ var x509 = pki.x509;
666
+ var genAlg = alg === "ec"
667
+ ? { name: "ECDSA", namedCurve: "P-256" }
668
+ : { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
669
+ publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
670
+ var keys = await nodeCrypto.webcrypto.subtle.generateKey(genAlg, true, ["sign", "verify"]);
671
+ var now = new Date();
672
+ var cert = await x509.X509CertificateGenerator.createSelfSigned({
673
+ serialNumber: "01",
674
+ name: "CN=" + cn,
675
+ notBefore: now,
676
+ notAfter: new Date(now.getTime() + C.TIME.days(365)),
677
+ signingAlgorithm: alg === "ec" ? { name: "ECDSA", hash: "SHA-256" } : { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
678
+ keys: keys,
679
+ });
680
+ var pkcs8 = await nodeCrypto.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
681
+ var keyPem = "-----BEGIN PRIVATE KEY-----\n" +
682
+ Buffer.from(pkcs8).toString("base64").match(/.{1,64}/g).join("\n") +
683
+ "\n-----END PRIVATE KEY-----\n";
684
+ return { certPem: cert.toString("pem"), keyPem: keyPem };
685
+ }
686
+
687
+ function _mkSp(certPem, extra) {
688
+ var opts = {
689
+ entityId: SP_ENTITY_ID,
690
+ assertionConsumerServiceUrl: ACS_URL,
691
+ idpEntityId: IDP_ENTITY_ID,
692
+ idpSsoUrl: IDP_SSO_URL,
693
+ idpSloUrl: IDP_SLO_URL,
694
+ idpCertPem: certPem,
695
+ };
696
+ if (extra) { for (var k in extra) { opts[k] = extra[k]; } }
697
+ return b.auth.saml.sp.create(opts);
698
+ }
699
+
700
+ // Build a <ds:Signature> that VERIFIES: digest over the canonicalized target,
701
+ // SignatureValue over the canonicalized SignedInfo, both through b.xmlC14n.
702
+ function _validSig(keyPem, refId, target, o) {
703
+ o = o || {};
704
+ var wc = !!o.withComments;
705
+ var canonUri = wc ? (EXC + "WithComments") : EXC;
706
+ var refTransform = wc ? (EXC + "WithComments") : EXC;
707
+ var sm = o.sigMethod || RSA_SHA256;
708
+ var hn = o.hashName || "sha256";
709
+ var du = o.digestUri || "http://www.w3.org/2001/04/xmlenc#sha256";
710
+ var dh = o.digestHash || "sha256";
711
+ var dg = nodeCrypto.createHash(dh).update(c14n.canonicalize(target, { withComments: wc })).digest("base64");
712
+ var si = "<ds:SignedInfo xmlns:ds=\"" + DS + "\">" +
713
+ "<ds:CanonicalizationMethod Algorithm=\"" + canonUri + "\"></ds:CanonicalizationMethod>" +
714
+ "<ds:SignatureMethod Algorithm=\"" + sm + "\"></ds:SignatureMethod>" +
715
+ "<ds:Reference URI=\"#" + refId + "\">" +
716
+ "<ds:Transforms>" +
717
+ "<ds:Transform Algorithm=\"" + ENVELOPED + "\"></ds:Transform>" +
718
+ "<ds:Transform Algorithm=\"" + refTransform + "\"></ds:Transform>" +
719
+ "</ds:Transforms>" +
720
+ "<ds:DigestMethod Algorithm=\"" + du + "\"></ds:DigestMethod>" +
721
+ "<ds:DigestValue>" + dg + "</ds:DigestValue>" +
722
+ "</ds:Reference></ds:SignedInfo>";
723
+ var priv = nodeCrypto.createPrivateKey({ key: keyPem, format: "pem" });
724
+ var so = { key: priv };
725
+ if (o.ec) so.dsaEncoding = "der"; else so.padding = nodeCrypto.constants.RSA_PKCS1_PADDING;
726
+ var sv = nodeCrypto.sign(hn, c14n.canonicalize(si, { withComments: wc }), so).toString("base64");
727
+ return "<ds:Signature xmlns:ds=\"" + DS + "\">" + si + "<ds:SignatureValue>" + sv + "</ds:SignatureValue></ds:Signature>";
728
+ }
729
+
730
+ // A structurally-valid-until-the-target-defect <ds:Signature>, used for the
731
+ // _verifyXmldsig refusals that fire BEFORE the digest/signature is checked.
732
+ function _craftSig(o) {
733
+ o = o || {};
734
+ if (o.noSignedInfo) return "<ds:Signature xmlns:ds=\"" + DS + "\"><ds:SignatureValue>AA==</ds:SignatureValue></ds:Signature>";
735
+ var canon = o.canon !== undefined ? o.canon : EXC;
736
+ var parts = "<ds:CanonicalizationMethod Algorithm=\"" + canon + "\"></ds:CanonicalizationMethod>";
737
+ if (!o.omitSigMethod) parts += "<ds:SignatureMethod Algorithm=\"" + (o.sigMethod || RSA_SHA256) + "\"></ds:SignatureMethod>";
738
+ if (!o.omitReference) {
739
+ var refUri = o.refUri !== undefined ? o.refUri : ("#" + o.refId);
740
+ var refInner = "";
741
+ if (o.transforms !== undefined) refInner += o.transforms;
742
+ if (!o.omitDigestMethod) refInner += "<ds:DigestMethod Algorithm=\"" + (o.digestMethod || "http://www.w3.org/2001/04/xmlenc#sha256") + "\"></ds:DigestMethod>";
743
+ var dv = o.digestValue !== undefined ? o.digestValue : "AA==";
744
+ refInner += "<ds:DigestValue>" + dv + "</ds:DigestValue>";
745
+ parts += "<ds:Reference URI=\"" + refUri + "\">" + refInner + "</ds:Reference>";
746
+ }
747
+ return "<ds:Signature xmlns:ds=\"" + DS + "\"><ds:SignedInfo xmlns:ds=\"" + DS + "\">" + parts +
748
+ "</ds:SignedInfo><ds:SignatureValue>AA==</ds:SignatureValue></ds:Signature>";
749
+ }
750
+
751
+ function _defSubject() {
752
+ return "<saml:Subject><saml:NameID Format=\"" + EMAIL + "\">alice@example.com</saml:NameID>" +
753
+ "<saml:SubjectConfirmation Method=\"" + BEARER + "\">" +
754
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>" +
755
+ "</saml:SubjectConfirmation></saml:Subject>";
756
+ }
757
+ function _defCond() {
758
+ return "<saml:Conditions NotBefore=\"" + iso(-C.TIME.minutes(5)) + "\" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\">" +
759
+ "<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID + "</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
760
+ }
761
+ function _defAuthn(ii) {
762
+ return "<saml:AuthnStatement SessionIndex=\"_sess-1\" AuthnInstant=\"" + ii + "\">" +
763
+ "<saml:AuthnContext><saml:AuthnContextClassRef>" +
764
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" +
765
+ "</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
766
+ }
767
+
768
+ // Build a signed Assertion. inner is assembled ONCE with a SIG placeholder so
769
+ // the bytes signed == the bytes verified. Overrides let each test shape the
770
+ // subject / conditions / signature.
771
+ function _buildAssertion(idp, o) {
772
+ o = o || {};
773
+ var ii = iso(0);
774
+ var aid = o.assertionId || ("_assertion-" + (o.tag || "t"));
775
+ var issuer = o.issuer !== undefined ? o.issuer : IDP_ENTITY_ID;
776
+ var issuerXml = issuer === null ? "" : ("<saml:Issuer>" + issuer + "</saml:Issuer>");
777
+ var subjectXml = o.subjectXml !== undefined ? o.subjectXml : _defSubject();
778
+ var conditions = o.conditions !== undefined ? o.conditions : _defCond();
779
+ var authnStmt = o.authnStmt !== undefined ? o.authnStmt : _defAuthn(ii);
780
+ var attrStmt = o.attrStmt || "";
781
+ var decoy = o.decoy || "";
782
+ var open = "<saml:Assertion xmlns:saml=\"" + SAML_A + "\" ID=\"" + aid + "\" Version=\"2.0\" IssueInstant=\"" + ii + "\">";
783
+ var close = "</saml:Assertion>";
784
+ var inner = issuerXml + "SIGPH" + subjectXml + conditions + authnStmt + attrStmt + decoy;
785
+ var noSig = open + inner.replace("SIGPH", "") + close;
786
+ var sigXml;
787
+ if (o.signatureXml !== undefined) {
788
+ sigXml = o.signatureXml;
789
+ } else {
790
+ var refId = o.refId || aid;
791
+ var digestTarget = o.digestTarget !== undefined ? o.digestTarget : noSig;
792
+ sigXml = _validSig((o.signKeyPem || idp.keyPem), refId, digestTarget, o.sigOpts);
793
+ }
794
+ return { full: open + inner.replace("SIGPH", sigXml) + close, assertionId: aid };
795
+ }
796
+
797
+ function _mkAssertionResponse(idp, o) {
798
+ o = o || {};
799
+ var a = _buildAssertion(idp, o);
800
+ var rid = "_response-" + (o.tag || "t");
801
+ var xml = "<samlp:Response xmlns:samlp=\"" + SAML_P + "\" xmlns:saml=\"" + SAML_A + "\" ID=\"" + rid +
802
+ "\" Version=\"2.0\" IssueInstant=\"" + iso(0) + "\" Destination=\"" + ACS_URL + "\">" +
803
+ "<saml:Issuer>" + IDP_ENTITY_ID + "</saml:Issuer>" + STATUS_OK + a.full + "</samlp:Response>";
804
+ return { xml: xml, b64: b64(xml), assertionId: a.assertionId, responseId: rid };
805
+ }
806
+
807
+ // Response-level signature (Assertion carries no Signature of its own).
808
+ function _mkResponseLevel(idp, o) {
809
+ o = o || {};
810
+ var ii = iso(0);
811
+ var rid = "_response-" + (o.tag || "rl");
812
+ var aid = "_assertion-" + (o.tag || "rl");
813
+ var assertion = "<saml:Assertion xmlns:saml=\"" + SAML_A + "\" ID=\"" + aid + "\" Version=\"2.0\" IssueInstant=\"" + ii + "\">" +
814
+ "<saml:Issuer>" + IDP_ENTITY_ID + "</saml:Issuer>" + _defSubject() + _defCond() + _defAuthn(ii) + "</saml:Assertion>";
815
+ var decoy = o.decoy || "";
816
+ var open = "<samlp:Response xmlns:samlp=\"" + SAML_P + "\" xmlns:saml=\"" + SAML_A + "\" ID=\"" + rid +
817
+ "\" Version=\"2.0\" IssueInstant=\"" + ii + "\" Destination=\"" + ACS_URL + "\">";
818
+ var close = "</samlp:Response>";
819
+ var inner = "<saml:Issuer>" + IDP_ENTITY_ID + "</saml:Issuer>" + "RSIGPH" + STATUS_OK + assertion + decoy;
820
+ var noSig = open + inner.replace("RSIGPH", "") + close;
821
+ var refId = o.refId || rid;
822
+ var digestTarget = o.digestTarget !== undefined ? o.digestTarget : noSig;
823
+ var sig = _validSig(idp.keyPem, refId, digestTarget, o.sigOpts);
824
+ var full = open + inner.replace("RSIGPH", sig) + close;
825
+ return { b64: b64(full), responseId: rid, assertionId: aid };
826
+ }
827
+
828
+ // ---------------------------------------------------------------------------
829
+ // _verifyXmldsig — structural refusals via a hostile assertion Signature
830
+ // ---------------------------------------------------------------------------
831
+
832
+ function testVerifyXmldsigStructural(idp) {
833
+ var sp = _mkSp(idp.certPem);
834
+ function code(sigXml, extra) {
835
+ var o = { tag: "vx", signatureXml: sigXml };
836
+ if (extra) { for (var k in extra) o[k] = extra[k]; }
837
+ return _verifyCode(sp, _mkAssertionResponse(idp, o).b64);
838
+ }
839
+ var aid = "_assertion-vx";
840
+ var goodTransforms = "<ds:Transforms><ds:Transform Algorithm=\"" + ENVELOPED + "\"></ds:Transform>" +
841
+ "<ds:Transform Algorithm=\"" + EXC + "\"></ds:Transform></ds:Transforms>";
842
+
843
+ check("vxmldsig: Signature without SignedInfo -> no-signed-info",
844
+ code(_craftSig({ noSignedInfo: true })) === "auth-saml/no-signed-info");
845
+ check("vxmldsig: unsupported CanonicalizationMethod -> unsupported-c14n",
846
+ code(_craftSig({ canon: "http://example/bogus-c14n", refId: aid })) === "auth-saml/unsupported-c14n");
847
+ check("vxmldsig: unsupported SignatureMethod -> unsupported-sig-alg",
848
+ code(_craftSig({ sigMethod: "http://example/bogus-sig", refId: aid })) === "auth-saml/unsupported-sig-alg");
849
+ check("vxmldsig: SignedInfo without Reference -> no-reference",
850
+ code(_craftSig({ omitReference: true })) === "auth-saml/no-reference");
851
+ check("vxmldsig: non-fragment Reference URI -> external-reference",
852
+ code(_craftSig({ refUri: "https://evil.example/x" })) === "auth-saml/external-reference");
853
+ check("vxmldsig: unsupported DigestMethod -> unsupported-digest",
854
+ code(_craftSig({ refId: aid, digestMethod: "http://example/bogus-digest" })) === "auth-saml/unsupported-digest");
855
+ check("vxmldsig: empty DigestValue -> no-digest-value",
856
+ code(_craftSig({ refId: aid, digestValue: "" })) === "auth-saml/no-digest-value");
857
+ check("vxmldsig: unsupported Transform -> unsupported-transform",
858
+ code(_craftSig({ refId: aid, transforms: "<ds:Transforms><ds:Transform Algorithm=\"http://example/bogus-xform\"></ds:Transform></ds:Transforms>" }))
859
+ === "auth-saml/unsupported-transform");
860
+ check("vxmldsig: Reference URI matching no element -> no-id-match",
861
+ code(_craftSig({ refUri: "#does-not-exist", transforms: goodTransforms })) === "auth-saml/no-id-match");
862
+ check("vxmldsig: Reference URI matching two elements -> duplicate-id (anti-wrapping)",
863
+ code(_craftSig({ refId: "dupid", transforms: goodTransforms }),
864
+ { assertionId: "dupid", decoy: "<saml:Advice xmlns:saml=\"" + SAML_A + "\" ID=\"dupid\">x</saml:Advice>" })
865
+ === "auth-saml/duplicate-id");
866
+ }
867
+
868
+ function testVerifyXmldsigNoSignatureValue(idp) {
869
+ var sp = _mkSp(idp.certPem);
870
+ var good = _mkAssertionResponse(idp, { tag: "nsv" }).b64;
871
+ var xml = Buffer.from(good, "base64").toString("utf8")
872
+ .replace(/<ds:SignatureValue>[^<]*<\/ds:SignatureValue>/, "<ds:SignatureValue></ds:SignatureValue>");
873
+ check("vxmldsig: empty SignatureValue (valid digest) -> no-signature-value",
874
+ _verifyCode(sp, b64(xml)) === "auth-saml/no-signature-value");
875
+ }
876
+
877
+ function testVerifyXmldsigBadSignature(idp, otherIdp) {
878
+ // Valid digest but SignedInfo signed by a DIFFERENT key -> bad-signature.
879
+ var sp = _mkSp(idp.certPem);
880
+ var resp = _mkAssertionResponse(idp, { tag: "bs", signKeyPem: otherIdp.keyPem }).b64;
881
+ check("vxmldsig: SignedInfo signed by wrong key -> bad-signature",
882
+ _verifyCode(sp, resp) === "auth-saml/bad-signature");
883
+ }
884
+
885
+ async function testVerifyXmldsigEcdsa() {
886
+ var ec = await _mint("ec-idp.example", "ec");
887
+ var sp = _mkSp(ec.certPem);
888
+ var info = _mkAssertionResponse(ec, {
889
+ tag: "ec",
890
+ sigOpts: { sigMethod: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", hashName: "sha256", ec: true },
891
+ });
892
+ check("vxmldsig: ECDSA-SHA256 assertion signature verifies (dsaEncoding der path)",
893
+ sp.verifyResponse(info.b64).nameId === "alice@example.com");
894
+ }
895
+
896
+ function testVerifyXmldsigWithComments(idp) {
897
+ var sp = _mkSp(idp.certPem);
898
+ var info = _mkAssertionResponse(idp, { tag: "wc", sigOpts: { withComments: true } });
899
+ check("vxmldsig: exclusive-c14n WithComments assertion signature verifies",
900
+ sp.verifyResponse(info.b64).nameId === "alice@example.com");
901
+ }
902
+
903
+ // ---------------------------------------------------------------------------
904
+ // verifyResponse — Response-level signature + signed-different-element
905
+ // ---------------------------------------------------------------------------
906
+
907
+ function testResponseLevelSignature(idp) {
908
+ var sp = _mkSp(idp.certPem);
909
+ var ok = _mkResponseLevel(idp, { tag: "rl-ok" });
910
+ check("verify: Response-level signature verifies (assertion unsigned)",
911
+ sp.verifyResponse(ok.b64).nameId === "alice@example.com");
912
+
913
+ var decoy = "<saml:Advice xmlns:saml=\"" + SAML_A + "\" ID=\"_rdecoy\">x</saml:Advice>";
914
+ var wrapped = _mkResponseLevel(idp, { tag: "rl-w", decoy: decoy, refId: "_rdecoy", digestTarget: decoy });
915
+ check("verify: Response signature over a different element -> signed-different-element",
916
+ _verifyCode(sp, wrapped.b64) === "auth-saml/signed-different-element");
917
+ }
918
+
919
+ function testAssertionSignedDifferentElement(idp) {
920
+ var sp = _mkSp(idp.certPem);
921
+ var decoy = "<saml:Advice xmlns:saml=\"" + SAML_A + "\" ID=\"_decoyA\">payload</saml:Advice>";
922
+ var resp = _mkAssertionResponse(idp, { tag: "sde", decoy: decoy, refId: "_decoyA", digestTarget: decoy });
923
+ check("verify: Assertion signature over a different element -> signed-different-element",
924
+ _verifyCode(sp, resp.b64) === "auth-saml/signed-different-element");
925
+ }
926
+
927
+ // ---------------------------------------------------------------------------
928
+ // verifyResponse — Bearer SubjectConfirmation fail-closed skips
929
+ // ---------------------------------------------------------------------------
930
+
931
+ function _bearerSubject(scd, method) {
932
+ return "<saml:Subject><saml:NameID Format=\"" + EMAIL + "\">alice@example.com</saml:NameID>" +
933
+ "<saml:SubjectConfirmation Method=\"" + (method || BEARER) + "\">" + scd + "</saml:SubjectConfirmation></saml:Subject>";
934
+ }
935
+
936
+ function testNoValidConfirmation(idp) {
937
+ var sp = _mkSp(idp.certPem);
938
+ function code(subjectXml) {
939
+ return _verifyCode(sp, _mkAssertionResponse(idp, { tag: "nvc" + (testNoValidConfirmation._n = (testNoValidConfirmation._n || 0) + 1), subjectXml: subjectXml }).b64);
940
+ }
941
+ var expect = "auth-saml/no-valid-confirmation";
942
+ check("verify: Bearer Recipient mismatch -> no-valid-confirmation",
943
+ code(_bearerSubject("<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"https://evil.example\"/>")) === expect);
944
+ check("verify: Bearer without SubjectConfirmationData -> no-valid-confirmation",
945
+ code(_bearerSubject("")) === expect);
946
+ check("verify: Bearer without NotOnOrAfter -> no-valid-confirmation",
947
+ code(_bearerSubject("<saml:SubjectConfirmationData Recipient=\"" + ACS_URL + "\"/>")) === expect);
948
+ check("verify: Bearer expired NotOnOrAfter -> no-valid-confirmation",
949
+ code(_bearerSubject("<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(-C.TIME.hours(1)) + "\" Recipient=\"" + ACS_URL + "\"/>")) === expect);
950
+ check("verify: Bearer unparseable NotOnOrAfter -> no-valid-confirmation",
951
+ code(_bearerSubject("<saml:SubjectConfirmationData NotOnOrAfter=\"not-a-date\" Recipient=\"" + ACS_URL + "\"/>")) === expect);
952
+ check("verify: Bearer NotBefore in the future -> no-valid-confirmation",
953
+ code(_bearerSubject("<saml:SubjectConfirmationData NotBefore=\"" + iso(C.TIME.hours(1)) + "\" NotOnOrAfter=\"" + iso(C.TIME.hours(2)) + "\" Recipient=\"" + ACS_URL + "\"/>")) === expect);
954
+ check("verify: Bearer unparseable NotBefore -> no-valid-confirmation",
955
+ code(_bearerSubject("<saml:SubjectConfirmationData NotBefore=\"not-a-date\" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>")) === expect);
956
+ check("verify: non-Bearer/non-HoK Method -> no-valid-confirmation",
957
+ code(_bearerSubject("<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>",
958
+ "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches")) === expect);
959
+ }
960
+
961
+ // ---------------------------------------------------------------------------
962
+ // verifyResponse — holder-of-key possession proof
963
+ // ---------------------------------------------------------------------------
964
+
965
+ function _hokScd(certB64, attrs) {
966
+ var a = attrs !== undefined ? attrs : (" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"");
967
+ var keyInfo = certB64 === null ? "" :
968
+ "<ds:KeyInfo xmlns:ds=\"" + DS + "\"><ds:X509Data><ds:X509Certificate>" + certB64 + "</ds:X509Certificate></ds:X509Data></ds:KeyInfo>";
969
+ return "<saml:SubjectConfirmationData" + a + ">" + keyInfo + "</saml:SubjectConfirmationData>";
970
+ }
971
+ function _hokSubject(scd) {
972
+ return "<saml:Subject><saml:NameID Format=\"" + EMAIL + "\">alice@example.com</saml:NameID>" +
973
+ "<saml:SubjectConfirmation Method=\"" + HOK + "\">" + (scd === null ? "" : scd) + "</saml:SubjectConfirmation></saml:Subject>";
974
+ }
975
+
976
+ function testHolderOfKey(idp, client, other) {
977
+ var sp = _mkSp(idp.certPem);
978
+ var clientB64 = _certBody(client.certPem);
979
+ var otherB64 = _certBody(other.certPem);
980
+ var presented = { presentedCertPem: client.certPem };
981
+ function verify(subjectXml, vopts, tag) {
982
+ return _mkAssertionResponse(idp, { tag: "hok-" + tag, subjectXml: subjectXml });
983
+ }
984
+
985
+ // Success — embedded X509 matches the presented possession-proof cert.
986
+ var okInfo = sp.verifyResponse(verify(_hokSubject(_hokScd(clientB64)), null, "ok").b64, { holderOfKey: presented });
987
+ check("HoK: matching possession cert -> confirmed, nameId returned", okInfo.nameId === "alice@example.com");
988
+ check("HoK: HoK confirmation returns null inResponseTo (bearerOk false)", okInfo.inResponseTo === null);
989
+
990
+ // Assertion uses HoK but the operator supplied no presented key.
991
+ check("HoK: HoK confirmation without holderOfKey opt -> hok-no-presented-key",
992
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64)), null, "npk").b64) === "auth-saml/hok-no-presented-key");
993
+
994
+ // presentedCertPem itself unparseable -> bad-hok-cert (fires before the loop).
995
+ check("HoK: unparseable presentedCertPem -> bad-hok-cert",
996
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64)), null, "bad").b64, { holderOfKey: { presentedCertPem: "not-a-cert" } })
997
+ === "auth-saml/bad-hok-cert");
998
+
999
+ // Embedded cert is a different key than presented -> key mismatch.
1000
+ check("HoK: embedded X509 != presented key -> hok-key-mismatch",
1001
+ _verifyCode(sp, verify(_hokSubject(_hokScd(otherB64)), null, "mm").b64, { holderOfKey: presented })
1002
+ === "auth-saml/hok-key-mismatch");
1003
+
1004
+ // KeyInfo shapes.
1005
+ check("HoK: SubjectConfirmationData without KeyInfo -> hok-no-keyinfo",
1006
+ _verifyCode(sp, verify(_hokSubject(_hokScd(null)), null, "nki").b64, { holderOfKey: presented })
1007
+ === "auth-saml/hok-no-keyinfo");
1008
+ check("HoK: KeyInfo without X509Data/X509Certificate -> hok-unsupported-keyinfo",
1009
+ _verifyCode(sp, verify(_hokSubject("<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL +
1010
+ "\"><ds:KeyInfo xmlns:ds=\"" + DS + "\"><ds:KeyValue><ds:RSAKeyValue></ds:RSAKeyValue></ds:KeyValue></ds:KeyInfo></saml:SubjectConfirmationData>"), null, "uki").b64,
1011
+ { holderOfKey: presented }) === "auth-saml/hok-unsupported-keyinfo");
1012
+ check("HoK: empty X509Certificate -> hok-no-cert",
1013
+ _verifyCode(sp, verify(_hokSubject(_hokScd("")), null, "nc").b64, { holderOfKey: presented })
1014
+ === "auth-saml/hok-no-cert");
1015
+ check("HoK: garbage X509Certificate -> hok-bad-cert",
1016
+ _verifyCode(sp, verify(_hokSubject(_hokScd(Buffer.from("not a cert").toString("base64"))), null, "bc").b64, { holderOfKey: presented })
1017
+ === "auth-saml/hok-bad-cert");
1018
+
1019
+ // Matching key but time / recipient / SCD fail-closed skips -> no confirmation.
1020
+ var nvc = "auth-saml/no-valid-confirmation";
1021
+ check("HoK: matched key but no SubjectConfirmationData -> no-valid-confirmation",
1022
+ _verifyCode(sp, verify(_hokSubject(null), null, "noscd").b64, { holderOfKey: presented }) === nvc);
1023
+ check("HoK: matched key but no NotOnOrAfter -> no-valid-confirmation",
1024
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64, " Recipient=\"" + ACS_URL + "\"")), null, "nnoa").b64, { holderOfKey: presented }) === nvc);
1025
+ check("HoK: matched key but expired NotOnOrAfter -> no-valid-confirmation",
1026
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64, " NotOnOrAfter=\"" + iso(-C.TIME.hours(1)) + "\" Recipient=\"" + ACS_URL + "\"")), null, "exp").b64, { holderOfKey: presented }) === nvc);
1027
+ check("HoK: matched key but NotBefore in the future -> no-valid-confirmation",
1028
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64, " NotBefore=\"" + iso(C.TIME.hours(1)) + "\" NotOnOrAfter=\"" + iso(C.TIME.hours(2)) + "\" Recipient=\"" + ACS_URL + "\"")), null, "nbf").b64, { holderOfKey: presented }) === nvc);
1029
+ check("HoK: matched key but Recipient mismatch -> no-valid-confirmation",
1030
+ _verifyCode(sp, verify(_hokSubject(_hokScd(clientB64, " NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"https://evil.example\"")), null, "rcp").b64, { holderOfKey: presented }) === nvc);
1031
+ }
1032
+
1033
+ // verifyResponse — holder-of-key must apply the SAME InResponseTo replay
1034
+ // check the Bearer path applies when the operator opts in via
1035
+ // expectedInResponseTo. SAML Web-Browser-SSO Profile §4.1.4.2 (incorporated
1036
+ // into the HoK profile by §3.1) binds a solicited response's
1037
+ // SubjectConfirmationData InResponseTo to the stored AuthnRequest ID. A HoK
1038
+ // confirmation that ignored expectedInResponseTo would silently drop the
1039
+ // operator's replay defense on that path.
1040
+ function testHolderOfKeyInResponseTo(idp, client) {
1041
+ var sp = _mkSp(idp.certPem);
1042
+ var clientB64 = _certBody(client.certPem);
1043
+ var presented = { presentedCertPem: client.certPem };
1044
+ function attrs(irt) {
1045
+ return " NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"" +
1046
+ (irt !== undefined ? " InResponseTo=\"" + irt + "\"" : "");
1047
+ }
1048
+ function mk(tag, irt) {
1049
+ return _mkAssertionResponse(idp, {
1050
+ tag: "hok-irt-" + tag,
1051
+ subjectXml: _hokSubject(_hokScd(clientB64, attrs(irt))),
1052
+ }).b64;
1053
+ }
1054
+
1055
+ // Matching InResponseTo — accepted and the validated value is echoed back
1056
+ // (not sourced from a different, non-validated confirmation).
1057
+ var okInfo = sp.verifyResponse(mk("ok", "_req-1"), { holderOfKey: presented, expectedInResponseTo: "_req-1" });
1058
+ check("HoK: matching InResponseTo -> accepted", okInfo.nameId === "alice@example.com");
1059
+ check("HoK: matching InResponseTo -> value echoed", okInfo.inResponseTo === "_req-1");
1060
+
1061
+ // Mismatched InResponseTo — the operator's replay defense MUST fire on the
1062
+ // HoK path, exactly as it does for Bearer.
1063
+ check("HoK: InResponseTo mismatch -> bad-in-response-to (replay defense on HoK path)",
1064
+ _verifyCode(sp, mk("mm", "_other"), { holderOfKey: presented, expectedInResponseTo: "_req-1" })
1065
+ === "auth-saml/bad-in-response-to");
1066
+
1067
+ // Absent InResponseTo while the operator expects one — also refused (a
1068
+ // solicited response missing the binding cannot be correlated).
1069
+ check("HoK: absent InResponseTo while expected -> bad-in-response-to",
1070
+ _verifyCode(sp, mk("absent"), { holderOfKey: presented, expectedInResponseTo: "_req-1" })
1071
+ === "auth-saml/bad-in-response-to");
1072
+
1073
+ // No expectedInResponseTo -> HoK still succeeds regardless of any present
1074
+ // InResponseTo (the operator did not opt into the replay binding).
1075
+ var noExpect = sp.verifyResponse(mk("noexp", "_whatever"), { holderOfKey: presented });
1076
+ check("HoK: no expectedInResponseTo -> accepted", noExpect.nameId === "alice@example.com");
1077
+ }
1078
+
1079
+ // ---------------------------------------------------------------------------
1080
+ // verifyResponse — Conditions, Audience, AuthnStatement variants
1081
+ // ---------------------------------------------------------------------------
1082
+
1083
+ function testConditionsAndAudience(idp) {
1084
+ var sp = _mkSp(idp.certPem);
1085
+ var ar = "<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID + "</saml:Audience></saml:AudienceRestriction>";
1086
+
1087
+ check("verify: unparseable Conditions/NotBefore -> conditions-bad-timestamp",
1088
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "cbt1",
1089
+ conditions: "<saml:Conditions NotBefore=\"not-a-date\" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\">" + ar + "</saml:Conditions>" }).b64)
1090
+ === "auth-saml/conditions-bad-timestamp");
1091
+ check("verify: unparseable Conditions/NotOnOrAfter -> conditions-bad-timestamp",
1092
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "cbt2",
1093
+ conditions: "<saml:Conditions NotBefore=\"" + iso(-C.TIME.minutes(5)) + "\" NotOnOrAfter=\"not-a-date\">" + ar + "</saml:Conditions>" }).b64)
1094
+ === "auth-saml/conditions-bad-timestamp");
1095
+
1096
+ // No Conditions element at all -> the audience binding is absent.
1097
+ check("verify: no Conditions element -> no-audience-restriction",
1098
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "nocond", conditions: "" }).b64)
1099
+ === "auth-saml/no-audience-restriction");
1100
+
1101
+ // AND-combined AudienceRestriction (SAML core 2.5.1.4): SP must be in EVERY one.
1102
+ var bothOk = "<saml:Conditions NotBefore=\"" + iso(-C.TIME.minutes(5)) + "\" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\">" +
1103
+ ar + "<saml:AudienceRestriction><saml:Audience>" + SP_ENTITY_ID + "</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
1104
+ check("verify: two AudienceRestrictions both naming SP -> accepted",
1105
+ sp.verifyResponse(_mkAssertionResponse(idp, { tag: "ars-ok", conditions: bothOk }).b64).nameId === "alice@example.com");
1106
+ var secondBad = "<saml:Conditions NotBefore=\"" + iso(-C.TIME.minutes(5)) + "\" NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\">" +
1107
+ ar + "<saml:AudienceRestriction><saml:Audience>https://other.example</saml:Audience></saml:AudienceRestriction></saml:Conditions>";
1108
+ check("verify: SP absent from a later AudienceRestriction -> wrong-audience",
1109
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "ars-bad", conditions: secondBad }).b64) === "auth-saml/wrong-audience");
1110
+
1111
+ // No AuthnStatement -> sessionIndex null.
1112
+ var noAuthn = sp.verifyResponse(_mkAssertionResponse(idp, { tag: "noauthn", authnStmt: "" }).b64);
1113
+ check("verify: assertion without AuthnStatement -> sessionIndex null", noAuthn.sessionIndex === null);
1114
+ }
1115
+
1116
+ // ---------------------------------------------------------------------------
1117
+ // verifyResponse — missing structural fields on an otherwise-signed assertion
1118
+ // (each fires only AFTER the XMLDSig verifies, so the signature gate is live)
1119
+ // ---------------------------------------------------------------------------
1120
+
1121
+ function testVerifyResponseMissingFields(idp) {
1122
+ var sp = _mkSp(idp.certPem);
1123
+
1124
+ // Signed assertion whose Subject is absent -> no-subject.
1125
+ check("verify: signed assertion without Subject -> no-subject",
1126
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "nosub", subjectXml: "" }).b64)
1127
+ === "auth-saml/no-subject");
1128
+
1129
+ // Signed assertion with a Subject but no NameID -> no-nameid.
1130
+ var subjectNoNameId = "<saml:Subject><saml:SubjectConfirmation Method=\"" + BEARER + "\">" +
1131
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>" +
1132
+ "</saml:SubjectConfirmation></saml:Subject>";
1133
+ check("verify: signed Subject without NameID -> no-nameid",
1134
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "nonid", subjectXml: subjectNoNameId }).b64)
1135
+ === "auth-saml/no-nameid");
1136
+
1137
+ // Bearer confirmation with NO InResponseTo, but the operator expects one:
1138
+ // the `inResponseTo === null` arm of the replay check must fire (this is the
1139
+ // sibling of the HoK path exercised above).
1140
+ check("verify: Bearer absent InResponseTo while expected -> bad-in-response-to",
1141
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "birt" }).b64, { expectedInResponseTo: "_req-1" })
1142
+ === "auth-saml/bad-in-response-to");
1143
+ }
1144
+
1145
+ // ---------------------------------------------------------------------------
1146
+ // verifyResponse — NameID XML-comment truncation defense
1147
+ //
1148
+ // The classic SAML comment-truncation bypass (Duo Labs, 2018): an attacker
1149
+ // splits a signed NameID text value with an XML comment so a partial-text
1150
+ // extractor reads a shorter, higher-privilege value while the signature still
1151
+ // validates (exclusive-c14n strips the comment before digesting). This
1152
+ // implementation extracts the FULL concatenated text of every text node
1153
+ // (skipping comments) and canonicalizes with the same comment-stripping, so
1154
+ // both the signed digest and the consumed NameID are the whole value — never a
1155
+ // truncated prefix. A single flat-text read here would be a full auth bypass.
1156
+ // ---------------------------------------------------------------------------
1157
+
1158
+ function testNameIdCommentTruncation(idp) {
1159
+ var sp = _mkSp(idp.certPem);
1160
+ var split = "<saml:Subject><saml:NameID Format=\"" + EMAIL + "\">" +
1161
+ "admin@good.example<!--wrap-->.attacker.example</saml:NameID>" +
1162
+ "<saml:SubjectConfirmation Method=\"" + BEARER + "\">" +
1163
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>" +
1164
+ "</saml:SubjectConfirmation></saml:Subject>";
1165
+ var info = sp.verifyResponse(_mkAssertionResponse(idp, { tag: "cmt", subjectXml: split }).b64);
1166
+ check("verify: comment-split NameID yields the FULL value, not a truncated prefix",
1167
+ info.nameId === "admin@good.example.attacker.example");
1168
+ check("verify: comment-split NameID is not truncated at the comment",
1169
+ info.nameId !== "admin@good.example");
1170
+ }
1171
+
1172
+ // ---------------------------------------------------------------------------
1173
+ // verifyResponse — EncryptedAssertion (RSA-OAEP-SHA256 + AES-256-GCM)
1174
+ // ---------------------------------------------------------------------------
1175
+
1176
+ function _encData(o) {
1177
+ o = o || {};
1178
+ var contentAlg = o.contentAlg || "http://www.w3.org/2009/xmlenc11#aes256-gcm";
1179
+ var keyAlg = o.keyAlg || "http://www.w3.org/2009/xmlenc11#rsa-oaep";
1180
+ var digestXml = o.oaepDigest === null ? "" :
1181
+ "<ds:DigestMethod Algorithm=\"" + (o.oaepDigest || "http://www.w3.org/2001/04/xmlenc#sha256") + "\"></ds:DigestMethod>";
1182
+ return "<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\">" +
1183
+ "<xenc:EncryptionMethod Algorithm=\"" + contentAlg + "\"></xenc:EncryptionMethod>" +
1184
+ "<ds:KeyInfo xmlns:ds=\"" + DS + "\"><xenc:EncryptedKey xmlns:xenc=\"" + XENC + "\">" +
1185
+ "<xenc:EncryptionMethod Algorithm=\"" + keyAlg + "\">" + digestXml + "</xenc:EncryptionMethod>" +
1186
+ "<xenc:CipherData><xenc:CipherValue>" + o.wrapped + "</xenc:CipherValue></xenc:CipherData>" +
1187
+ "</xenc:EncryptedKey></ds:KeyInfo>" +
1188
+ "<xenc:CipherData><xenc:CipherValue>" + o.content + "</xenc:CipherValue></xenc:CipherData>" +
1189
+ "</xenc:EncryptedData></saml:EncryptedAssertion>";
1190
+ }
1191
+
1192
+ function testEncryptedAssertion(idp) {
1193
+ var sp = _mkSp(idp.certPem);
1194
+ var spKp = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
1195
+ var spPriv = spKp.privateKey.export({ type: "pkcs8", format: "pem" });
1196
+ var spPub = spKp.publicKey.export({ type: "spki", format: "pem" });
1197
+ function wrap(cek) { return nodeCrypto.publicEncrypt({ key: spPub, padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" }, cek).toString("base64"); }
1198
+ function gcm(cek, buf) {
1199
+ var iv = nodeCrypto.randomBytes(12); // allow:raw-byte-literal — GCM 96-bit IV
1200
+ var cipher = nodeCrypto.createCipheriv("aes-256-gcm", cek, iv);
1201
+ var ct = Buffer.concat([cipher.update(buf), cipher.final()]);
1202
+ return Buffer.concat([iv, ct, cipher.getAuthTag()]).toString("base64");
1203
+ }
1204
+ function verifyEnc(encInner, key) { return _verifyCode(sp, b64(_response(STATUS_OK + encInner)), { spPrivateKeyPem: key }); }
1205
+
1206
+ // Happy path: decrypt -> splice -> verify the recovered signed assertion.
1207
+ var cek = nodeCrypto.randomBytes(32); // allow:raw-byte-literal — AES-256 key
1208
+ var clear = _buildAssertion(idp, { tag: "enc" }).full;
1209
+ var okXml = b64(_response(STATUS_OK + _encData({ wrapped: wrap(cek), content: gcm(cek, Buffer.from(clear, "utf8")) })));
1210
+ check("encrypted: RSA-OAEP-SHA256 + AES-256-GCM round trip -> nameId",
1211
+ sp.verifyResponse(okXml, { spPrivateKeyPem: spPriv }).nameId === "alice@example.com");
1212
+
1213
+ // Structural refusals (no crypto reached — any non-empty spPrivateKeyPem).
1214
+ check("encrypted: EncryptedAssertion without EncryptedData -> encrypted-no-encrypted-data",
1215
+ verifyEnc("<saml:EncryptedAssertion></saml:EncryptedAssertion>", "x") === "auth-saml/encrypted-no-encrypted-data");
1216
+ check("encrypted: EncryptedData without EncryptionMethod -> encrypted-no-method",
1217
+ verifyEnc("<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\"></xenc:EncryptedData></saml:EncryptedAssertion>", "x")
1218
+ === "auth-saml/encrypted-no-method");
1219
+ check("encrypted: EncryptedData without KeyInfo -> encrypted-no-keyinfo",
1220
+ verifyEnc("<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes256-gcm\"></xenc:EncryptionMethod></xenc:EncryptedData></saml:EncryptedAssertion>", "x")
1221
+ === "auth-saml/encrypted-no-keyinfo");
1222
+ check("encrypted: KeyInfo without EncryptedKey -> encrypted-no-encrypted-key",
1223
+ verifyEnc("<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes256-gcm\"></xenc:EncryptionMethod><ds:KeyInfo xmlns:ds=\"" + DS + "\"></ds:KeyInfo></xenc:EncryptedData></saml:EncryptedAssertion>", "x")
1224
+ === "auth-saml/encrypted-no-encrypted-key");
1225
+ check("encrypted: EncryptedKey without EncryptionMethod -> encrypted-no-key-alg",
1226
+ verifyEnc("<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes256-gcm\"></xenc:EncryptionMethod><ds:KeyInfo xmlns:ds=\"" + DS + "\"><xenc:EncryptedKey xmlns:xenc=\"" + XENC + "\"></xenc:EncryptedKey></ds:KeyInfo></xenc:EncryptedData></saml:EncryptedAssertion>", "x")
1227
+ === "auth-saml/encrypted-no-key-alg");
1228
+ check("encrypted: EncryptedKey without CipherValue -> encrypted-no-key-cipher-value",
1229
+ verifyEnc("<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes256-gcm\"></xenc:EncryptionMethod><ds:KeyInfo xmlns:ds=\"" + DS + "\"><xenc:EncryptedKey xmlns:xenc=\"" + XENC + "\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"></xenc:EncryptionMethod></xenc:EncryptedKey></ds:KeyInfo></xenc:EncryptedData></saml:EncryptedAssertion>", "x")
1230
+ === "auth-saml/encrypted-no-key-cipher-value");
1231
+ check("encrypted: unsupported key-transport alg -> encrypted-unsupported-key-alg",
1232
+ verifyEnc(_encData({ keyAlg: "http://example/bogus-key-alg", wrapped: "AA==", content: "AA==" }), "x")
1233
+ === "auth-saml/encrypted-unsupported-key-alg");
1234
+ check("encrypted: unsupported OAEP DigestMethod -> encrypted-unsupported-oaep-digest",
1235
+ verifyEnc(_encData({ oaepDigest: "http://example/bogus-digest", wrapped: "AA==", content: "AA==" }), "x")
1236
+ === "auth-saml/encrypted-unsupported-oaep-digest");
1237
+ check("encrypted: SHA-1 OAEP (default) -> encrypted-weak-oaep-digest",
1238
+ verifyEnc(_encData({ oaepDigest: null, wrapped: "AA==", content: "AA==" }), "x")
1239
+ === "auth-saml/encrypted-weak-oaep-digest");
1240
+ check("encrypted: unparseable spPrivateKeyPem -> encrypted-bad-sp-key",
1241
+ verifyEnc(_encData({ wrapped: "AA==", content: "AA==" }), "not-a-key") === "auth-saml/encrypted-bad-sp-key");
1242
+
1243
+ // Crypto-reaching refusals (need the real SP key).
1244
+ check("encrypted: undecryptable wrapped key -> encrypted-key-unwrap-failed",
1245
+ verifyEnc(_encData({ wrapped: nodeCrypto.randomBytes(256).toString("base64"), content: "AA==" }), spPriv)
1246
+ === "auth-saml/encrypted-key-unwrap-failed");
1247
+ check("encrypted: wrong CEK length for AES-256-GCM -> encrypted-wrong-cek-len",
1248
+ verifyEnc(_encData({ wrapped: wrap(nodeCrypto.randomBytes(16)), content: "AA==" }), spPriv)
1249
+ === "auth-saml/encrypted-wrong-cek-len");
1250
+ check("encrypted: content shorter than IV+tag -> encrypted-content-too-short",
1251
+ verifyEnc(_encData({ wrapped: wrap(nodeCrypto.randomBytes(32)), content: Buffer.alloc(10).toString("base64") }), spPriv)
1252
+ === "auth-saml/encrypted-content-too-short");
1253
+ check("encrypted: AES-GCM tag mismatch -> encrypted-content-tag-mismatch",
1254
+ verifyEnc(_encData({ wrapped: wrap(nodeCrypto.randomBytes(32)),
1255
+ content: Buffer.concat([nodeCrypto.randomBytes(12), nodeCrypto.randomBytes(20), nodeCrypto.randomBytes(16)]).toString("base64") }), spPriv)
1256
+ === "auth-saml/encrypted-content-tag-mismatch");
1257
+ check("encrypted: AES-CBC content alg refused -> encrypted-unsupported-content-alg",
1258
+ verifyEnc(_encData({ contentAlg: "http://www.w3.org/2001/04/xmlenc#aes256-cbc", wrapped: wrap(nodeCrypto.randomBytes(32)), content: "AA==" }), spPriv)
1259
+ === "auth-saml/encrypted-unsupported-content-alg");
1260
+
1261
+ var cek2 = nodeCrypto.randomBytes(32);
1262
+ check("encrypted: cleartext root is not an Assertion -> encrypted-not-assertion",
1263
+ verifyEnc(_encData({ wrapped: wrap(cek2), content: gcm(cek2, Buffer.from("<saml:Foo xmlns:saml=\"" + SAML_A + "\">x</saml:Foo>", "utf8")) }), spPriv)
1264
+ === "auth-saml/encrypted-not-assertion");
1265
+ var cek3 = nodeCrypto.randomBytes(32);
1266
+ check("encrypted: cleartext is not parseable XML -> encrypted-bad-cleartext",
1267
+ verifyEnc(_encData({ wrapped: wrap(cek3), content: gcm(cek3, Buffer.from("garbage-no-xml", "utf8")) }), spPriv)
1268
+ === "auth-saml/encrypted-bad-cleartext");
1269
+ }
1270
+
1271
+ // ---------------------------------------------------------------------------
1272
+ // SLO — embedded XMLDSig (POST) classical + Ed25519, and error branches
1273
+ // ---------------------------------------------------------------------------
1274
+
1275
+ function _ed25519Raw() {
1276
+ var ed = nodeCrypto.generateKeyPairSync("ed25519");
1277
+ var pk8 = ed.privateKey.export({ type: "pkcs8", format: "der" });
1278
+ var spki = ed.publicKey.export({ type: "spki", format: "der" });
1279
+ return { seed: new Uint8Array(pk8.subarray(pk8.length - 32)), pub: new Uint8Array(spki.subarray(spki.length - 32)) };
1280
+ }
1281
+
1282
+ function testSloPostBindings() {
1283
+ var sp = _mkSp(FAKE_CERT);
1284
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
1285
+ var skPem = rsa.privateKey.export({ type: "pkcs8", format: "pem" });
1286
+ var pkPem = rsa.publicKey.export({ type: "spki", format: "pem" });
1287
+ var ed = _ed25519Raw();
1288
+
1289
+ function roundTrip(alg, sk, pk) {
1290
+ var post = sp.buildLogoutRequestPost({ nameId: "alice@idp", sessionIndex: "_s-9", signingKey: sk, signingAlg: alg });
1291
+ var parsed = sp.parseLogoutRequestPost(post.samlRequest, { idpVerifyKey: pk, idpVerifyAlg: alg });
1292
+ return parsed.nameId === "alice@idp" && parsed.sessionIndex === "_s-9";
1293
+ }
1294
+ check("SLO POST: rsa-sha256 embedded XMLDSig round trip (PEM)", roundTrip("rsa-sha256", skPem, pkPem));
1295
+ check("SLO POST: rsa-sha256 embedded XMLDSig round trip (KeyObject)", roundTrip("rsa-sha256", rsa.privateKey, rsa.publicKey));
1296
+ check("SLO POST: rsa-sha384 embedded XMLDSig round trip", roundTrip("rsa-sha384", skPem, pkPem));
1297
+ check("SLO POST: rsa-sha512 embedded XMLDSig round trip", roundTrip("rsa-sha512", skPem, pkPem));
1298
+ check("SLO POST: ed25519 embedded XMLDSig round trip (raw key)", roundTrip("ed25519", ed.seed, ed.pub));
1299
+
1300
+ // _embedXmlDsig key/alg validation (buildLogoutRequestPost calls it directly).
1301
+ check("SLO POST: unknown signingAlg -> bad-signing-alg",
1302
+ _codeOf(function () { sp.buildLogoutRequestPost({ nameId: "a", signingAlg: "bogus" }); }) === "auth-saml/bad-signing-alg");
1303
+ check("SLO POST: classical signingKey not a PEM/KeyObject -> bad-signing-key",
1304
+ _codeOf(function () { sp.buildLogoutRequestPost({ nameId: "a", signingKey: 123, signingAlg: "rsa-sha256" }); }) === "auth-saml/bad-signing-key");
1305
+ check("SLO POST: ml-dsa signingKey not a Uint8Array -> bad-signing-key",
1306
+ _codeOf(function () { sp.buildLogoutRequestPost({ nameId: "a", signingKey: "nope", signingAlg: "ml-dsa-65" }); }) === "auth-saml/bad-signing-key");
1307
+
1308
+ // _verifyEmbeddedXmlDsig error branches.
1309
+ var unsigned = sp.buildLogoutRequestPost({ nameId: "a@idp" });
1310
+ var kp = pq.ml_dsa_65.keygen();
1311
+ check("SLO POST: verify requested but body unsigned -> no-signature",
1312
+ _codeOf(function () { sp.parseLogoutRequestPost(unsigned.samlRequest, { idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/no-signature");
1313
+
1314
+ var signed65 = sp.buildLogoutRequestPost({ nameId: "a@idp", signingKey: kp.secretKey, signingAlg: "ml-dsa-65" });
1315
+ var kp87 = pq.ml_dsa_87.keygen();
1316
+ check("SLO POST: SignatureMethod URN != expected -> wrong-sig-alg",
1317
+ _codeOf(function () { sp.parseLogoutRequestPost(signed65.samlRequest, { idpVerifyKey: kp87.publicKey, idpVerifyAlg: "ml-dsa-87" }); }) === "auth-saml/wrong-sig-alg");
1318
+
1319
+ var respRaw = sp.buildLogoutResponse({ inResponseTo: "_x", destination: IDP_SLO_URL }).raw;
1320
+ check("SLO POST: verify a LogoutResponse as a LogoutRequest -> wrong-root",
1321
+ _codeOf(function () { sp.parseLogoutRequestPost(b64(respRaw), { idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/wrong-root");
1322
+
1323
+ var tampered = Buffer.from(signed65.samlRequest, "base64").toString("utf8").replace("a@idp", "b@idp");
1324
+ check("SLO POST: tampered signed content -> digest-mismatch",
1325
+ _codeOf(function () { sp.parseLogoutRequestPost(b64(tampered), { idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/digest-mismatch");
1326
+
1327
+ var noNameId = "<samlp:LogoutRequest xmlns:samlp=\"" + SAML_P + "\" xmlns:saml=\"" + SAML_A + "\" ID=\"_x\"><saml:Issuer>i</saml:Issuer></samlp:LogoutRequest>";
1328
+ check("SLO POST: LogoutRequest without NameID -> no-nameid",
1329
+ _codeOf(function () { sp.parseLogoutRequestPost(b64(noNameId)); }) === "auth-saml/no-nameid");
1330
+ }
1331
+
1332
+ function testSloRedirectAndParse() {
1333
+ var sp = _mkSp(FAKE_CERT);
1334
+ var ed = _ed25519Raw();
1335
+ var kp = pq.ml_dsa_65.keygen();
1336
+
1337
+ // Ed25519 redirect-binding round trip (raw key sign + verify paths).
1338
+ var lr = sp.buildLogoutRequest({ nameId: "a@idp", sessionIndex: "_s", signingKey: ed.seed, signingAlg: "ed25519" });
1339
+ var q = lr.redirectUrl.split("?")[1];
1340
+ var samlReq = decodeURIComponent(q.split("&")[0].slice("SAMLRequest=".length));
1341
+ check("SLO redirect: ed25519 raw-key round trip",
1342
+ sp.parseLogoutRequest(samlReq, { queryString: q, idpVerifyKey: ed.pub, idpVerifyAlg: "ed25519" }).nameId === "a@idp");
1343
+ check("SLO redirect: verify requested without queryString -> no-query-string",
1344
+ _codeOf(function () { sp.parseLogoutRequest(samlReq, { idpVerifyKey: ed.pub, idpVerifyAlg: "ed25519" }); }) === "auth-saml/no-query-string");
1345
+ check("SLO redirect: unknown idpVerifyAlg -> bad-verify-alg",
1346
+ _codeOf(function () { sp.parseLogoutRequest(samlReq, { queryString: q, idpVerifyKey: ed.pub, idpVerifyAlg: "bogus" }); }) === "auth-saml/bad-verify-alg");
1347
+
1348
+ // buildLogoutRequest classical bad key.
1349
+ check("SLO redirect: classical signingKey not a PEM/KeyObject -> bad-signing-key",
1350
+ _codeOf(function () { sp.buildLogoutRequest({ nameId: "a", signingKey: 123, signingAlg: "rsa-sha256" }); }) === "auth-saml/bad-signing-key");
1351
+
1352
+ // parseLogoutResponse signed round trip + error branches.
1353
+ var resp = sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL, signingKey: kp.secretKey, signingAlg: "ml-dsa-65" });
1354
+ var rq = resp.redirectUrl.split("?")[1];
1355
+ var samlResp = decodeURIComponent(rq.split("&")[0].slice("SAMLResponse=".length));
1356
+ check("SLO redirect: parseLogoutResponse signed round trip -> success",
1357
+ sp.parseLogoutResponse(samlResp, { queryString: rq, idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }).success === true);
1358
+ check("SLO redirect: parseLogoutResponse verify without queryString -> no-query-string",
1359
+ _codeOf(function () { sp.parseLogoutResponse(samlResp, { idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/no-query-string");
1360
+ check("SLO redirect: parseLogoutResponse unknown idpVerifyAlg -> bad-verify-alg",
1361
+ _codeOf(function () { sp.parseLogoutResponse(samlResp, { queryString: rq, idpVerifyKey: kp.publicKey, idpVerifyAlg: "bogus" }); }) === "auth-saml/bad-verify-alg");
1362
+
1363
+ // buildLogoutResponse signing validation.
1364
+ check("SLO: buildLogoutResponse unknown signingAlg -> bad-signing-alg",
1365
+ _codeOf(function () { sp.buildLogoutResponse({ inResponseTo: "_x", destination: IDP_SLO_URL, signingAlg: "bogus" }); }) === "auth-saml/bad-signing-alg");
1366
+ check("SLO: buildLogoutResponse classical bad signingKey -> bad-signing-key",
1367
+ _codeOf(function () { sp.buildLogoutResponse({ inResponseTo: "_x", destination: IDP_SLO_URL, signingKey: 123, signingAlg: "rsa-sha256" }); }) === "auth-saml/bad-signing-key");
1368
+
1369
+ // Wrong-document parse refusals.
1370
+ var respQuery = sp.buildLogoutResponse({ inResponseTo: "_x", destination: IDP_SLO_URL }).redirectUrl.split("?")[1];
1371
+ var respOnly = decodeURIComponent(respQuery.split("&")[0].slice("SAMLResponse=".length));
1372
+ check("SLO: parseLogoutRequest given a LogoutResponse -> not-logout-request",
1373
+ _codeOf(function () { sp.parseLogoutRequest(respOnly); }) === "auth-saml/not-logout-request");
1374
+ var reqQuery = sp.buildLogoutRequest({ nameId: "a", sessionIndex: "_s" }).redirectUrl.split("?")[1];
1375
+ var reqOnly = decodeURIComponent(reqQuery.split("&")[0].slice("SAMLRequest=".length));
1376
+ check("SLO: parseLogoutResponse given a LogoutRequest -> not-logout-response",
1377
+ _codeOf(function () { sp.parseLogoutResponse(reqOnly); }) === "auth-saml/not-logout-response");
1378
+ var noNameIdReq = zlib.deflateRawSync(Buffer.from(
1379
+ "<samlp:LogoutRequest xmlns:samlp=\"" + SAML_P + "\" xmlns:saml=\"" + SAML_A + "\" ID=\"_x\"><saml:Issuer>i</saml:Issuer></samlp:LogoutRequest>", "utf8")).toString("base64");
1380
+ check("SLO: parseLogoutRequest LogoutRequest without NameID -> no-nameid",
1381
+ _codeOf(function () { sp.parseLogoutRequest(noNameIdReq); }) === "auth-saml/no-nameid");
1382
+
1383
+ // SOAP parse-side branches.
1384
+ check("SLO SOAP: unparseable envelope -> bad-soap",
1385
+ _codeOf(function () { sp.parseLogoutResponseSoap("<not-closed"); }) === "auth-saml/bad-soap");
1386
+ var soapWrongInner = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body>" +
1387
+ "<samlp:Foo xmlns:samlp=\"" + SAML_P + "\"></samlp:Foo></soapenv:Body></soapenv:Envelope>";
1388
+ check("SLO SOAP: body element is not a LogoutResponse -> wrong-root",
1389
+ _codeOf(function () { sp.parseLogoutResponseSoap(soapWrongInner); }) === "auth-saml/wrong-root");
1390
+ var lrResp = sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL }).raw;
1391
+ var soapUnsigned = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body>" + lrResp + "</soapenv:Body></soapenv:Envelope>";
1392
+ check("SLO SOAP: verify requested but LogoutResponse unsigned -> no-signature",
1393
+ _codeOf(function () { sp.parseLogoutResponseSoap(soapUnsigned, { idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/no-signature");
1394
+ }
1395
+
1396
+ // ---------------------------------------------------------------------------
1397
+ // fetchMdq — transport + verification branches (require-cache transport fake)
1398
+ // ---------------------------------------------------------------------------
1399
+
1400
+ function _fedSignature(fed, refId, elementXml) {
1401
+ var digest = nodeCrypto.createHash("sha256").update(c14n.canonicalize(elementXml)).digest("base64");
1402
+ var signedInfo = "<ds:SignedInfo xmlns:ds=\"" + DS + "\">" +
1403
+ "<ds:CanonicalizationMethod Algorithm=\"" + EXC + "\"></ds:CanonicalizationMethod>" +
1404
+ "<ds:SignatureMethod Algorithm=\"" + RSA_SHA256 + "\"></ds:SignatureMethod>" +
1405
+ "<ds:Reference URI=\"#" + refId + "\"><ds:Transforms>" +
1406
+ "<ds:Transform Algorithm=\"" + ENVELOPED + "\"></ds:Transform>" +
1407
+ "<ds:Transform Algorithm=\"" + EXC + "\"></ds:Transform></ds:Transforms>" +
1408
+ "<ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod>" +
1409
+ "<ds:DigestValue>" + digest + "</ds:DigestValue></ds:Reference></ds:SignedInfo>";
1410
+ var priv = nodeCrypto.createPrivateKey({ key: fed.keyPem, format: "pem" });
1411
+ var sig = nodeCrypto.sign("sha256", c14n.canonicalize(signedInfo), { key: priv, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }).toString("base64");
1412
+ return "<ds:Signature xmlns:ds=\"" + DS + "\">" + signedInfo + "<ds:SignatureValue>" + sig + "</ds:SignatureValue></ds:Signature>";
1413
+ }
1414
+
1415
+ async function _fetchMdqWith(status, body, trustCertPem) {
1416
+ var hcPath = require.resolve("../../lib/http-client");
1417
+ var origHc = require.cache[hcPath].exports;
1418
+ require.cache[hcPath].exports = Object.assign({}, origHc, {
1419
+ request: async function () { return { statusCode: status, headers: {}, body: body == null ? body : Buffer.from(body, "utf8") }; },
1420
+ });
1421
+ var samlPath = require.resolve("../../lib/auth/saml");
1422
+ delete require.cache[samlPath];
1423
+ var saml = require(samlPath);
1424
+ try {
1425
+ var xml = await saml.fetchMdq({ baseUrl: "https://mdq.test.invalid", entityId: IDP_ENTITY_ID, trustCertPem: trustCertPem });
1426
+ return { xml: xml, code: null };
1427
+ } catch (e) {
1428
+ return { xml: null, code: e.code || e.message };
1429
+ } finally {
1430
+ require.cache[hcPath].exports = origHc;
1431
+ delete require.cache[samlPath];
1432
+ }
1433
+ }
1434
+
1435
+ async function testFetchMdqBranches(fed) {
1436
+ var r1 = await _fetchMdqWith(500, "<x/>", null);
1437
+ check("fetchMdq: non-2xx status -> mdq-fetch-failed", r1.code === "auth-saml/mdq-fetch-failed");
1438
+ var r2 = await _fetchMdqWith(200, "", null);
1439
+ check("fetchMdq: empty body -> mdq-empty", r2.code === "auth-saml/mdq-empty");
1440
+
1441
+ var plainEd = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + IDP_ENTITY_ID + "\"></md:EntityDescriptor>";
1442
+ var r3 = await _fetchMdqWith(200, plainEd, null);
1443
+ check("fetchMdq: no trustCertPem -> returns metadata unverified", r3.code === null && r3.xml === plainEd);
1444
+
1445
+ var r4 = await _fetchMdqWith(200, plainEd, fed.certPem);
1446
+ check("fetchMdq: trustCertPem supplied but metadata unsigned -> mdq-unsigned", r4.code === "auth-saml/mdq-unsigned");
1447
+
1448
+ var dupSig = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" ID=\"G1\" entityID=\"" + IDP_ENTITY_ID + "\">" +
1449
+ "<ds:Signature xmlns:ds=\"" + DS + "\"></ds:Signature><ds:Signature xmlns:ds=\"" + DS + "\"></ds:Signature></md:EntityDescriptor>";
1450
+ var r5 = await _fetchMdqWith(200, dupSig, fed.certPem);
1451
+ check("fetchMdq: duplicate top-level Signature -> mdq-duplicate-signature", r5.code === "auth-saml/mdq-duplicate-signature");
1452
+
1453
+ // Federation-signed descriptor whose entityID differs from the requested one.
1454
+ var mismatchEntity = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" ID=\"G1\" entityID=\"https://other.example\">" +
1455
+ "<md:IDPSSODescriptor protocolSupportEnumeration=\"" + SAML_P + "\"></md:IDPSSODescriptor></md:EntityDescriptor>";
1456
+ var sig = _fedSignature(fed, "G1", mismatchEntity);
1457
+ var signedMismatch = mismatchEntity.slice(0, mismatchEntity.indexOf(">") + 1) + sig + mismatchEntity.slice(mismatchEntity.indexOf(">") + 1);
1458
+ var r6 = await _fetchMdqWith(200, signedMismatch, fed.certPem);
1459
+ check("fetchMdq: signed EntityDescriptor entityID != requested -> mdq-entity-mismatch", r6.code === "auth-saml/mdq-entity-mismatch");
1460
+
1461
+ // Full success: a federation-signed EntityDescriptor whose signature
1462
+ // verifies against trustCertPem, whose Reference binds the document-root
1463
+ // EntityDescriptor, and whose entityID equals the requested one -> the raw
1464
+ // metadata XML is returned (the advertised happy path, end to end).
1465
+ var okEntity = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" ID=\"G1\" entityID=\"" + IDP_ENTITY_ID + "\">" +
1466
+ "<md:IDPSSODescriptor protocolSupportEnumeration=\"" + SAML_P + "\"></md:IDPSSODescriptor></md:EntityDescriptor>";
1467
+ var okSig = _fedSignature(fed, "G1", okEntity);
1468
+ var signedOk = okEntity.slice(0, okEntity.indexOf(">") + 1) + okSig + okEntity.slice(okEntity.indexOf(">") + 1);
1469
+ var r7 = await _fetchMdqWith(200, signedOk, fed.certPem);
1470
+ check("fetchMdq: valid federation signature + matching entityID -> returns metadata",
1471
+ r7.code === null && r7.xml === signedOk);
1472
+ }
1473
+
1474
+ // ---------------------------------------------------------------------------
1475
+ // verifyResponse — duplicate NameID + undeclared-prefix namespace resolution
1476
+ // ---------------------------------------------------------------------------
1477
+
1478
+ function testMoreVerifyResponse(idp) {
1479
+ var sp = _mkSp(idp.certPem);
1480
+ var twoNameId = "<saml:Subject>" +
1481
+ "<saml:NameID Format=\"" + EMAIL + "\">alice@example.com</saml:NameID>" +
1482
+ "<saml:NameID>mallory@evil.example</saml:NameID>" +
1483
+ "<saml:SubjectConfirmation Method=\"" + BEARER + "\">" +
1484
+ "<saml:SubjectConfirmationData NotOnOrAfter=\"" + iso(C.TIME.minutes(5)) + "\" Recipient=\"" + ACS_URL + "\"/>" +
1485
+ "</saml:SubjectConfirmation></saml:Subject>";
1486
+ check("verify: Subject with two NameID children -> duplicate-nameid (XSW)",
1487
+ _verifyCode(sp, _mkAssertionResponse(idp, { tag: "dnid", subjectXml: twoNameId }).b64) === "auth-saml/duplicate-nameid");
1488
+
1489
+ // A Status carried under an UNDECLARED namespace prefix does not resolve to
1490
+ // the SAML protocol namespace, so it is treated as absent -> bad-status. This
1491
+ // drives the prefix->namespace lookup returning null for an undeclared prefix.
1492
+ var undeclared = "<samlp:Response xmlns:samlp=\"" + SAML_P + "\" ID=\"_r\">" +
1493
+ "<zz:Status><zz:StatusCode Value=\"" + SUCCESS + "\"/></zz:Status></samlp:Response>";
1494
+ check("verify: Status under an undeclared prefix -> bad-status",
1495
+ _verifyCode(_mkSp(FAKE_CERT), b64(undeclared)) === "auth-saml/bad-status");
1496
+ }
1497
+
1498
+ // ---------------------------------------------------------------------------
1499
+ // SLO — remaining redirect + response build/parse branches
1500
+ // ---------------------------------------------------------------------------
1501
+
1502
+ function testSloExtraBranches() {
1503
+ var sp = _mkSp(FAKE_CERT);
1504
+ var kp = pq.ml_dsa_65.keygen();
1505
+
1506
+ var lrRs = sp.buildLogoutRequest({ nameId: "a@idp", sessionIndex: "_s", relayState: "/back&x=1" });
1507
+ check("SLO: buildLogoutRequest appends RelayState",
1508
+ lrRs.redirectUrl.indexOf("RelayState=" + encodeURIComponent("/back&x=1")) !== -1);
1509
+ var respRs = sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL, relayState: "/back" });
1510
+ check("SLO: buildLogoutResponse appends RelayState",
1511
+ respRs.redirectUrl.indexOf("RelayState=" + encodeURIComponent("/back")) !== -1);
1512
+
1513
+ check("SLO: buildLogoutResponse ml-dsa signingKey not a Uint8Array -> bad-signing-key",
1514
+ _codeOf(function () { sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL, signingKey: "nope", signingAlg: "ml-dsa-65" }); }) === "auth-saml/bad-signing-key");
1515
+
1516
+ check("SLO: parseLogoutResponse undeflatable base64 -> bad-saml-response",
1517
+ _codeOf(function () { sp.parseLogoutResponse(b64("this is not deflate-raw data")); }) === "auth-saml/bad-saml-response");
1518
+
1519
+ var lrSigned = sp.buildLogoutRequest({ nameId: "a@idp", sessionIndex: "_s", signingKey: kp.secretKey, signingAlg: "ml-dsa-65" });
1520
+ var lq = lrSigned.redirectUrl.split("?")[1];
1521
+ var lreq = decodeURIComponent(lq.split("&")[0].slice("SAMLRequest=".length));
1522
+ check("SLO: parseLogoutRequest verify throws on malformed key -> verify-threw",
1523
+ _codeOf(function () { sp.parseLogoutRequest(lreq, { queryString: lq, idpVerifyKey: new Uint8Array(5), idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/verify-threw");
1524
+
1525
+ var unsignedResp = sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL });
1526
+ var urq = unsignedResp.redirectUrl.split("?")[1];
1527
+ var uresp = decodeURIComponent(urq.split("&")[0].slice("SAMLResponse=".length));
1528
+ check("SLO: parseLogoutResponse verify but query lacks Signature -> no-signature",
1529
+ _codeOf(function () { sp.parseLogoutResponse(uresp, { queryString: urq, idpVerifyKey: kp.publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/no-signature");
1530
+
1531
+ var signedResp = sp.buildLogoutResponse({ inResponseTo: "_o", destination: IDP_SLO_URL, signingKey: kp.secretKey, signingAlg: "ml-dsa-65" });
1532
+ var srq = signedResp.redirectUrl.split("?")[1];
1533
+ var sresp = decodeURIComponent(srq.split("&")[0].slice("SAMLResponse=".length));
1534
+ check("SLO: parseLogoutResponse verify throws on malformed key -> verify-threw",
1535
+ _codeOf(function () { sp.parseLogoutResponse(sresp, { queryString: srq, idpVerifyKey: new Uint8Array(5), idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/verify-threw");
1536
+ check("SLO: parseLogoutResponse wrong verify key -> bad-signature",
1537
+ _codeOf(function () { sp.parseLogoutResponse(sresp, { queryString: srq, idpVerifyKey: pq.ml_dsa_65.keygen().publicKey, idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/bad-signature");
1538
+
1539
+ var noBody = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
1540
+ "<soapenv:Header></soapenv:Header></soapenv:Envelope>";
1541
+ check("SLO SOAP: Envelope without a Body child -> bad-soap",
1542
+ _codeOf(function () { sp.parseLogoutResponseSoap(noBody); }) === "auth-saml/bad-soap");
1543
+ }
1544
+
1545
+ // ---------------------------------------------------------------------------
1546
+ // _verifyEmbeddedXmlDsig — structural refusals via a crafted embedded Signature
1547
+ // ---------------------------------------------------------------------------
1548
+
1549
+ function _craftReq(sigXml, rootId) {
1550
+ return b64("<samlp:LogoutRequest xmlns:samlp=\"" + SAML_P + "\" xmlns:saml=\"" + SAML_A + "\" ID=\"" + (rootId || "_x") +
1551
+ "\" Version=\"2.0\" IssueInstant=\"" + iso(0) + "\" Destination=\"" + IDP_SLO_URL + "\">" +
1552
+ "<saml:Issuer>i</saml:Issuer>" + sigXml + "<saml:NameID>a</saml:NameID></samlp:LogoutRequest>");
1553
+ }
1554
+
1555
+ function testEmbeddedXmlDsigStructural() {
1556
+ var sp = _mkSp(FAKE_CERT);
1557
+ var kp = pq.ml_dsa_65.keygen();
1558
+ function code(sigXml, rootId, alg) {
1559
+ return _codeOf(function () { sp.parseLogoutRequestPost(_craftReq(sigXml, rootId), { idpVerifyKey: kp.publicKey, idpVerifyAlg: alg || "ml-dsa-65" }); });
1560
+ }
1561
+ check("embedded: unknown idpVerifyAlg -> bad-verify-alg",
1562
+ code(_craftSig({ noSignedInfo: true }), "_x", "bogus") === "auth-saml/bad-verify-alg");
1563
+ check("embedded: Signature without SignedInfo -> no-signed-info",
1564
+ code(_craftSig({ noSignedInfo: true })) === "auth-saml/no-signed-info");
1565
+ check("embedded: unsupported CanonicalizationMethod -> unsupported-c14n",
1566
+ code(_craftSig({ canon: "http://example/bogus-c14n" })) === "auth-saml/unsupported-c14n");
1567
+ check("embedded: SignedInfo without Reference -> no-reference",
1568
+ code(_craftSig({ sigMethod: MLDSA65_URN, omitReference: true })) === "auth-saml/no-reference");
1569
+ check("embedded: non-fragment Reference URI -> external-reference",
1570
+ code(_craftSig({ sigMethod: MLDSA65_URN, refUri: "https://evil.example/x" })) === "auth-saml/external-reference");
1571
+ check("embedded: Reference URI != root ID -> ref-mismatch",
1572
+ code(_craftSig({ sigMethod: MLDSA65_URN, refUri: "#other" }), "_x") === "auth-saml/ref-mismatch");
1573
+ check("embedded: unsupported DigestMethod -> unsupported-digest",
1574
+ code(_craftSig({ sigMethod: MLDSA65_URN, refId: "_x", digestMethod: "http://example/bogus-digest" }), "_x") === "auth-saml/unsupported-digest");
1575
+ check("embedded: empty DigestValue -> no-digest-value",
1576
+ code(_craftSig({ sigMethod: MLDSA65_URN, refId: "_x", digestMethod: SHA3_512, digestValue: "" }), "_x") === "auth-saml/no-digest-value");
1577
+
1578
+ // no-signature-value: a validly-signed post whose SignatureValue is blanked
1579
+ // (the digest still matches, so the branch after the digest gate fires).
1580
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
1581
+ var skPem = rsa.privateKey.export({ type: "pkcs8", format: "pem" });
1582
+ var pkPem = rsa.publicKey.export({ type: "spki", format: "pem" });
1583
+ var post = sp.buildLogoutRequestPost({ nameId: "a@idp", signingKey: skPem, signingAlg: "rsa-sha256" });
1584
+ var blanked = Buffer.from(post.samlRequest, "base64").toString("utf8")
1585
+ .replace(/<ds:SignatureValue>[^<]*<\/ds:SignatureValue>/, "<ds:SignatureValue></ds:SignatureValue>");
1586
+ check("embedded: empty SignatureValue (valid digest) -> no-signature-value",
1587
+ _codeOf(function () { sp.parseLogoutRequestPost(b64(blanked), { idpVerifyKey: pkPem, idpVerifyAlg: "rsa-sha256" }); }) === "auth-saml/no-signature-value");
1588
+
1589
+ // sig-verify-threw: valid signed post, verify with a malformed ml-dsa key.
1590
+ var post2 = sp.buildLogoutRequestPost({ nameId: "a@idp", signingKey: kp.secretKey, signingAlg: "ml-dsa-65" });
1591
+ check("embedded: signature verify throws on malformed key -> sig-verify-threw",
1592
+ _codeOf(function () { sp.parseLogoutRequestPost(post2.samlRequest, { idpVerifyKey: new Uint8Array(5), idpVerifyAlg: "ml-dsa-65" }); }) === "auth-saml/sig-verify-threw");
1593
+ }
1594
+
1595
+ // ---------------------------------------------------------------------------
1596
+ // EncryptedAssertion — content-cipher + XChaCha20 length pre-checks
1597
+ // ---------------------------------------------------------------------------
1598
+
1599
+ function testEncryptedExtra(idp) {
1600
+ var sp = _mkSp(idp.certPem);
1601
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
1602
+ var spPriv = rsa.privateKey.export({ type: "pkcs8", format: "pem" });
1603
+ var spPub = rsa.publicKey.export({ type: "spki", format: "pem" });
1604
+ function wrap(cek) { return nodeCrypto.publicEncrypt({ key: spPub, padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" }, cek).toString("base64"); }
1605
+ function verifyEnc(encInner) { return _verifyCode(sp, b64(_response(STATUS_OK + encInner)), { spPrivateKeyPem: spPriv }); }
1606
+
1607
+ var noContent = "<saml:EncryptedAssertion><xenc:EncryptedData xmlns:xenc=\"" + XENC + "\">" +
1608
+ "<xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes256-gcm\"></xenc:EncryptionMethod>" +
1609
+ "<ds:KeyInfo xmlns:ds=\"" + DS + "\"><xenc:EncryptedKey xmlns:xenc=\"" + XENC + "\">" +
1610
+ "<xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod></xenc:EncryptionMethod>" +
1611
+ "<xenc:CipherData><xenc:CipherValue>" + wrap(nodeCrypto.randomBytes(32)) + "</xenc:CipherValue></xenc:CipherData>" + // allow:raw-byte-literal — AES-256 key
1612
+ "</xenc:EncryptedKey></ds:KeyInfo></xenc:EncryptedData></saml:EncryptedAssertion>";
1613
+ check("encrypted: EncryptedData without content CipherValue -> encrypted-no-content-cipher-value",
1614
+ verifyEnc(noContent) === "auth-saml/encrypted-no-content-cipher-value");
1615
+
1616
+ // XChaCha20-Poly1305 length pre-checks (fire before the AEAD call).
1617
+ check("encrypted: XChaCha20 wrong CEK length -> encrypted-wrong-cek-len",
1618
+ verifyEnc(_encData({ contentAlg: XCHACHA_URN, wrapped: wrap(nodeCrypto.randomBytes(16)), content: "AA==" })) === "auth-saml/encrypted-wrong-cek-len");
1619
+ check("encrypted: XChaCha20 content shorter than nonce+tag -> encrypted-content-too-short",
1620
+ verifyEnc(_encData({ contentAlg: XCHACHA_URN, wrapped: wrap(nodeCrypto.randomBytes(32)), content: Buffer.alloc(10).toString("base64") })) === "auth-saml/encrypted-content-too-short");
1621
+ }
1622
+
1623
+ // ---------------------------------------------------------------------------
1624
+ // EncryptedAssertion — PQC-first key transport + content encryption round trips
1625
+ // ML-KEM-1024 key transport (urn:blamejs:experimental:xmlenc:ml-kem-1024)
1626
+ // XChaCha20-Poly1305 content (urn:blamejs:experimental:xmlenc:xchacha20-poly1305)
1627
+ // These exercise the two framework-experimental decrypt paths end to end:
1628
+ // _decryptEncryptedAssertion must call real, exported b.crypto primitives
1629
+ // (the envelope opener + the packed XChaCha20-Poly1305 AEAD), not symbols
1630
+ // crypto.js never exposed.
1631
+ // ---------------------------------------------------------------------------
1632
+
1633
+ var MLKEM_URN = "urn:blamejs:experimental:xmlenc:ml-kem-1024";
1634
+
1635
+ // AES-256-GCM content framing (nonce(12) || ciphertext || tag(16)) — the wire
1636
+ // shape _decryptEncryptedAssertion reads for the AES-GCM content branch.
1637
+ function _gcmContent(cek, buf) {
1638
+ var iv = nodeCrypto.randomBytes(12); // allow:raw-byte-literal — GCM 96-bit IV
1639
+ var cipher = nodeCrypto.createCipheriv("aes-256-gcm", cek, iv);
1640
+ var ct = Buffer.concat([cipher.update(buf), cipher.final()]);
1641
+ return Buffer.concat([iv, ct, cipher.getAuthTag()]).toString("base64");
1642
+ }
1643
+
1644
+ // XChaCha20-Poly1305 content framing (nonce(24) || ciphertext || tag(16)).
1645
+ // b.crypto.encryptPacked emits a 1-byte format tag + that exact tail; strip
1646
+ // the format byte to leave the XMLEnc CipherValue the SAML reader expects.
1647
+ function _xchachaContent(cek, buf) {
1648
+ var packed = b.crypto.encryptPacked(buf, cek); // [fmt(1) | nonce(24) | ct+tag]
1649
+ return packed.subarray(1).toString("base64");
1650
+ }
1651
+
1652
+ // Wrap a CEK in the framework ML-KEM-1024 KEM-only envelope; the envelope's
1653
+ // plaintext IS the CEK. Passing only the ML-KEM public key selects the
1654
+ // KEM-only suite (no P-384 hybrid leg) that the SAML urn expects.
1655
+ function _wrapMlkem(spPubMlkem, cek) { return b.crypto.encrypt(cek, spPubMlkem); }
1656
+
1657
+ function testEncryptedAssertionPqc(idp) {
1658
+ var sp = _mkSp(idp.certPem);
1659
+ var clear = _buildAssertion(idp, { tag: "pqc-enc" }).full;
1660
+
1661
+ // SP holds an ML-KEM-1024 keypair for PQC key transport.
1662
+ var mlkemKp = b.crypto.generateEncryptionKeyPair(); // { publicKey: ml-kem-1024, privateKey, ec... }
1663
+
1664
+ // RSA keypair for the mixed RSA-transport + XChaCha-content case.
1665
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 }); // allow:raw-byte-literal — RFC 8301 §3.1 RSA bit floor
1666
+ var rsaPriv = rsa.privateKey.export({ type: "pkcs8", format: "pem" });
1667
+ var rsaPub = rsa.publicKey.export({ type: "spki", format: "pem" });
1668
+ function wrapRsa(cek) {
1669
+ return nodeCrypto.publicEncrypt({ key: rsaPub, padding: nodeCrypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" }, cek).toString("base64");
1670
+ }
1671
+
1672
+ // Case A — ML-KEM-1024 key transport + AES-256-GCM content. Isolates the
1673
+ // envelope-unwrap primitive (bug 1): pre-fix, the ml-kem branch called the
1674
+ // never-exported bCrypto.decryptEnvelope and threw encrypted-key-unwrap-failed.
1675
+ var cekA = nodeCrypto.randomBytes(32); // allow:raw-byte-literal — AES-256 key
1676
+ var xmlA = b64(_response(STATUS_OK + _encData({
1677
+ keyAlg: MLKEM_URN, oaepDigest: null,
1678
+ wrapped: _wrapMlkem(mlkemKp.publicKey, cekA),
1679
+ content: _gcmContent(cekA, Buffer.from(clear, "utf8")),
1680
+ })));
1681
+ check("encrypted(pqc): ML-KEM-1024 key transport + AES-256-GCM -> nameId",
1682
+ sp.verifyResponse(xmlA, { spPrivateKeyPem: mlkemKp.privateKey }).nameId === "alice@example.com");
1683
+
1684
+ // Case B — RSA-OAEP-SHA256 key transport + XChaCha20-Poly1305 content.
1685
+ // Isolates the AEAD content primitive (bug 2): pre-fix, the xchacha branch
1686
+ // called the never-exported bCrypto.aeadDecrypt and threw content-tag-mismatch.
1687
+ var cekB = nodeCrypto.randomBytes(32); // allow:raw-byte-literal — XChaCha20 key
1688
+ var xmlB = b64(_response(STATUS_OK + _encData({
1689
+ contentAlg: XCHACHA_URN,
1690
+ wrapped: wrapRsa(cekB),
1691
+ content: _xchachaContent(cekB, Buffer.from(clear, "utf8")),
1692
+ })));
1693
+ check("encrypted(pqc): RSA-OAEP-SHA256 + XChaCha20-Poly1305 content -> nameId",
1694
+ sp.verifyResponse(xmlB, { spPrivateKeyPem: rsaPriv }).nameId === "alice@example.com");
1695
+
1696
+ // Case C — full PQC-first path: ML-KEM-1024 key transport + XChaCha20-Poly1305
1697
+ // content (both fixed primitives in one assertion).
1698
+ var cekC = nodeCrypto.randomBytes(32); // allow:raw-byte-literal — XChaCha20 key
1699
+ var xmlC = b64(_response(STATUS_OK + _encData({
1700
+ keyAlg: MLKEM_URN, oaepDigest: null, contentAlg: XCHACHA_URN,
1701
+ wrapped: _wrapMlkem(mlkemKp.publicKey, cekC),
1702
+ content: _xchachaContent(cekC, Buffer.from(clear, "utf8")),
1703
+ })));
1704
+ check("encrypted(pqc): ML-KEM-1024 + XChaCha20-Poly1305 full PQC path -> nameId",
1705
+ sp.verifyResponse(xmlC, { spPrivateKeyPem: mlkemKp.privateKey }).nameId === "alice@example.com");
1706
+
1707
+ // Authentication must still hold: a single flipped byte in the XChaCha20
1708
+ // ciphertext+tag fails the Poly1305 verification (the fix routes through
1709
+ // the real AEAD, it does not skip the tag check).
1710
+ var packedBad = b.crypto.encryptPacked(Buffer.from(clear, "utf8"), cekC); // [fmt(1) | nonce(24) | ct+tag]
1711
+ packedBad[packedBad.length - 1] ^= 0xff; // allow:raw-byte-literal — corrupt the Poly1305 tag
1712
+ var xmlBad = b64(_response(STATUS_OK + _encData({
1713
+ keyAlg: MLKEM_URN, oaepDigest: null, contentAlg: XCHACHA_URN,
1714
+ wrapped: _wrapMlkem(mlkemKp.publicKey, cekC),
1715
+ content: packedBad.subarray(1).toString("base64"),
1716
+ })));
1717
+ check("encrypted(pqc): XChaCha20 tampered tag -> encrypted-content-tag-mismatch",
1718
+ _verifyCode(sp, xmlBad, { spPrivateKeyPem: mlkemKp.privateKey }) === "auth-saml/encrypted-content-tag-mismatch");
1719
+
1720
+ // A corrupt ML-KEM envelope still fails closed as an unwrap error (no
1721
+ // silent accept of an undecryptable key transport).
1722
+ check("encrypted(pqc): corrupt ML-KEM envelope -> encrypted-key-unwrap-failed",
1723
+ _verifyCode(sp, b64(_response(STATUS_OK + _encData({
1724
+ keyAlg: MLKEM_URN, oaepDigest: null, contentAlg: XCHACHA_URN,
1725
+ wrapped: nodeCrypto.randomBytes(64).toString("base64"),
1726
+ content: _xchachaContent(cekC, Buffer.from(clear, "utf8")),
1727
+ }))), { spPrivateKeyPem: mlkemKp.privateKey }) === "auth-saml/encrypted-key-unwrap-failed");
1728
+ }
1729
+
1730
+ async function run() {
1731
+ // create()
1732
+ testCreateRequiredFields();
1733
+ testCreateRejectsBadOpts();
1734
+ // buildAuthnRequest
1735
+ testAuthnRequestShapeAndRelayState();
1736
+ testAuthnRequestNameIdPolicy();
1737
+ testAuthnRequestQuerySeparator();
1738
+ testAuthnRequestAttributeEscaping();
1739
+ // metadata
1740
+ testMetadata();
1741
+ testMetadataSlo();
1742
+ // verifyResponse — pre-signature
1743
+ testVerifyResponseInputValidation();
1744
+ testVerifyResponseStatus();
1745
+ testVerifyResponseXswStructural();
1746
+ testVerifyResponseNoAssertionAndUnsigned();
1747
+ testVerifyResponseEncryptedWithoutKey();
1748
+ // verifyResponse — signed path
1749
+ await testVerifyResponseHappyPath();
1750
+ await testVerifyResponseInResponseTo();
1751
+ await testVerifyResponseWrongIssuer();
1752
+ await testVerifyResponseAudience();
1753
+ await testVerifyResponseConditionsExpired();
1754
+ await testVerifyResponseDuplicateSubjectXsw();
1755
+ await testVerifyResponseTamperedDigest();
1756
+ // SLO redirect
1757
+ testLogoutRequestValidation();
1758
+ testLogoutResponseValidation();
1759
+ testParseLogoutRequestValidation();
1760
+ testLogoutResponseRoundTrip();
1761
+ // SLO POST / SOAP
1762
+ testLogoutRequestPostRoundTrip();
1763
+ testParseLogoutRequestPostValidation();
1764
+ testLogoutRequestSoapShape();
1765
+ testParseLogoutResponseSoap();
1766
+ // fetchMdq
1767
+ await testFetchMdqInputValidation();
1768
+
1769
+ var idp = await _mint("idp.example");
1770
+ var other = await _mint("other-idp.example");
1771
+ var client = await _mint("client.example");
1772
+ var otherClient = await _mint("other-client.example");
1773
+ var fed = await _mint("federation.example");
1774
+
1775
+ // _verifyXmldsig
1776
+ testVerifyXmldsigStructural(idp);
1777
+ testVerifyXmldsigNoSignatureValue(idp);
1778
+ testVerifyXmldsigBadSignature(idp, other);
1779
+ await testVerifyXmldsigEcdsa();
1780
+ testVerifyXmldsigWithComments(idp);
1781
+ // verifyResponse signed-path
1782
+ testResponseLevelSignature(idp);
1783
+ testAssertionSignedDifferentElement(idp);
1784
+ testNoValidConfirmation(idp);
1785
+ testHolderOfKey(idp, client, otherClient);
1786
+ testHolderOfKeyInResponseTo(idp, client);
1787
+ testConditionsAndAudience(idp);
1788
+ testVerifyResponseMissingFields(idp);
1789
+ testNameIdCommentTruncation(idp);
1790
+ testMoreVerifyResponse(idp);
1791
+ testEncryptedAssertion(idp);
1792
+ testEncryptedExtra(idp);
1793
+ testEncryptedAssertionPqc(idp);
1794
+ // SLO
1795
+ testSloPostBindings();
1796
+ testSloRedirectAndParse();
1797
+ testSloExtraBranches();
1798
+ testEmbeddedXmlDsigStructural();
1799
+ // fetchMdq
1800
+ await testFetchMdqBranches(fed);
1801
+ }
1802
+
1803
+ if (require.main === module) {
1804
+ run().then(function () {
1805
+ console.log("OK — " + helpers.getChecks() + " checks passed");
1806
+ process.exit(0);
1807
+ }).catch(function (e) { console.error(e && e.stack || e); process.exit(1); });
1808
+ }
1809
+ module.exports = { run: run };