@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.36

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 (138) hide show
  1. package/README.md +16 -7
  2. package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
  3. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
  4. package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
  5. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
  6. package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
  7. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
  8. package/dist/{builder-yAlpiIqP.mjs → builder-DSZ7FZmg.mjs} +96 -42
  9. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  10. package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
  11. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  12. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  13. package/dist/compat/mod.d.cts +2 -1
  14. package/dist/compat/mod.d.ts +2 -3
  15. package/dist/compat/mod.js +3 -3
  16. package/dist/compat/outgoing-jsonld.test.mjs +4 -4
  17. package/dist/compat/public-audience.test.mjs +4 -4
  18. package/dist/compat/transformers.test.mjs +5 -5
  19. package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
  20. package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
  21. package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
  22. package/dist/{deno-XBVlKnOX.mjs → deno-DCdM7d93.mjs} +1 -1
  23. package/dist/{docloader-DPp-X6gk.mjs → docloader-B7mVwA5_.mjs} +4 -4
  24. package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
  25. package/dist/federation/builder.test.mjs +163 -11
  26. package/dist/federation/circuit-breaker.test.d.mts +2 -0
  27. package/dist/federation/circuit-breaker.test.mjs +446 -0
  28. package/dist/federation/collection.test.mjs +3 -3
  29. package/dist/federation/handler.test.mjs +1770 -29
  30. package/dist/federation/idempotency.test.mjs +5 -5
  31. package/dist/federation/inbox.test.mjs +4 -4
  32. package/dist/federation/keycache.test.mjs +5 -5
  33. package/dist/federation/kv.test.mjs +2 -2
  34. package/dist/federation/metrics.test.d.mts +2 -0
  35. package/dist/federation/metrics.test.mjs +634 -0
  36. package/dist/federation/middleware.test.mjs +4036 -146
  37. package/dist/federation/mod.cjs +195 -14
  38. package/dist/federation/mod.d.cts +6 -4
  39. package/dist/federation/mod.d.ts +6 -6
  40. package/dist/federation/mod.js +192 -14
  41. package/dist/federation/mq.test.mjs +176 -15
  42. package/dist/federation/negotiation.test.mjs +4 -4
  43. package/dist/federation/retry.test.mjs +3 -3
  44. package/dist/federation/router.test.mjs +190 -9
  45. package/dist/federation/send.test.mjs +153 -15
  46. package/dist/federation/temporal.test.d.mts +2 -0
  47. package/dist/federation/temporal.test.mjs +71 -0
  48. package/dist/federation/webfinger.test.mjs +150 -5
  49. package/dist/{http-CSlLA54v.mjs → http-BufbkLuW.mjs} +146 -47
  50. package/dist/{http-B4rrzxtg.js → http-Dh18nwJg.js} +994 -64
  51. package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
  52. package/dist/{http-BrGqJDXL.cjs → http-gOiudB3g.cjs} +1099 -61
  53. package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
  54. package/dist/{key-Bzx--sHD.mjs → key-DZdKLJXT.mjs} +43 -18
  55. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  56. package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BuK_nlpY.cjs} +20 -3
  57. package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-C7SQnkGI.mjs} +21 -3
  58. package/dist/{kv-cache-EMIqoIuB.js → kv-cache-DNDSb6Po.js} +20 -3
  59. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  60. package/dist/ld-0Lsznacf.mjs +626 -0
  61. package/dist/metrics-BHeWd24f.mjs +815 -0
  62. package/dist/{middleware-zjZ6AFCW.mjs → middleware-BNANvDh2.mjs} +1 -1
  63. package/dist/{middleware-nidH7VZH.js → middleware-BgzRkDcm.js} +2260 -556
  64. package/dist/{middleware-An4DjES4.cjs → middleware-D-iES1nQ.cjs} +2318 -623
  65. package/dist/{middleware-K1g6bEkV.mjs → middleware-EV6J_eAc.mjs} +1639 -373
  66. package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
  67. package/dist/mod-C0F6kvgS.d.cts +182 -0
  68. package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
  69. package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
  70. package/dist/mod-vPYVoa5n.d.ts +182 -0
  71. package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
  72. package/dist/mod.cjs +9 -6
  73. package/dist/mod.d.cts +12 -10
  74. package/dist/mod.d.ts +12 -12
  75. package/dist/mod.js +11 -11
  76. package/dist/mq-D-nlpY04.d.ts +208 -0
  77. package/dist/mq-D8uSFzxe.d.cts +208 -0
  78. package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
  79. package/dist/nodeinfo/client.test.mjs +4 -4
  80. package/dist/nodeinfo/handler.test.mjs +4 -4
  81. package/dist/nodeinfo/mod.d.cts +2 -1
  82. package/dist/nodeinfo/mod.d.ts +2 -3
  83. package/dist/nodeinfo/mod.js +3 -3
  84. package/dist/nodeinfo/types.test.mjs +3 -3
  85. package/dist/otel/exporter.test.mjs +28 -25
  86. package/dist/otel/mod.cjs +6 -5
  87. package/dist/otel/mod.d.cts +5 -3
  88. package/dist/otel/mod.d.ts +5 -5
  89. package/dist/otel/mod.js +8 -7
  90. package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
  91. package/dist/{owner-C-tqfvxn.mjs → owner-Cl-1cAR8.mjs} +2 -2
  92. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  93. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  94. package/dist/{proof-Bbbwf8_x.js → proof-CyXndf6-.js} +378 -15
  95. package/dist/{proof-BlmRPzrK.mjs → proof-Dnz8jtiQ.mjs} +26 -8
  96. package/dist/{proof-BW89QMVB.cjs → proof-tz35unOj.cjs} +432 -15
  97. package/dist/runtime/mod.d.cts +1 -0
  98. package/dist/runtime/mod.d.ts +1 -2
  99. package/dist/runtime/mod.js +3 -3
  100. package/dist/{send-05pJLcb6.mjs → send-DIbBYN5P.mjs} +72 -26
  101. package/dist/sig/http.test.mjs +356 -33
  102. package/dist/sig/key.test.mjs +103 -6
  103. package/dist/sig/ld.test.mjs +696 -7
  104. package/dist/sig/mod.cjs +2 -2
  105. package/dist/sig/mod.d.cts +4 -3
  106. package/dist/sig/mod.d.ts +4 -5
  107. package/dist/sig/mod.js +4 -4
  108. package/dist/sig/owner.test.mjs +6 -6
  109. package/dist/sig/proof.test.mjs +169 -8
  110. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
  111. package/dist/temporal-BEwob1Vg.mjs +95 -0
  112. package/dist/testing/mod.d.mts +110 -10
  113. package/dist/testing/mod.mjs +1 -2
  114. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  115. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  116. package/dist/utils/docloader.test.mjs +6 -6
  117. package/dist/utils/kv-cache.test.mjs +67 -2
  118. package/dist/utils/mod.cjs +1 -1
  119. package/dist/utils/mod.d.cts +2 -1
  120. package/dist/utils/mod.d.ts +2 -3
  121. package/dist/utils/mod.js +3 -3
  122. package/dist/vocab/cjs.test.mjs +1 -1
  123. package/dist/vocab/mod.d.cts +1 -0
  124. package/dist/vocab/mod.d.ts +1 -2
  125. package/dist/vocab/mod.js +2 -2
  126. package/package.json +16 -16
  127. package/dist/ld-DR78beiv.mjs +0 -279
  128. package/dist/middleware-BFBaR0mF.cjs +0 -4
  129. package/dist/mod-2d12ffz3.d.ts +0 -64
  130. package/dist/mod-D35TRn09.d.cts +0 -62
  131. package/dist/router-CrMLXoOr.mjs +0 -114
  132. package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
  133. package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
  134. package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
  135. package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
  136. package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
  137. package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
  138. /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -1,19 +1,21 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-XBVlKnOX.mjs";
