@fedify/fedify 2.3.0-dev.1110 → 2.3.0-dev.1114

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 (95) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DQP-q39h.mjs} +27 -2
  2. package/dist/{builder-B-Y6fwSu.mjs → builder-YlEusQth.mjs} +3 -3
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/outgoing-jsonld.test.mjs +1 -1
  6. package/dist/compat/public-audience.test.mjs +1 -1
  7. package/dist/compat/transformers.test.mjs +2 -2
  8. package/dist/{context-C0C_sRha.d.cts → context-Ch-ZLyTQ.d.cts} +1 -1
  9. package/dist/{context-Dqgt8saU.d.ts → context-cSUMk2da.d.ts} +1 -1
  10. package/dist/{deno-hqC7tKJn.mjs → deno-CF3jMgip.mjs} +1 -1
  11. package/dist/{docloader-BOEuuXkX.mjs → docloader-BENj6vQ4.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +3 -3
  13. package/dist/federation/collection.test.mjs +2 -2
  14. package/dist/federation/handler.test.mjs +8 -7
  15. package/dist/federation/idempotency.test.mjs +5 -5
  16. package/dist/federation/inbox.test.mjs +1 -1
  17. package/dist/federation/keycache.test.mjs +1 -1
  18. package/dist/federation/kv.test.mjs +2 -2
  19. package/dist/federation/middleware.test.mjs +10 -10
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +2 -2
  22. package/dist/federation/mod.d.ts +2 -2
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/mq.test.mjs +2 -2
  25. package/dist/federation/negotiation.test.mjs +2 -2
  26. package/dist/federation/router.test.mjs +2 -2
  27. package/dist/federation/send.test.mjs +11 -11
  28. package/dist/federation/webfinger.test.mjs +3 -3
  29. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  30. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  31. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  32. package/dist/{http-BLopFpvC.mjs → http-BmOZYc-8.mjs} +86 -37
  33. package/dist/{http-DV0il3vk.cjs → http-CKCgOPkX.cjs} +427 -35
  34. package/dist/{http-O8MYWwk8.js → http-CpzZ9zsb.js} +393 -37
  35. package/dist/{http-BDZeS5om.d.ts → http-D6LP89UO.d.ts} +7 -1
  36. package/dist/{http-C87EWkO0.d.cts → http-D6aw3j2U.d.cts} +7 -1
  37. package/dist/{key-DW1EVmtP.mjs → key-B4I8H5Lc.mjs} +1 -1
  38. package/dist/{kv-cache-Dya-TWMe.cjs → kv-cache-DY-XWOqM.cjs} +1 -1
  39. package/dist/{kv-cache-C3NWWiTg.js → kv-cache-Wc5ezcVW.js} +1 -1
  40. package/dist/{ld-BNkk2Yal.mjs → ld-B5D5THhl.mjs} +60 -9
  41. package/dist/{send-hokVCPu6.mjs → metrics-ek3ilf6c.mjs} +53 -221
  42. package/dist/{middleware-CjzI3aYo.js → middleware-CuZbBw-N.js} +16 -269
  43. package/dist/{middleware-DA2WTBr4.mjs → middleware-DlcecZMq.mjs} +29 -23
  44. package/dist/{middleware-D6FbOjuK.mjs → middleware-EI7OU6BR.mjs} +1 -1
  45. package/dist/{middleware-DUWeXjZR.cjs → middleware-EqTYPG4F.cjs} +45 -298
  46. package/dist/{mod-DXY9JF28.d.cts → mod-B-Lin9Sy.d.ts} +25 -2
  47. package/dist/{mod-DHO9lk3D.d.ts → mod-BDhgfjP7.d.cts} +25 -2
  48. package/dist/{mod-B0rWmfW5.d.cts → mod-BR_BB0bh.d.cts} +1 -1
  49. package/dist/{mod-Dx3-hqyo.d.ts → mod-C6E8rkcz.d.ts} +1 -1
  50. package/dist/{mod-BhU_H1I_.d.ts → mod-DLrRb0dx.d.ts} +1 -1
  51. package/dist/{mod-CLPnQPsv.d.cts → mod-P9tE2WmM.d.cts} +1 -1
  52. package/dist/mod.cjs +4 -4
  53. package/dist/mod.d.cts +5 -5
  54. package/dist/mod.d.ts +5 -5
  55. package/dist/mod.js +4 -4
  56. package/dist/nodeinfo/client.test.mjs +2 -2
  57. package/dist/nodeinfo/handler.test.mjs +3 -3
  58. package/dist/nodeinfo/types.test.mjs +2 -2
  59. package/dist/otel/exporter.test.mjs +2 -2
  60. package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
  61. package/dist/{owner-jvJAtR5O.mjs → owner-DO810N24.mjs} +2 -2
  62. package/dist/{proof-mfmHH9j0.mjs → proof-BgfyWv7b.mjs} +25 -7
  63. package/dist/{proof-BD92WeqV.cjs → proof-DIoqrKnX.cjs} +78 -11
  64. package/dist/{proof-5kT7OUPV.js → proof-Vd8-1EWh.js} +78 -11
  65. package/dist/send-CAYXdUTk.mjs +225 -0
  66. package/dist/sig/accept.test.mjs +1 -1
  67. package/dist/sig/http.test.mjs +212 -6
  68. package/dist/sig/key.test.mjs +4 -4
  69. package/dist/sig/ld.test.mjs +138 -5
  70. package/dist/sig/mod.cjs +2 -2
  71. package/dist/sig/mod.d.cts +2 -2
  72. package/dist/sig/mod.d.ts +2 -2
  73. package/dist/sig/mod.js +2 -2
  74. package/dist/sig/owner.test.mjs +4 -4
  75. package/dist/sig/proof.test.mjs +167 -6
  76. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BTEgfoJo.mjs} +2 -27
  77. package/dist/utils/docloader.test.mjs +5 -5
  78. package/dist/utils/kv-cache.test.mjs +1 -1
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +5 -5
  84. /package/dist/{accept-CceiKpCy.mjs → accept-CgDcxvjV.mjs} +0 -0
  85. /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
  86. /package/dist/{client-B_A6mfn3.mjs → client-Bneh_DYR.mjs} +0 -0
  87. /package/dist/{collection-CA3V5zyK.mjs → collection-Cc3DVAhE.mjs} +0 -0
  88. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  89. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  90. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  91. /package/dist/{keys-C3kae-6B.mjs → keys-CSYsOMFG.mjs} +0 -0
  92. /package/dist/{kv-x2IvBUyq.mjs → kv-QHE0oeM3.mjs} +0 -0
  93. /package/dist/{kv-cache-CiiNwT6W.mjs → kv-cache-DihufyAQ.mjs} +0 -0
  94. /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-c9zmYKgA.mjs} +0 -0
  95. /package/dist/{types-BFowWFTT.mjs → types-D09GN0uZ.mjs} +0 -0
