@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271

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 (244) hide show
  1. package/README.md +50 -39
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-B-9mXxFW.mjs +989 -0
  30. package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-DBoYbvYW.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +385 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -261
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +279 -1
  61. package/dist/federation/mq.test.mjs +123 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +42 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
  92. package/dist/http-B5JT115n.cjs +10630 -0
  93. package/dist/http-DydWCB7R.js +6527 -0
  94. package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
  95. package/dist/key-BspPiXqn.mjs +951 -0
  96. package/dist/key-C1nv7_jD.d.ts +1125 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
  104. package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
  105. package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
  106. package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
  113. package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
  114. package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
  115. package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
  119. package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
  120. package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +9 -9
  125. package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
  126. package/dist/nodeinfo/client.test.mjs +139 -4
  127. package/dist/nodeinfo/handler.test.mjs +5 -5
  128. package/dist/nodeinfo/mod.cjs +1 -1
  129. package/dist/nodeinfo/mod.d.cts +1 -1
  130. package/dist/nodeinfo/mod.d.ts +1 -1
  131. package/dist/nodeinfo/mod.js +1 -1
  132. package/dist/nodeinfo/types.test.mjs +4 -4
  133. package/dist/otel/exporter.test.mjs +3 -3
  134. package/dist/otel/mod.cjs +0 -1
  135. package/dist/otel/mod.d.cts +1 -1
  136. package/dist/otel/mod.d.ts +1 -1
  137. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  138. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  139. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  140. package/dist/owner-DYgfTFHy.mjs +267 -0
  141. package/dist/portable-collection-BpsVsRov.js +670 -0
  142. package/dist/portable-collection-C74P1vtC.mjs +418 -0
  143. package/dist/portable-collection-DZd2bm7M.cjs +686 -0
  144. package/dist/portable-key-BIMLrAP4.mjs +549 -0
  145. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  146. package/dist/proof-Dxy6ByqI.mjs +1299 -0
  147. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  148. package/dist/router-u5O5KCQr.mjs +156 -0
  149. package/dist/runtime/mod.cjs +1 -1
  150. package/dist/runtime/mod.js +1 -1
  151. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  152. package/dist/send-C4dDpAxm.mjs +495 -0
  153. package/dist/sig/accept.test.mjs +1 -1
  154. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  155. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  156. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  157. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  158. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  159. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  160. package/dist/sig/compound-proof.test.d.mts +2 -0
  161. package/dist/sig/compound-proof.test.mjs +411 -0
  162. package/dist/sig/http.test.mjs +702 -31
  163. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  164. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  165. package/dist/sig/key.test.mjs +220 -8
  166. package/dist/sig/ld.test.mjs +65 -7
  167. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  168. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  169. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  170. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  171. package/dist/sig/mod.cjs +18 -16
  172. package/dist/sig/mod.d.cts +4 -4
  173. package/dist/sig/mod.d.ts +4 -4
  174. package/dist/sig/mod.js +3 -3
  175. package/dist/sig/owner.test.mjs +108 -9
  176. package/dist/sig/portable-collection.test.d.mts +2 -0
  177. package/dist/sig/portable-collection.test.mjs +697 -0
  178. package/dist/sig/portable-interop.test.d.mts +2 -0
  179. package/dist/sig/portable-interop.test.mjs +156 -0
  180. package/dist/sig/portable-key-id.test.d.mts +2 -0
  181. package/dist/sig/portable-key-id.test.mjs +67 -0
  182. package/dist/sig/portable-key.test.d.mts +2 -0
  183. package/dist/sig/portable-key.test.mjs +1439 -0
  184. package/dist/sig/proof.test.mjs +1753 -16
  185. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  186. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  187. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  188. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  189. package/dist/sig/verification.test.d.mts +2 -0
  190. package/dist/sig/verification.test.mjs +310 -0
  191. package/dist/std__assert-DvMDRPes.mjs +8 -0
  192. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  193. package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
  194. package/dist/testing/context.test.d.mts +2 -0
  195. package/dist/testing/context.test.mjs +19 -0
  196. package/dist/testing/kv.test.d.mts +2 -0
  197. package/dist/testing/kv.test.mjs +101 -0
  198. package/dist/testing/mod.d.mts +1617 -48
  199. package/dist/testing/mod.mjs +6 -3
  200. package/dist/testing-t8xDeJZ5.mjs +8 -0
  201. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  202. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  203. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  204. package/dist/utils/docloader.test.mjs +530 -8
  205. package/dist/utils/kv-cache.test.mjs +2 -2
  206. package/dist/utils/mod.cjs +1 -1
  207. package/dist/utils/mod.d.cts +1 -1
  208. package/dist/utils/mod.d.ts +1 -1
  209. package/dist/utils/mod.js +1 -1
  210. package/dist/verification-C3954hQh.mjs +5 -0
  211. package/dist/verification-Dpfcw8ml.mjs +105 -0
  212. package/dist/vocab/cjs.test.mjs +1 -1
  213. package/package.json +8 -8
  214. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  215. package/dist/docloader-e1khNUKy.mjs +0 -49
  216. package/dist/http-CsmQ-BQ4.js +0 -2483
  217. package/dist/http-VyDTd4G3.d.cts +0 -553
  218. package/dist/http-hlIjmewS.cjs +0 -2694
  219. package/dist/http-lf8Hsd91.d.ts +0 -553
  220. package/dist/key-DfPsX8hv.mjs +0 -453
  221. package/dist/keycache-_DHqz5z_.mjs +0 -97
  222. package/dist/metrics-CoUJLQhZ.d.cts +0 -276
  223. package/dist/metrics-Dc4jR-IC.d.ts +0 -276
  224. package/dist/mod-B0hW12_O.d.cts +0 -307
  225. package/dist/mod-COIAjwRS.d.ts +0 -307
  226. package/dist/owner-Br9YYzn_.mjs +0 -120
  227. package/dist/proof-B4ngoaNQ.mjs +0 -316
  228. package/dist/proof-B6E0XUXJ.js +0 -1415
  229. package/dist/proof-NN4xCQGL.cjs +0 -1562
  230. package/dist/send-CxHKUNzv.mjs +0 -239
  231. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  232. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  233. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  234. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  235. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  236. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  237. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  238. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -1,10 +1,14 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BHzKn9Ig.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DMk-nTxI.mjs";