4
+ import { n as version, t as name } from "./deno-DCdM7d93.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BHeWd24f.mjs";
5
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPkZzmGN.mjs";
6
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-Bzx--sHD.mjs";
7
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-DZdKLJXT.mjs";
8
+ import { getLogger } from "@logtape/logtape";
7
9
  import { CryptographicKey } from "@fedify/vocab";
8
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
9
11
  import { FetchError } from "@fedify/vocab-runtime";
10
- import { getLogger } from "@logtape/logtape";
11
12
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
12
13
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
13
14
  import { encodeHex } from "byte-encodings/hex";
14
15
  import { Item, decodeDict, encodeItem } from "structured-field-values";
15
16
  //#region src/sig/http.ts
16
17
  const DEFAULT_MAX_REDIRECTION = 20;
18
+ const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
17
19
  /**
18
20
  * Signs a request using the given private key.
19
21
  * @param request The request to sign.
@@ -316,6 +318,27 @@ function parseKeyId(value) {
316
318
  function getKeyFetchErrorName(error) {
317
319
  return error.name || error.constructor.name || "Error";
318
320
  }
321
+ /**
322
+ * Known draft-cavage `algorithm` parameter values, used to keep the
323
+ * `http_signatures.algorithm` metric attribute on a bounded set. The header
324
+ * field is attacker-controlled and not used to select the verification
325
+ * algorithm, so unknown values are dropped from the metric to prevent
326
+ * cardinality blow-up.
327
+ */
328
+ const DRAFT_KNOWN_ALGORITHMS = new Set([
329
+ "ecdsa-sha256",
330
+ "ecdsa-sha384",
331
+ "ecdsa-sha512",
332
+ "ed25519",
333
+ "hs2019",
334
+ "rsa-sha1",
335
+ "rsa-sha256",
336
+ "rsa-sha512"
337
+ ]);
338
+ function classifyHttpVerifyResult(result) {
339
+ if (result.verified) return "verified";
340
+ return result.reason.type === "noSignature" ? "missing" : "rejected";
341
+ }
319
342
  function recordVerificationResult(span, result) {
320
343
  span.setAttribute("http_signatures.verified", result.verified);
321
344
  if (result.verified === true) return;
@@ -359,27 +382,37 @@ async function verifyRequestDetailed(request, options = {}) {
359
382
  span.setAttribute(ATTR_URL_FULL, request.url);
360
383
  for (const [name, value] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(name), value);
361
384
  }
385
+ const start = performance.now();
386
+ const metricsContext = {};
387
+ let result;
388
+ let threw = false;
362
389
  try {
363
390
  let spec = options.spec;
364
391
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
365
- let result;
366
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
367
- else result = await verifyRequestDraft(request, span, options);
392
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
393
+ else result = await verifyRequestDraft(request, span, metricsContext, options);
368
394
  recordVerificationResult(span, result);
369
395
  if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
370
396
  return result;
371
397
  } catch (error) {
398
+ threw = true;
372
399
  span.setStatus({
373
400
  code: SpanStatusCode.ERROR,
374
401
  message: String(error)
375
402
  });
376
403
  throw error;
377
404
  } finally {
405
+ const classified = threw ? "error" : classifyHttpVerifyResult(result);
406
+ const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
407
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
408
+ algorithm: metricsContext.algorithm,
409
+ failureReason
410
+ });
378
411
  span.end();