@@ -1,9 +1,10 @@
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-hqC7tKJn.mjs";
5
- import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CceiKpCy.mjs";
6
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-DW1EVmtP.mjs";
4
+ import { n as version, t as name } from "./deno-CF3jMgip.mjs";
5
+ import { a as measureSignatureKeyFetch, n as getFederationMetrics, t as getDurationMs } from "./metrics-ek3ilf6c.mjs";
6
+ import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CgDcxvjV.mjs";
7
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-B4I8H5Lc.mjs";
7
8
  import { getLogger } from "@logtape/logtape";
8
9
  import { CryptographicKey } from "@fedify/vocab";
9
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -317,6 +318,27 @@ function parseKeyId(value) {
317
318
  function getKeyFetchErrorName(error) {
318
319
  return error.name || error.constructor.name || "Error";
319
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
+ }
320
342
  function recordVerificationResult(span, result) {
321
343
  span.setAttribute("http_signatures.verified", result.verified);
322
344
  if (result.verified === true) return;
@@ -360,27 +382,37 @@ async function verifyRequestDetailed(request, options = {}) {
360
382
  span.setAttribute(ATTR_URL_FULL, request.url);
361
383
  for (const [name, value] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(name), value);
362
384
  }
385
+ const start = performance.now();
386
+ const metricsContext = {};
387
+ let result;
388
+ let threw = false;
363
389
  try {
364
390
  let spec = options.spec;
365
391
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
366
- let result;
367
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
368
- 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);
369
394
  recordVerificationResult(span, result);
370
395
  if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
371
396
  return result;
372
397
  } catch (error) {
398
+ threw = true;
373
399
  span.setStatus({
374
400
  code: SpanStatusCode.ERROR,
375
401
  message: String(error)
376
402
  });
377
403
  throw error;
378
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
+ });
379
411
  span.end();