6
- import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-8PiolDP7.mjs";
7
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-DfPsX8hv.mjs";
4
+ import { m as parseKeyIdString } from "./portable-key-id-Ca30-hOa.mjs";
5
+ import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
+ import { n as version, t as name } from "./deno-oR5aJKpU.mjs";
7
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CYg9yxBL.mjs";
8
+ import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
9
+ import { a as promoteKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
10
+ import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-BspPiXqn.mjs";
11
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-BIMLrAP4.mjs";
8
12
  import { getLogger } from "@logtape/logtape";
9
13
  import { CryptographicKey } from "@fedify/vocab";
10
14
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -12,7 +16,7 @@ import { FetchError } from "@fedify/vocab-runtime";
12
16
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
13
17
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
14
18
  import { encodeHex } from "byte-encodings/hex";
15
- import { Item, decodeDict, encodeItem } from "structured-field-values";
19
+ import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
16
20
  //#region src/sig/http.ts
17
21
  const DEFAULT_MAX_REDIRECTION = 20;
18
22
  const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
@@ -201,6 +205,84 @@ function parseRfc9421Signature(signature) {
201
205
  for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
202
206
  return result;
203
207
  }
208
+ /**
209
+ * Parses the `Signature` header of draft-cavage HTTP Signatures into its
210
+ * fields, e.g., `keyId` and `headers`.
211
+ */
212
+ function parseDraftSignature(signature) {
213
+ return Object.fromEntries(signature.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
214
+ }
215
+ /**
216
+ * Lists the HTTP Signatures that a request claims to carry, without verifying
217
+ * any of them. Signatures whose key IDs are not URLs are left out.
218
+ * @param request The request.
219
+ * @param maxSignatures The maximum number of RFC 9421 signatures to consider,
220
+ * counted the same way as by {@link verifyRequest}, so
221
+ * that the signatures it ignores are not listed either.
222
+ * @returns The signatures; RFC 9421 ones if the request has
223
+ * a `Signature-Input` header, or a draft-cavage one otherwise.
224
+ * @internal
225
+ */
226
+ function listRequestSignatures(request, maxSignatures = 3) {
227
+ const signatureInput = request.headers.get("Signature-Input");
228
+ if (signatureInput != null) {
229
+ const signatures = [];
230
+ const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
231
+ for (const [label, input] of inputs.slice(0, maxSignatures)) {
232
+ const keyId = parseKeyId(input.keyId);
233
+ if (keyId == null) continue;
234
+ signatures.push({
235
+ keyId,
236
+ label,
237
+ components: input.components
238
+ });
239
+ }
240
+ return signatures;
241
+ }
242
+ const signature = request.headers.get("Signature");
243
+ if (signature == null) return [];
244
+ const values = parseDraftSignature(signature);
245
+ const keyId = parseKeyId(values.keyId);
246
+ if (keyId == null) return [];
247
+ return [{
248
+ keyId,
249
+ label: null,
250
+ components: (values.headers ?? "").split(/\s+/g).filter((name) => name !== "").map((name) => ({
251
+ value: name,
252
+ params: {}
253
+ }))
254
+ }];
255
+ }
256
+ /**
257
+ * Makes a copy of a request that carries only one of its RFC 9421 signatures,
258
+ * so that verifying the copy verifies that signature and no other.
259
+ * A draft-cavage signature is the only one in its request, so the request is
260
+ * just copied.
261
+ * @param request The request, whose body must not be consumed.
262
+ * @param signature One of the signatures that {@link listRequestSignatures}
263
+ * lists for the request.
264
+ * @returns The copy of the request.
265
+ * @internal
266
+ */
267
+ function selectRequestSignature(request, signature) {
268
+ const copy = request.clone();
269
+ if (signature.label == null) return copy;
270
+ const { label } = signature;
271
+ const pick = (header) => {
272
+ if (header == null) return "";
273
+ let dict;
274
+ try {
275
+ dict = decodeDict(header);
276
+ } catch {
277
+ return "";
278
+ }
279
+ return Object.hasOwn(dict, label) ? encodeDict({ [label]: dict[label] }) : "";
280
+ };
281
+ const headers = new Headers(copy.headers);
282
+ headers.set("Signature-Input", pick(headers.get("Signature-Input")));
283
+ headers.set("Signature", pick(headers.get("Signature")));
284
+ return new Request(copy, { headers });
285
+ }
204
286
  async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer, rfc9421Options) {
205
287
  if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
206
288
  const url = new URL(request.url);
@@ -274,6 +356,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
274
356
  body
275
357
  });