379
412
  }
380
413
  });
381
414
  }
382
- async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
415
+ async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
383
416
  const logger = getLogger([
384
417
  "fedify",
385
418
  "sig",
@@ -527,13 +560,18 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
527
560
  const keyIdUrl = parseKeyId(keyId);
528
561
  if (keyIdUrl == null) return invalidSignatureResult(null);
529
562
  span?.setAttribute("http_signatures.key_id", keyId);
530
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
531
- const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, CryptographicKey, {
563
+ if ("algorithm" in sigValues) {
564
+ span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
565
+ const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
566
+ if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
567
+ }
568
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, CryptographicKey, {
532
569
  documentLoader,
533
570
  contextLoader,
534
571
  keyCache,
535
- tracerProvider
536
- });
572
+ tracerProvider,
573
+ meterProvider
574
+ }));
537
575
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
538
576
  if (key == null) return invalidSignatureResult(keyIdUrl);
539
577
  const headerNames = headers.split(/\s+/g);
@@ -555,7 +593,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
555
593
  signature,
556
594
  message
557
595
  });
558
- return await verifyRequestDetailed(originalRequest, {
596
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
559
597
  documentLoader,
560
598
  contextLoader,
561
599
  timeWindow,
@@ -563,7 +601,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
563
601
  keyCache: {
564
602
  get: () => Promise.resolve(void 0),
565
603
  set: async (keyId, key) => await keyCache?.set(keyId, key)
566
- }
604
+ },
605
+ meterProvider,
606
+ tracerProvider
567
607
  });
568
608
  }
569
609
  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}", {
@@ -639,7 +679,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
639
679
  }
640
680
  return false;
641
681
  }