380
412
  }
381
413
  });
382
414
  }
383
- 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 } = {}) {
384
416
  const logger = getLogger([
385
417
  "fedify",
386
418
  "sig",
@@ -528,13 +560,17 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
528
560
  const keyIdUrl = parseKeyId(keyId);
529
561
  if (keyIdUrl == null) return invalidSignatureResult(null);
530
562
  span?.setAttribute("http_signatures.key_id", keyId);
531
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
532
- 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, {
533
569
  documentLoader,
534
570
  contextLoader,
535
571
  keyCache,
536
572
  tracerProvider
537
- });
573
+ }));
538
574
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
539
575
  if (key == null) return invalidSignatureResult(keyIdUrl);
540
576
  const headerNames = headers.split(/\s+/g);
@@ -556,7 +592,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
556
592
  signature,
557
593
  message
558
594
  });
559
- return await verifyRequestDetailed(originalRequest, {
595
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
560
596
  documentLoader,
561
597
  contextLoader,
562
598
  timeWindow,
@@ -564,7 +600,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
564
600
  keyCache: {
565
601
  get: () => Promise.resolve(void 0),
566
602
  set: async (keyId, key) => await keyCache?.set(keyId, key)
567
- }
603
+ },
604
+ meterProvider,
605
+ tracerProvider
568
606
  });
569
607
  }
570
608
  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}", {
@@ -640,7 +678,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
640
678
  }
641
679
  return false;
642
680
  }
643
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
681
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
644
682
  const logger = getLogger([
645
683
  "fedify",
646
684
  "sig",
@@ -674,9 +712,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
674
712
  return invalidSignatureResult(null);
675
713
  }
676
714
  let failure = noSignatureResult();
715
+ let failureAlgorithm;
716
+ const setFailure = (result, algorithm) => {
717
+ failure = result;
718
+ failureAlgorithm = algorithm;
719
+ };
677
720
  for (const sigName of signatureNames) {
678
721
  if (!signatures[sigName]) {
679
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
722
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
680
723
  continue;
681
724
  }
682
725
  const sigInput = signatureInputs[sigName];
@@ -687,7 +730,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
687
730
  signatureName: sigName,
688
731
  signatureInput: signatureInputHeader
689
732
  });
690
- failure = invalidSignatureResult(null);
733
+ setFailure(invalidSignatureResult(null));
691
734
  continue;
692
735
  }
693
736
  if (!sigInput.created) {
@@ -695,7 +738,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
695
738
  signatureName: sigName,
696
739
  signatureInput: signatureInputHeader
697
740
  });
698
- failure = invalidSignatureResult(keyId);
741
+ setFailure(invalidSignatureResult(keyId));
699
742
  continue;
700
743
  }
701
744
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -707,14 +750,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
707
750
  created: signatureCreated.toString(),
708
751
  now: now.toString()
709
752
  });
710
- failure = invalidSignatureResult(keyId);
753
+ setFailure(invalidSignatureResult(keyId));
711
754
  continue;
712
755
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
713
756
  logger.debug("Failed to verify; signature created time is too far in the past.", {
714
757
  created: signatureCreated.toString(),
715
758
  now: now.toString()
716
759
  });