276
358
  }
359
+ /**
360
+ * Checks that a maximum number of signatures to verify is a positive safe
361
+ * integer or `Infinity`.
362
+ * @param value The value to check.
363
+ * @param name The name of the option, used in the error message.
364
+ * @throws {RangeError} Thrown if the value is not valid.
365
+ * @internal
366
+ */
367
+ function validateMaxSignatures(value, name) {
368
+ if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
369
+ throw new RangeError(`${name} must be a positive integer or Infinity.`);
370
+ }
277
371
  const supportedHashAlgorithms = {
278
372
  "sha": "SHA-1",
279
373
  "sha-256": "SHA-256",
@@ -307,13 +401,18 @@ function keyFetchErrorResult(keyId, result) {
307
401
  }
308
402
  };
309
403
  }
404
+ /**
405
+ * Creates the resolvers of keys of FEP-ef61 portable actors, which HTTP
406
+ * Signature verification alone passes to key lookups.
407
+ */
408
+ function createPortableKeyResolvers(options) {
409
+ return {
410
+ gatewayKey: (document, actor, keyId) => verifyPortableGatewayKeyDocument(document, actor, keyId, options),
411
+ actorKey: (keyId) => resolvePortableActorKey(keyId, options)
412
+ };
413
+ }
310
414
  function parseKeyId(value) {
311
- if (value == null) return null;
312
- try {
313
- return new URL(value);
314
- } catch {
315
- return null;
316
- }
415
+ return value == null ? null : parseKeyIdString(value);
317
416
  }