642
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
682
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
643
683
  const logger = getLogger([
644
684
  "fedify",
645
685
  "sig",
@@ -673,9 +713,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
673
713
  return invalidSignatureResult(null);
674
714
  }
675
715
  let failure = noSignatureResult();
716
+ let failureAlgorithm;
717
+ const setFailure = (result, algorithm) => {
718
+ failure = result;
719
+ failureAlgorithm = algorithm;
720
+ };
676
721
  for (const sigName of signatureNames) {
677
722
  if (!signatures[sigName]) {
678
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
723
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
679
724
  continue;
680
725
  }
681
726
  const sigInput = signatureInputs[sigName];
@@ -686,7 +731,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
686
731
  signatureName: sigName,
687
732
  signatureInput: signatureInputHeader
688
733
  });
689
- failure = invalidSignatureResult(null);
734
+ setFailure(invalidSignatureResult(null));
690
735
  continue;
691
736
  }
692
737
  if (!sigInput.created) {
@@ -694,7 +739,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
694
739
  signatureName: sigName,
695
740
  signatureInput: signatureInputHeader
696
741
  });
697
- failure = invalidSignatureResult(keyId);
742
+ setFailure(invalidSignatureResult(keyId));
698
743
  continue;
699
744
  }
700
745
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -706,14 +751,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
706
751
  created: signatureCreated.toString(),
707
752
  now: now.toString()
708
753
  });
709
- failure = invalidSignatureResult(keyId);
754
+ setFailure(invalidSignatureResult(keyId));
710
755
  continue;
711
756
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
712
757
  logger.debug("Failed to verify; signature created time is too far in the past.", {
713
758
  created: signatureCreated.toString(),
714
759
  now: now.toString()
715
760
  });
716
- failure = invalidSignatureResult(keyId);
761
+ setFailure(invalidSignatureResult(keyId));
717
762
  continue;
718
763
  }
719
764
  }
@@ -721,34 +766,35 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
721
766
  const contentDigestHeader = request.headers.get("Content-Digest");
722
767
  if (!contentDigestHeader) {
723
768
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
724
- failure = invalidSignatureResult(keyId);
769
+ setFailure(invalidSignatureResult(keyId));
725
770
  continue;
726
771
  }
727
772
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
728
773
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
729
- failure = invalidSignatureResult(keyId);
774
+ setFailure(invalidSignatureResult(keyId));
730
775
  continue;
731
776
  }
732
777
  }
733
778
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
734
779
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
735
780
  if (keyId == null) {
736
- failure = invalidSignatureResult(null);
781
+ setFailure(invalidSignatureResult(null));
737
782
  continue;
738
783
  }
739
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
784
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
740
785
  documentLoader,
741
786
  contextLoader,
742
787
  keyCache,
743
- tracerProvider
744
- });
788
+ tracerProvider,
789
+ meterProvider
790
+ }));
745
791
  if (fetchError != null) {
746
- failure = keyFetchErrorResult(keyId, fetchError);
792
+ setFailure(keyFetchErrorResult(keyId, fetchError));
747
793
  continue;
748
794
  }
749
795
  if (!key) {
750
796
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
751
- failure = invalidSignatureResult(keyId);
797
+ setFailure(invalidSignatureResult(keyId));
752
798
  continue;
753
799
  }
754
800
  let alg = sigInput.alg?.toLowerCase();
@@ -760,12 +806,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
760
806
  }
761
807
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
762
808
  const algorithm = alg && rfc9421AlgorithmMap[alg];
809
+ const candidateAlgorithm = algorithm ? alg : void 0;
763
810
  if (!algorithm) {
764
811
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
765
812
  algorithm: sigInput.alg,
766
813
  supported: Object.keys(rfc9421AlgorithmMap)
767
814
  });
768
- failure = invalidSignatureResult(keyId);
815
+ setFailure(invalidSignatureResult(keyId));
769
816
  continue;
770
817
  }
771
818
  let signatureBase;
@@ -776,20 +823,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
776
823
  error,
777
824
  signatureInput: sigInput
778
825
  });
779
- failure = invalidSignatureResult(keyId);
826
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
780
827
  continue;
781
828
  }
782
829
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
783
830
  span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