717
- failure = invalidSignatureResult(keyId);
760
+ setFailure(invalidSignatureResult(keyId));
718
761
  continue;
719
762
  }
720
763
  }
@@ -722,34 +765,34 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
722
765
  const contentDigestHeader = request.headers.get("Content-Digest");
723
766
  if (!contentDigestHeader) {
724
767
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
725
- failure = invalidSignatureResult(keyId);
768
+ setFailure(invalidSignatureResult(keyId));
726
769
  continue;
727
770
  }
728
771
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
729
772
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
730
- failure = invalidSignatureResult(keyId);
773
+ setFailure(invalidSignatureResult(keyId));
731
774
  continue;
732
775
  }
733
776
  }
734
777
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
735
778
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
736
779
  if (keyId == null) {
737
- failure = invalidSignatureResult(null);
780
+ setFailure(invalidSignatureResult(null));
738
781
  continue;
739
782
  }
740
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
783
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
741
784
  documentLoader,
742
785
  contextLoader,
743
786
  keyCache,
744
787
  tracerProvider
745
- });
788
+ }));
746
789
  if (fetchError != null) {
747
- failure = keyFetchErrorResult(keyId, fetchError);
790
+ setFailure(keyFetchErrorResult(keyId, fetchError));
748
791
  continue;
749
792
  }
750
793
  if (!key) {
751
794
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
752
- failure = invalidSignatureResult(keyId);
795
+ setFailure(invalidSignatureResult(keyId));
753
796
  continue;
754
797
  }
755
798
  let alg = sigInput.alg?.toLowerCase();
@@ -761,12 +804,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
761
804
  }
762
805
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
763
806
  const algorithm = alg && rfc9421AlgorithmMap[alg];
807
+ const candidateAlgorithm = algorithm ? alg : void 0;
764
808
  if (!algorithm) {
765
809
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
766
810
  algorithm: sigInput.alg,
767
811
  supported: Object.keys(rfc9421AlgorithmMap)
768
812
  });
769
- failure = invalidSignatureResult(keyId);
813
+ setFailure(invalidSignatureResult(keyId));
770
814
  continue;
771
815
  }
772
816
  let signatureBase;
@@ -777,20 +821,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
777
821
  error,
778
822
  signatureInput: sigInput
779
823
  });
780
- failure = invalidSignatureResult(keyId);
824
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
781
825
  continue;
782
826
  }
783
827
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
784
828
  span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
785
829
  try {
786
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
787
- verified: true,
788
- key,
789
- signatureLabel: sigName
790
- };
791
- else if (cached) {
830
+ if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
831
+ metricsContext.algorithm = candidateAlgorithm;
832
+ return {
833
+ verified: true,
834
+ key,
835
+ signatureLabel: sigName
836
+ };
837
+ } else if (cached) {
792
838
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
793
- return await verifyRequestDetailed(originalRequest, {
839
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
794
840
  documentLoader,
795
841
  contextLoader,
796
842
  timeWindow,
@@ -799,14 +845,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
799
845
  get: () => Promise.resolve(void 0),
800
846
  set: async (keyId, key) => await keyCache?.set(keyId, key)
801
847
  },
802
- spec: "rfc9421"
848
+ spec: "rfc9421",
849
+ meterProvider,
850
+ tracerProvider
803
851
  });
804
852
  } else {
805
853
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
806
854
  keyId: sigInput.keyId,
807
855
  signatureBase
808
856
  });
809
- failure = invalidSignatureResult(keyId);
857
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
810
858
  }
811
859
  } catch (error) {
812
860
  logger.debug("Error during signature verification: {error}", {
@@ -814,9 +862,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
814
862
  keyId: sigInput.keyId,
815
863
  algorithm: sigInput.alg
816
864
  });
817
- failure = invalidSignatureResult(keyId);
865
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
818
866
  }
819
867
  }
868
+ metricsContext.algorithm = failureAlgorithm;
820
869
  return failure;
821
870
  }
822
871
  /**