318
417
  function getKeyFetchErrorName(error) {
319
418
  return error.name || error.constructor.name || "Error";
@@ -325,7 +424,7 @@ function getKeyFetchErrorName(error) {
325
424
  * algorithm, so unknown values are dropped from the metric to prevent
326
425
  * cardinality blow-up.
327
426
  */
328
- const DRAFT_KNOWN_ALGORITHMS = new Set([
427
+ const DRAFT_KNOWN_ALGORITHMS = /* @__PURE__ */ new Set([
329
428
  "ecdsa-sha256",
330
429
  "ecdsa-sha384",
331
430
  "ecdsa-sha512",
@@ -361,6 +460,8 @@ function recordVerificationResult(span, result) {
361
460
  * @param options Options for verifying the request.
362
461
  * @returns The public key of the verified signature, or `null` if the signature
363
462
  * could not be verified.
463
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
464
+ * is not a positive integer or `Infinity`.
364
465
  */
365
466
  async function verifyRequest(request, options = {}) {
366
467
  const result = await verifyRequestDetailed(request, options);
@@ -373,9 +474,20 @@ async function verifyRequest(request, options = {}) {
373
474
  * @param request The request to verify.
374
475
  * @param options Options for verifying the request.
375
476
  * @returns The verified public key, or a structured verification failure.
477
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
478
+ * is not a positive integer or `Infinity`.
376
479
  * @since 2.1.0
377
480
  */
378
481
  async function verifyRequestDetailed(request, options = {}) {
482
+ if (options[verificationObservation] != null && options[verificationObservation]?.attempt == null) return await observeAttempt(options, "http", async (observation) => {
483
+ const result = await verifyRequestDetailed(request, {
484
+ ...options,
485
+ [verificationObservation]: observation
486
+ });
487
+ if (!result.verified) observation.attempt.reason = result.reason.type === "noSignature" ? { type: "noSignature" } : { type: "signatureVerificationFailed" };
488
+ return result;
489
+ }, (result) => result.verified);
490
+ if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
379
491
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
380
492
  if (span.isRecording()) {
381
493
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);
@@ -392,7 +504,8 @@ async function verifyRequestDetailed(request, options = {}) {
392
504
  if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
393
505
  else result = await verifyRequestDraft(request, span, metricsContext, options);
394
506
  recordVerificationResult(span, result);
395
- if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
507
+ if (result.verified) promoteKeyOwnerEvidence(result.key);
508
+ else span.setStatus({ code: SpanStatusCode.ERROR });
396
509
  return result;
397
510
  } catch (error) {
398
511
  threw = true;
@@ -412,7 +525,27 @@ async function verifyRequestDetailed(request, options = {}) {
412
525
  }
413
526
  });
414
527
  }
415
- async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
528
+ async function verifyRequestDraft(request, span, metricsContext, options = {}) {
529
+ if (options[verificationObservation]?.check != null) return await verifyRequestDraftInternal(request, span, metricsContext, options);
530
+ let result = noSignatureResult();
531
+ const raw = request.headers.get("Signature");
532
+ const declared = raw == null ? null : parseDraftSignature(raw)?.keyId ?? null;
533
+ if (raw == null) return result;
534
+ await observeCheck(options, {
535
+ mechanism: "http",
536
+ spec: "draft-cavage-http-signatures-12",
537
+ label: null
538
+ }, declared, async (observation) => {
539
+ result = await verifyRequestDraftInternal(request, span, metricsContext, {
540
+ ...options,
541
+ [verificationObservation]: observation
542
+ });
543
+ if (!result.verified && result.reason.type === "keyFetchError" && observation.check != null) observation.check.reason = result.reason;
544
+ return result.verified ? result.key : null;
545
+ });
546
+ return result;
547
+ }
548
+ async function verifyRequestDraftInternal(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider, [verificationObservation]: observation } = {}) {
416
549
  const logger = getLogger([
417
550
  "fedify",
418
551
  "sig",
@@ -432,7 +565,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
432
565
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
433
566
  return noSignatureResult();
434
567
  }
435
- const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
568
+ const sigValues = parseDraftSignature(sigHeader);
436
569
  const parsedKeyId = parseKeyId(sigValues.keyId);
437
570
  const dateHeader = request.headers.get("Date");
438
571
  if (dateHeader == null) {
@@ -570,7 +703,14 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
570
703
  contextLoader,
571
704
  keyCache,
572
705
  tracerProvider,
573
- meterProvider
706
+ meterProvider,
707
+ portableKeyResolvers: createPortableKeyResolvers({
708
+ documentLoader,
709
+ contextLoader,
710
+ keyCache,
711
+ tracerProvider,
712
+ meterProvider
713
+ })
574
714
  }));
575
715
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
576
716
  if (key == null) return invalidSignatureResult(keyIdUrl);
@@ -586,6 +726,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
586
726
  const message = headerNames.map((name) => `${name}: ` + (name === "(request-target)" ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}` : name === "(created)" ? sigValues.created ?? "" : name === "(expires)" ? sigValues.expires ?? "" : name === "host" ? request.headers.get("host") ?? new URL(request.url).host : request.headers.get(name))).join("\n");
587
727
  const sig = decodeBase64(signature);
588
728
  span?.setAttribute("http_signatures.signature", encodeHex(sig));
729
+ triedKey({ [verificationObservation]: observation }, key);
589
730
  if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
590
731
  if (cached) {
591
732
  logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
@@ -598,12 +739,10 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
598
739
  contextLoader,
599
740
  timeWindow,
600
741
  currentTime,
601
- keyCache: {
602
- get: () => Promise.resolve(void 0),
603
- set: async (keyId, key) => await keyCache?.set(keyId, key)
604
- },
742
+ keyCache: bypassKeyCacheReads(keyCache),
605
743
  meterProvider,
606
- tracerProvider
744
+ tracerProvider,
745
+ [verificationObservation]: observation
607
746
  });
608
747
  }
609
748
  logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
@@ -679,7 +818,101 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
679
818
  }
680
819
  return false;
681
820
  }
682
- async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
821
+ /**
822
+ * Verifies one RFC 9421 signature of a request with a key.
823
+ * @param request The request, whose body may be consumed already.
824
+ * @param sigInput The parsed `Signature-Input` member of the signature.
825
+ * @param sigBytes The bytes of the signature.
826
+ * @param key The public key named by the signature.
827
+ * @param span The span to record the algorithm and the signature on.
828
+ * @returns Whether the signature is verified, and the algorithm of the
829
+ * signature if it is supported.
830
+ */
831
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
832
+ const logger = getLogger([
833
+ "fedify",
834
+ "sig",
835
+ "http"
836
+ ]);
837
+ let alg = sigInput.alg?.toLowerCase();
838
+ if (alg == null) {
839
+ if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
840
+ else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
841
+ else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
842
+ else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
843
+ }
844
+ if (alg) span?.setAttribute("http_signatures.algorithm", alg);
845
+ const algorithm = alg && rfc9421AlgorithmMap[alg];
846
+ if (!algorithm) {
847
+ logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
848
+ algorithm: sigInput.alg,
849
+ supported: Object.keys(rfc9421AlgorithmMap)
850
+ });
851
+ return {
852
+ verified: false,
853
+ mismatched: false
854
+ };
855
+ }
856
+ const metricAlgorithm = alg;
857
+ let signatureBase;
858
+ try {
859
+ signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
860
+ } catch (error) {
861
+ logger.debug("Failed to create signature base for verification: {error}", {
862
+ error,
863
+ signatureInput: sigInput
864
+ });
865
+ return {
866
+ verified: false,
867
+ algorithm: metricAlgorithm,
868
+ mismatched: false
869
+ };
870
+ }
871
+ const signatureBaseBytes = new TextEncoder().encode(signatureBase);
872
+ span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
873
+ const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
874
+ if (key.publicKey.algorithm.name !== algorithmName) {
875
+ logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
876
+ keyId: sigInput.keyId,
877
+ algorithm: alg
878
+ });
879
+ return {
880
+ verified: false,
881
+ algorithm: metricAlgorithm,
882
+ mismatched: true
883
+ };
884
+ }
885
+ let verified;
886
+ try {
887
+ triedKey(observationOptions, key);
888
+ verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
889
+ } catch (error) {
890
+ logger.debug("Error during signature verification: {error}", {
891
+ error,
892
+ keyId: sigInput.keyId,
893
+ algorithm: sigInput.alg
894
+ });
895
+ return {
896
+ verified: false,
897
+ algorithm: metricAlgorithm,
898
+ mismatched: false
899
+ };
900
+ }
901
+ if (verified) return {
902
+ verified: true,
903
+ algorithm: metricAlgorithm
904
+ };
905
+ logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
906
+ keyId: sigInput.keyId,
907
+ signatureBase
908
+ });
909
+ return {
910
+ verified: false,
911
+ algorithm: metricAlgorithm,
912
+ mismatched: true
913
+ };
914
+ }
915
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, [verificationObservation]: observation, meterProvider, tracerProvider } = {}) {
683
916
  const logger = getLogger([
684
917
  "fedify",
685
918
  "sig",
@@ -692,7 +925,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
692
925
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
693
926
  return noSignatureResult();
694
927
  }
695
- const originalRequest = request;
696
928
  request = request.clone();
697
929
  const signatureInputHeader = request.headers.get("Signature-Input");
698
930
  if (!signatureInputHeader) {
@@ -712,160 +944,148 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
712
944
  logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
713
945
  return invalidSignatureResult(null);
714
946
  }
947
+ if (signatureNames.length > maxSignatures) {
948
+ logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
949
+ maxSignatures,
950
+ count: signatureNames.length
951
+ });
952
+ signatureNames.splice(maxSignatures);
953
+ }
954
+ const keyLookups = /* @__PURE__ */ new Map();
955
+ const refreshedKeyIds = /* @__PURE__ */ new Set();
956
+ const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
957
+ documentLoader,
958
+ contextLoader,
959
+ keyCache,
960
+ tracerProvider,
961
+ meterProvider,
962
+ portableKeyResolvers: createPortableKeyResolvers({
963
+ documentLoader,
964
+ contextLoader,
965
+ keyCache,
966
+ tracerProvider,
967
+ meterProvider
968
+ })
969
+ }));
715
970
  let failure = noSignatureResult();
716
971
  let failureAlgorithm;
717
972
  const setFailure = (result, algorithm) => {
718
973
  failure = result;
719
974
  failureAlgorithm = algorithm;
720
975
  };
976
+ let body = null;
977
+ let digestValid = null;
721
978
  for (const sigName of signatureNames) {
722
- if (!signatures[sigName]) {
723
- setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
724
- continue;
725
- }
726
- const sigInput = signatureInputs[sigName];
727
- const sigBytes = signatures[sigName];
728
- const keyId = parseKeyId(sigInput.keyId);
729
- if (!sigInput.keyId) {
730
- logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
731
- signatureName: sigName,
732
- signatureInput: signatureInputHeader
733
- });
734
- setFailure(invalidSignatureResult(null));
735
- continue;
736
- }
737
- if (!sigInput.created) {
738
- logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
739
- signatureName: sigName,
740
- signatureInput: signatureInputHeader
741
- });
742
- setFailure(invalidSignatureResult(keyId));
743
- continue;
744
- }
745
- const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
746
- const now = currentTime ?? Temporal.Now.instant();
747
- if (timeWindow !== false) {
748
- const tw = timeWindow ?? { hours: 1 };
749
- if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
750
- logger.debug("Failed to verify; signature created time is too far in the future.", {
751
- created: signatureCreated.toString(),
752
- now: now.toString()
979
+ let winning;
980
+ await observeCheck({ [verificationObservation]: observation }, {
981
+ mechanism: "http",
982
+ spec: "rfc9421",
983
+ label: sigName
984
+ }, signatureInputs[sigName]?.keyId ?? null, async (candidateObservation) => {
985
+ if (!signatures[sigName]) {
986
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
987
+ return null;
988
+ }
989
+ const sigInput = signatureInputs[sigName];
990
+ const sigBytes = signatures[sigName];
991
+ const keyId = parseKeyId(sigInput.keyId);
992
+ if (!sigInput.keyId) {
993
+ logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
994
+ signatureName: sigName,
995
+ signatureInput: signatureInputHeader
753
996
  });
754
- setFailure(invalidSignatureResult(keyId));
755
- continue;
756
- } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
757
- logger.debug("Failed to verify; signature created time is too far in the past.", {
758
- created: signatureCreated.toString(),
759
- now: now.toString()
997
+ setFailure(invalidSignatureResult(null));
998
+ return null;
999
+ }
1000
+ if (!sigInput.created) {
1001
+ logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
1002
+ signatureName: sigName,
1003
+ signatureInput: signatureInputHeader
760
1004
  });
761
1005
  setFailure(invalidSignatureResult(keyId));
762
- continue;
1006
+ return null;
763
1007
  }
764
- }
765
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
766
- const contentDigestHeader = request.headers.get("Content-Digest");
767
- if (!contentDigestHeader) {
768
- logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
769
- setFailure(invalidSignatureResult(keyId));
770
- continue;
1008
+ const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
1009
+ const now = currentTime ?? Temporal.Now.instant();
1010
+ if (timeWindow !== false) {
1011
+ const tw = timeWindow ?? { hours: 1 };
1012
+ if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
1013
+ logger.debug("Failed to verify; signature created time is too far in the future.", {
1014
+ created: signatureCreated.toString(),
1015
+ now: now.toString()
1016
+ });
1017
+ setFailure(invalidSignatureResult(keyId));
1018
+ return null;
1019
+ } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
1020
+ logger.debug("Failed to verify; signature created time is too far in the past.", {
1021
+ created: signatureCreated.toString(),
1022
+ now: now.toString()
1023
+ });
1024
+ setFailure(invalidSignatureResult(keyId));
1025
+ return null;
1026
+ }
771
1027
  }
772
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
773
- logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
774
- setFailure(invalidSignatureResult(keyId));
775
- continue;
1028
+ if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
1029
+ const contentDigestHeader = request.headers.get("Content-Digest");
1030
+ if (!contentDigestHeader) {
1031
+ logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1032
+ setFailure(invalidSignatureResult(keyId));
1033
+ return null;
1034
+ }
1035
+ body ??= await request.arrayBuffer();
1036
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
1037
+ if (!digestValid) {
1038
+ logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1039
+ setFailure(invalidSignatureResult(keyId));
1040
+ return null;
1041
+ }
776
1042
  }
777
- }
778
- span?.setAttribute("http_signatures.key_id", sigInput.keyId);
779
- span?.setAttribute("http_signatures.created", sigInput.created.toString());
780
- if (keyId == null) {
781
- setFailure(invalidSignatureResult(null));
782
- continue;
783
- }
784
- const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
785
- documentLoader,
786
- contextLoader,
787
- keyCache,
788
- tracerProvider,
789
- meterProvider
790
- }));
791
- if (fetchError != null) {
792
- setFailure(keyFetchErrorResult(keyId, fetchError));
793
- continue;
794
- }
795
- if (!key) {
796
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
797
- setFailure(invalidSignatureResult(keyId));
798
- continue;
799
- }
800
- let alg = sigInput.alg?.toLowerCase();
801
- if (alg == null) {
802
- if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
803
- else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
804
- else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
805
- else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
806
- }
807
- if (alg) span?.setAttribute("http_signatures.algorithm", alg);
808
- const algorithm = alg && rfc9421AlgorithmMap[alg];
809
- const candidateAlgorithm = algorithm ? alg : void 0;
810
- if (!algorithm) {
811
- logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
812
- algorithm: sigInput.alg,
813
- supported: Object.keys(rfc9421AlgorithmMap)
814
- });
815
- setFailure(invalidSignatureResult(keyId));
816
- continue;
817
- }
818
- let signatureBase;
819
- try {
820
- signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
821
- } catch (error) {
822
- logger.debug("Failed to create signature base for verification: {error}", {
823
- error,
824
- signatureInput: sigInput
825
- });
826
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
827
- continue;
828
- }
829
- const signatureBaseBytes = new TextEncoder().encode(signatureBase);
830
- span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
831
- try {
832
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
833
- metricsContext.algorithm = candidateAlgorithm;
834
- return {
835
- verified: true,
836
- key,
837
- signatureLabel: sigName
838
- };
839
- } else if (cached) {
840
- logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
841
- return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
842
- documentLoader,
843
- contextLoader,
844
- timeWindow,
845
- currentTime,
846
- keyCache: {
847
- get: () => Promise.resolve(void 0),
848
- set: async (keyId, key) => await keyCache?.set(keyId, key)
849
- },
850
- spec: "rfc9421",
851
- meterProvider,
852
- tracerProvider
853
- });
854
- } else {
855
- logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
856
- keyId: sigInput.keyId,
857
- signatureBase
858
- });
859
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1043
+ span?.setAttribute("http_signatures.key_id", sigInput.keyId);
1044
+ span?.setAttribute("http_signatures.created", sigInput.created.toString());
1045
+ if (keyId == null) {
1046
+ setFailure(invalidSignatureResult(null));
1047
+ return null;
860
1048
  }
861
- } catch (error) {
862
- logger.debug("Error during signature verification: {error}", {
863
- error,
864
- keyId: sigInput.keyId,
865
- algorithm: sigInput.alg
866
- });
867
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
868
- }
1049
+ let lookup = keyLookups.get(keyId.href);
1050
+ if (lookup == null) {
1051
+ lookup = await lookUpKey(keyId, keyCache);
1052
+ keyLookups.set(keyId.href, lookup);
1053
+ }
1054
+ while (true) {
1055
+ const { key, cached, fetchError } = lookup;
1056
+ if (fetchError != null) {
1057
+ setFailure(keyFetchErrorResult(keyId, fetchError));
1058
+ break;
1059
+ }
1060
+ if (!key) {
1061
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1062
+ setFailure(invalidSignatureResult(keyId));
1063
+ break;
1064
+ }
1065
+ const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, { [verificationObservation]: candidateObservation });
1066
+ if (result.verified) {
1067
+ metricsContext.algorithm = result.algorithm;
1068
+ winning = {
1069
+ verified: true,
1070
+ key,
1071
+ signatureLabel: sigName
1072
+ };
1073
+ return key;
1074
+ }
1075
+ if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
1076
+ logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1077
+ refreshedKeyIds.add(keyId.href);
1078
+ lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
1079
+ keyLookups.set(keyId.href, lookup);
1080
+ continue;
1081
+ }
1082
+ setFailure(invalidSignatureResult(keyId), result.algorithm);
1083
+ break;
1084
+ }
1085
+ if (!failure.verified && failure.reason.type === "keyFetchError" && candidateObservation.check != null) candidateObservation.check.reason = failure.reason;
1086
+ return null;
1087
+ });
1088
+ if (winning != null) return winning;
869
1089
  }
870
1090
  metricsContext.algorithm = failureAlgorithm;
871
1091
  return failure;
@@ -875,10 +1095,12 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
875
1095
  * @param request The original request.
876
1096
  * @param location The redirect location.
877
1097
  * @param body The request body as ArrayBuffer or null.
1098
+ * @param validateRedirect The function to validate the redirect URL.
878
1099
  * @returns A new Request object for the redirect.
879
1100
  */
880
- function createRedirectRequest(request, location, body) {
1101
+ async function createRedirectRequest(request, location, body, validateRedirect) {
881
1102
  const url = new URL(location, request.url);
1103
+ await validateRedirect?.(url.href);
882
1104
  return new Request(url, {
883
1105
  method: request.method,
884
1106
  headers: request.headers,
@@ -962,21 +1184,24 @@ async function doubleKnock(request, identity, options = {}) {
962
1184
  }
963
1185
  async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
964
1186
  const { specDeterminer, log, tracerProvider, signal } = options;
1187
+ signal?.throwIfAborted();
965
1188
  const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
966
1189
  visited.add(request.url);
967
1190
  const origin = new URL(request.url).origin;
968
1191
  const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
1192
+ signal?.throwIfAborted();
969
1193
  const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
970
1194
  let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
971
1195
  spec: firstTrySpec,
972
1196
  tracerProvider,
973
1197
  body
974
1198
  });
1199
+ signal?.throwIfAborted();
975
1200
  log?.(signedRequest);
976
1201
  let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
977
1202
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
978
1203
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
979
- const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
1204
+ const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
980
1205
  if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
981
1206
  return doubleKnockInternal(redirectRequest, identity, {
982
1207
  ...options,
@@ -996,6 +1221,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
996
1221
  let fulfilled = false;
997
1222
  let challengeRequest;
998
1223
  for (const entry of entries) {
1224
+ signal?.throwIfAborted();
999
1225
  const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
1000
1226
  if (rfc9421 == null) continue;
1001
1227
  logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
@@ -1018,13 +1244,14 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
1018
1244
  }
1019
1245
  }
1020
1246
  if (fulfilled && challengeRequest != null) {
1247
+ signal?.throwIfAborted();
1021
1248
  signedRequest = challengeRequest;
1022
1249
  log?.(signedRequest);
1023
1250
  response = await fetch(signedRequest, {
1024
1251
  redirect: "manual",
1025
1252
  signal
1026
1253
  });
1027
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(createRedirectRequest(request, response.headers.get("Location"), body), identity, {
1254
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
1028
1255
  ...options,
1029
1256
  body
1030
1257
  });
@@ -1042,16 +1269,18 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
1042
1269
  status: response.status,
1043
1270
  statusText: response.statusText
1044
1271
  });
1272
+ signal?.throwIfAborted();
1045
1273
  signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1046
1274
  spec,
1047
1275
  tracerProvider,
1048
1276
  body
1049
1277
  });
1278
+ signal?.throwIfAborted();
1050
1279
  log?.(signedRequest);
1051
1280
  response = await fetchDoubleKnockRequest(request, signedRequest, signal);
1052
1281
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1053
1282
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
1054
- const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
1283
+ const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
1055
1284
  if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
1056
1285
  return doubleKnockInternal(redirectRequest, identity, {
1057
1286
  ...options,
@@ -1089,4 +1318,4 @@ function timingSafeEqual(a, b) {
1089
1318
  return result === 0;
1090
1319
  }
1091
1320
  //#endregion
1092
- export { parseRfc9421Signature as a, timingSafeEqual as c, formatRfc9421SignatureParameters as i, verifyRequest as l, doubleKnock as n, parseRfc9421SignatureInput as o, formatRfc9421Signature as r, signRequest as s, createRfc9421SignatureBase as t, verifyRequestDetailed as u };
1321
+ export { listRequestSignatures as a, selectRequestSignature as c, validateMaxSignatures as d, verifyRequest as f, formatRfc9421SignatureParameters as i, signRequest as l, doubleKnock as n, parseRfc9421Signature as o, verifyRequestDetailed as p, formatRfc9421Signature as r, parseRfc9421SignatureInput as s, createRfc9421SignatureBase as t, timingSafeEqual as u };