784
831
  try {
785
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
786
- verified: true,
787
- key,
788
- signatureLabel: sigName
789
- };
790
- else if (cached) {
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) {
791
840
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
792
- return await verifyRequestDetailed(originalRequest, {
841
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
793
842
  documentLoader,
794
843
  contextLoader,
795
844
  timeWindow,
@@ -798,14 +847,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
798
847
  get: () => Promise.resolve(void 0),
799
848
  set: async (keyId, key) => await keyCache?.set(keyId, key)
800
849
  },
801
- spec: "rfc9421"
850
+ spec: "rfc9421",
851
+ meterProvider,
852
+ tracerProvider
802
853
  });
803
854
  } else {
804
855
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
805
856
  keyId: sigInput.keyId,
806
857
  signatureBase
807
858
  });
808
- failure = invalidSignatureResult(keyId);
859
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
809
860
  }
810
861
  } catch (error) {
811
862
  logger.debug("Error during signature verification: {error}", {
@@ -813,9 +864,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
813
864
  keyId: sigInput.keyId,
814
865
  algorithm: sigInput.alg
815
866
  });
816
- failure = invalidSignatureResult(keyId);
867
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
817
868
  }
818
869
  }
870
+ metricsContext.algorithm = failureAlgorithm;
819
871
  return failure;
820
872
  }
821
873
  /**
@@ -842,6 +894,59 @@ function createRedirectRequest(request, location, body) {
842
894
  cache: request.cache
843
895
  });
844
896
  }
897
+ async function fetchDoubleKnockRequest(request, signedRequest, signal) {
898
+ const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
899
+ for (let attempt = 1;; attempt++) try {
900
+ return await fetch(signedRequest, {
901
+ redirect: "manual",
902
+ signal
903
+ });
904
+ } catch (error) {
905
+ const abortedSignal = getAbortedSignal(signal, request.signal, signedRequest.signal);
906
+ if (abortedSignal != null) throw getAbortReason(abortedSignal);
907
+ if (isAbortError(error)) throw error;
908
+ if (attempt >= maxAttempts) throw createFetchError(request.url, error);
909
+ await sleep(DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS, signal, request.signal, signedRequest.signal);
910
+ }
911
+ }
912
+ function createFetchError(url, cause) {
913
+ const error = new FetchError(url, cause instanceof Error ? cause.message : String(cause));
914
+ error.cause = cause;
915
+ return error;
916
+ }
917
+ function isAbortError(error) {
918
+ return error instanceof Error && error.name === "AbortError";
919
+ }
920
+ async function sleep(ms, ...signals) {
921
+ const abortSignals = signals.filter((signal) => signal != null);
922
+ const abortedSignal = getAbortedSignal(...abortSignals);
923
+ if (abortedSignal != null) throw getAbortReason(abortedSignal);
924
+ if (abortSignals.length < 1) {
925
+ await new Promise((resolve) => setTimeout(resolve, ms));
926
+ return;
927
+ }
928
+ await new Promise((resolve, reject) => {
929
+ const removeAbortListeners = () => {
930
+ for (const signal of abortSignals) signal.removeEventListener("abort", handleAbort);
931
+ };
932
+ const timeout = setTimeout(() => {
933
+ removeAbortListeners();
934
+ resolve();
935
+ }, ms);
936
+ function handleAbort(event) {
937
+ clearTimeout(timeout);
938
+ removeAbortListeners();
939
+ reject(getAbortReason(event.currentTarget));
940
+ }
941
+ for (const signal of abortSignals) signal.addEventListener("abort", handleAbort, { once: true });
942
+ });
943
+ }
944
+ function getAbortedSignal(...signals) {
945
+ return signals.find((signal) => signal?.aborted);
946
+ }
947
+ function getAbortReason(signal) {
948
+ return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
949
+ }
845
950
  /**
846
951
  * Performs a double-knock request to the given URL. For the details of
847
952
  * double-knocking, see
@@ -868,10 +973,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
868
973
  body
869
974
  });
870
975
  log?.(signedRequest);
871
- let response = await fetch(signedRequest, {
872
- redirect: "manual",
873
- signal
874
- });
976
+ let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
875
977
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
876
978
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
877
979
  const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
@@ -946,10 +1048,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
946
1048
  body
947
1049
  });
948
1050
  log?.(signedRequest);
949
- response = await fetch(signedRequest, {
950
- redirect: "manual",
951
- signal
952
- });
1051
+ response = await fetchDoubleKnockRequest(request, signedRequest, signal);
953
1052
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
954
1053
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
955
1054
  const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);