@fedify/fedify 2.4.0-dev.2256 → 2.4.0-dev.2260

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 (105) hide show
  1. package/dist/{builder-DvSQqk2l.mjs → builder-BK8HfSUm.mjs} +8 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-5bg_j_ye.mjs → compound-proof-D--HeIbV.mjs} +41 -4
  6. package/dist/{context-CtI307_A.d.ts → context-7NyiqIcg.d.ts} +111 -2
  7. package/dist/{context-B6X-t8gI.d.cts → context-tsYPJ2Ew.d.cts} +111 -2
  8. package/dist/{deno-AmcRMq5z.mjs → deno-DVP7DGJC.mjs} +1 -1
  9. package/dist/{docloader-CIP_SG39.mjs → docloader-Dv1JJl37.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +76 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-report.test.d.mts +2 -0
  17. package/dist/federation/inbox-report.test.mjs +475 -0
  18. package/dist/federation/metrics.test.mjs +1 -1
  19. package/dist/federation/middleware.test.mjs +7 -7
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +4 -3
  22. package/dist/federation/mod.d.ts +4 -3
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/outgoing-proof.test.mjs +54 -10
  25. package/dist/federation/portable-actor.test.mjs +3 -3
  26. package/dist/federation/portable-chain.test.mjs +3 -3
  27. package/dist/federation/portable-collection.test.mjs +66 -4
  28. package/dist/federation/portable-delivery.test.mjs +2 -2
  29. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  30. package/dist/federation/portable-inbox.test.mjs +67 -6
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.mjs +2 -2
  33. package/dist/federation/portable.test.mjs +3 -3
  34. package/dist/federation/send.test.mjs +3 -3
  35. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  36. package/dist/federation/tasks/tasks.test.mjs +2 -2
  37. package/dist/federation/temporal.test.mjs +1 -1
  38. package/dist/federation/webfinger.test.mjs +1 -1
  39. package/dist/{federation-BDzIgqOd.cjs → federation-Bo5z395X.cjs} +552 -122
  40. package/dist/{http-1fZ83jj7.cjs → http-BIhWdDf5.cjs} +447 -110
  41. package/dist/{http-BGb3QioZ.js → http-DRMu1C2T.js} +430 -111
  42. package/dist/{http-DkqWu-sm.mjs → http-DvpuyDJB.mjs} +141 -98
  43. package/dist/{key-CEsu7oaD.mjs → key-B7N8mNoY.mjs} +30 -3
  44. package/dist/{http-BrwKINQn.d.cts → key-C1nv7_jD.d.ts} +638 -229
  45. package/dist/{http-DGx0KOws.d.ts → key-DYxuyd-s.d.cts} +638 -229
  46. package/dist/{kv-cache-CJS4mImS.mjs → kv-cache-D0kA0rml.mjs} +1 -1
  47. package/dist/{kv-cache-CGhEgt98.js → kv-cache-DZLpqhW9.js} +1 -1
  48. package/dist/{kv-cache-BEWuZSB4.cjs → kv-cache-mF8Zmjix.cjs} +1 -1
  49. package/dist/{ld-Cf4CPWN_.mjs → ld-BDMNyM2M.mjs} +40 -5
  50. package/dist/{metrics-CocsZP0E.mjs → metrics-BbBj-VZN.mjs} +2 -1
  51. package/dist/{middleware-Cne_jMZo.mjs → middleware-CE1X7-UM.mjs} +1 -1
  52. package/dist/{middleware-Dxn0E9ES.mjs → middleware-DAA7wDdS.mjs} +520 -131
  53. package/dist/{middleware-BfYke3YQ.js → middleware-DfUWPAa-.js} +552 -122
  54. package/dist/{mod-C7_2j7VT.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  55. package/dist/{mod-BlLwewYe.d.ts → mod-CavhAgLw.d.cts} +5 -255
  56. package/dist/{mod-VHYh1lTK.d.cts → mod-CtpF2BU5.d.ts} +5 -255
  57. package/dist/{mod-B8MI1mpi.d.ts → mod-DEZXwV6J.d.ts} +1 -1
  58. package/dist/{mod-DFL6IxYQ.d.cts → mod-Dw-nPxQh.d.cts} +1 -1
  59. package/dist/{mod-BhTaDR5H.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  60. package/dist/mod.cjs +4 -4
  61. package/dist/mod.d.cts +7 -7
  62. package/dist/mod.d.ts +7 -7
  63. package/dist/mod.js +4 -4
  64. package/dist/nodeinfo/handler.test.mjs +1 -1
  65. package/dist/{owner-DSJ7nFgy.mjs → owner-CzUycuhd.mjs} +3 -3
  66. package/dist/{portable-collection-BuBV6xTo.js → portable-collection-COcJxZg_.js} +1 -1
  67. package/dist/{portable-collection-qskqXJrH.mjs → portable-collection-CTcNJiAa.mjs} +2 -2
  68. package/dist/{portable-collection-r-mHg1PV.cjs → portable-collection-cPMffcHJ.cjs} +1 -1
  69. package/dist/{portable-key-woGH_1nn.mjs → portable-key-B4QN3OzR.mjs} +1 -1
  70. package/dist/{proof-8WiH_-Gq.mjs → proof-nce4pf5S.mjs} +133 -16
  71. package/dist/{send-JKzZBgPU.mjs → send-4Idpe2z0.mjs} +3 -3
  72. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  73. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  74. package/dist/sig/compound-proof-verification.test.mjs +19 -2
  75. package/dist/sig/compound-proof.test.mjs +2 -2
  76. package/dist/sig/http.test.mjs +2 -2
  77. package/dist/sig/key.test.mjs +1 -1
  78. package/dist/sig/ld.test.mjs +2 -2
  79. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  80. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  81. package/dist/sig/mod.cjs +2 -2
  82. package/dist/sig/mod.d.cts +2 -2
  83. package/dist/sig/mod.d.ts +2 -2
  84. package/dist/sig/mod.js +2 -2
  85. package/dist/sig/owner.test.mjs +1 -1
  86. package/dist/sig/portable-collection.test.mjs +1 -1
  87. package/dist/sig/portable-interop.test.mjs +3 -3
  88. package/dist/sig/portable-key.test.mjs +6 -6
  89. package/dist/sig/proof.test.mjs +76 -3
  90. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  91. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  92. package/dist/sig/verification.test.d.mts +2 -0
  93. package/dist/sig/verification.test.mjs +310 -0
  94. package/dist/{temporal-CT3fRaMH.mjs → temporal-BbjmPHYn.mjs} +1 -1
  95. package/dist/testing/kv.test.mjs +1 -1
  96. package/dist/testing/mod.d.mts +315 -28
  97. package/dist/utils/docloader.test.mjs +2 -2
  98. package/dist/utils/kv-cache.test.mjs +1 -1
  99. package/dist/utils/mod.cjs +1 -1
  100. package/dist/utils/mod.d.cts +1 -1
  101. package/dist/utils/mod.d.ts +1 -1
  102. package/dist/utils/mod.js +1 -1
  103. package/dist/verification-C3954hQh.mjs +5 -0
  104. package/dist/verification-Dpfcw8ml.mjs +105 -0
  105. package/package.json +7 -7
@@ -15,7 +15,7 @@ import serialize from "json-canon";
15
15
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
16
16
  //#region deno.json
17
17
  var name = "@fedify/fedify";
18
- var version = "2.4.0-dev.2256+683c50ba";
18
+ var version = "2.4.0-dev.2260+42356e7d";
19
19
  //#endregion
20
20
  //#region src/sig/portable-key-id.ts
21
21
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -258,6 +258,106 @@ async function isSamePublicKey(a, b) {
258
258
  return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
259
259
  }
260
260
  //#endregion
261
+ //#region src/sig/verification.ts
262
+ const verificationObservation = Symbol("verificationObservation");
263
+ function snapshotKey(key) {
264
+ if (key.publicKey == null) throw new TypeError("Verification key has no public key.");
265
+ const id = key.id == null ? null : new URL(key.id.href);
266
+ return key instanceof CryptographicKey ? {
267
+ type: "cryptographicKey",
268
+ id,
269
+ ownerId: key.ownerId == null ? null : new URL(key.ownerId.href),
270
+ publicKey: key.publicKey
271
+ } : {
272
+ type: "multikey",
273
+ id,
274
+ controllerId: key.controllerId == null ? null : new URL(key.controllerId.href),
275
+ publicKey: key.publicKey
276
+ };
277
+ }
278
+ function triedKey(options, key) {
279
+ const check = options[verificationObservation]?.check;
280
+ if (check != null) check.triedKeys.push(snapshotKey(key));
281
+ }
282
+ async function observeCheck(options, identity, declaredKeyId, operation) {
283
+ const observation = options[verificationObservation];
284
+ if (observation?.attempt == null) return await operation(observation ?? { attempts: [] });
285
+ const check = {
286
+ identity,
287
+ declaredKeyId,
288
+ triedKeys: []
289
+ };
290
+ const base = {
291
+ ...identity,
292
+ declaredKeyId,
293
+ triedKeys: check.triedKeys
294
+ };
295
+ try {
296
+ const key = await operation({
297
+ ...observation,
298
+ check
299
+ });
300
+ observation.attempt.checks.push(key == null ? {
301
+ ...base,
302
+ status: "rejected",
303
+ reason: check.reason ?? { type: "invalidSignature" }
304
+ } : {
305
+ ...base,
306
+ status: "verified",
307
+ key: check.triedKeys.at(-1) ?? snapshotKey(key)
308
+ });
309
+ return key;
310
+ } catch (error) {
311
+ observation.attempt.checks.push({
312
+ ...base,
313
+ status: "error",
314
+ error
315
+ });
316
+ throw error;
317
+ }
318
+ }
319
+ async function observeAttempt(options, mechanism, operation, isVerified) {
320
+ const observation = options[verificationObservation];
321
+ if (observation == null) return await operation({ attempts: [] });
322
+ const attempt = { checks: [] };
323
+ const base = {
324
+ mechanism,
325
+ subject: observation.subject == null ? {
326
+ id: null,
327
+ pointer: ""
328
+ } : {
329
+ id: observation.subject.id == null ? null : new URL(observation.subject.id.href),
330
+ pointer: observation.subject.pointer
331
+ },
332
+ checks: attempt.checks
333
+ };
334
+ try {
335
+ const value = await operation({
336
+ ...observation,
337
+ subject: base.subject,
338
+ attempt,
339
+ check: void 0
340
+ });
341
+ observation.attempts.push(isVerified(value) ? {
342
+ ...base,
343
+ status: "verified",
344
+ signatures: attempt.checks.filter((check) => check.status === "verified")
345
+ } : {
346
+ ...base,
347
+ status: "rejected",
348
+ reason: attempt.reason ?? { type: "signatureVerificationFailed" }
349
+ });
350
+ return value;
351
+ } catch (error) {
352
+ observation.attempts.push({
353
+ ...base,
354
+ status: "error",
355
+ error
356
+ });
357
+ throw error;
358
+ }
359
+ }
360
+ //#endregion
261
361
  //#region src/sig/accept.ts
262
362
  /**
263
363
  * `Accept-Signature` header parsing, serialization, and validation utilities
@@ -714,6 +814,7 @@ var FederationMetrics = class {
714
814
  };
715
815
  if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
716
816
  if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
817
+ if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
717
818
  if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
718
819
  if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
719
820
  this.signatureVerificationDuration.record(durationMs, attributes);
@@ -2217,12 +2318,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
2217
2318
  };
2218
2319
  }
2219
2320
  async function fetchKeyInternal(keyId, cls, options = {}) {
2220
- return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
2321
+ return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, async (_cacheKey, _keyId, _keyCache, _logger) => {
2322
+ const check = options[verificationObservation]?.check;
2323
+ if (check != null) try {
2324
+ const result = await _keyCache?.getFetchError?.(_cacheKey);
2325
+ if (result != null) check.reason = {
2326
+ type: "keyFetchError",
2327
+ keyId: new URL(_cacheKey.href),
2328
+ result
2329
+ };
2330
+ } catch {}
2221
2331
  return {
2222
2332
  key: null,
2223
2333
  cached: true
2224
2334
  };
2225
2335
  }, async (error, cacheKey, keyId, keyCache, logger) => {
2336
+ const check = options[verificationObservation]?.check;
2337
+ if (check != null) {
2338
+ let result;
2339
+ try {
2340
+ result = error instanceof FetchError && error.response != null ? {
2341
+ status: error.response.status,
2342
+ response: error.response.clone()
2343
+ } : { error: error instanceof Error ? error : new Error(String(error)) };
2344
+ } catch {
2345
+ result = { error: error instanceof Error ? error : /* @__PURE__ */ new Error() };
2346
+ }
2347
+ check.reason = {
2348
+ type: "keyFetchError",
2349
+ keyId: new URL(cacheKey.href),
2350
+ result
2351
+ };
2352
+ }
2226
2353
  logger.debug("Failed to fetch key {keyId}.", {
2227
2354
  keyId,
2228
2355
  error
@@ -2679,6 +2806,13 @@ function detachSignature(jsonLd) {
2679
2806
  */
2680
2807
  async function verifySignature(jsonLd, options = {}) {
2681
2808
  if (!hasSignature(jsonLd)) return null;
2809
+ if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) {
2810
+ const declared = getLdSignatureObject(jsonLd)?.creator;
2811
+ return await observeCheck(options, { mechanism: "linkedData" }, typeof declared === "string" ? declared : null, (observation) => verifySignature(jsonLd, {
2812
+ ...options,
2813
+ [verificationObservation]: observation
2814
+ }));
2815
+ }
2682
2816
  const sig = jsonLd.signature;
2683
2817
  let signature;
2684
2818
  try {
@@ -2724,6 +2858,7 @@ async function verifySignature(jsonLd, options = {}) {
2724
2858
  const encoder = new TextEncoder();
2725
2859
  const message = sigOptsHash + docHash;
2726
2860
  const messageBytes = encoder.encode(message);
2861
+ triedKey(options, key);
2727
2862
  if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
2728
2863
  if (cached) {
2729
2864
  logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
@@ -2735,6 +2870,7 @@ async function verifySignature(jsonLd, options = {}) {
2735
2870
  keyCache: bypassKeyCacheReads(options.keyCache)
2736
2871
  }));
2737
2872
  if (key == null) return null;
2873
+ triedKey(options, key);
2738
2874
  return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
2739
2875
  }
2740
2876
  logger$4.debug("Failed to verify with the fetched key {keyId}; signature {signatureValue} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
@@ -2788,6 +2924,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
2788
2924
  return await verifyJsonLdInternal(jsonLd, options, false);
2789
2925
  }
2790
2926
  async function verifyJsonLdInternal(jsonLd, options, compact) {
2927
+ if (options[verificationObservation]?.attempt == null) return await observeAttempt({
2928
+ ...options,
2929
+ [verificationObservation]: options[verificationObservation] ?? { attempts: [] }
2930
+ }, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
2931
+ ...options,
2932
+ [verificationObservation]: observation
2933
+ }, compact), (value) => value);
2934
+ const observation = options[verificationObservation];
2791
2935
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
2792
2936
  const start = performance.now();
2793
2937
  let verified = false;
@@ -2800,6 +2944,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2800
2944
  } : options;
2801
2945
  const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
2802
2946
  const object = await Object$1.fromJsonLd(compacted, verificationOptions);
2947
+ observation?.parsedObject?.(object);
2948
+ if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
2803
2949
  if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
2804
2950
  span.setAttribute("activitypub.object.type", getTypeId(object).href);
2805
2951
  const sig = getLdSignatureObject(jsonLd);
@@ -2814,13 +2960,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2814
2960
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
2815
2961
  if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
2816
2962
  const key = await verifySignature(compacted, verificationOptions);
2817
- if (key == null) return false;
2963
+ if (key == null) {
2964
+ if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
2965
+ return false;
2966
+ }
2818
2967
  if (key.ownerId == null) {
2968
+ if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
2819
2969
  logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
2820
2970
  return false;
2821
2971
  }
2822
2972
  attributions.delete(key.ownerId.href);
2823
2973
  if (attributions.size > 0) {
2974
+ if (observation?.attempt != null) observation.attempt.reason = {
2975
+ type: "uncoveredAttribution",
2976
+ attributionIds: [...attributions].map((id) => new URL(id))
2977
+ };
2824
2978
  logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
2825
2979
  return false;
2826
2980
  }
@@ -2834,8 +2988,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2834
2988
  });
2835
2989
  throw error;
2836
2990
  } finally {
2991
+ if (observation?.attempt?.reason != null) {
2992
+ span.setAttribute("activitypub.verification.failure_reason", observation.attempt.reason.type);
2993
+ span.setStatus({ code: SpanStatusCode.ERROR });
2994
+ }
2837
2995
  const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
2838
- getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, { ldType: signatureType });
2996
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
2997
+ ldType: signatureType,
2998
+ verificationFailureReason: verified || threw ? void 0 : observation?.attempt?.reason?.type ?? (hasLdSignatureProperty(jsonLd) ? "signatureVerificationFailed" : "noSignature")
2999
+ });
2839
3000
  span.end();
2840
3001
  }
2841
3002
  });
@@ -3463,19 +3624,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
3463
3624
  */
3464
3625
  async function verifyMapLocalProof(jsonLd, options = {}) {
3465
3626
  if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
3627
+ const rawProof = jsonLd.proof;
3628
+ const previousChecks = options[verificationObservation]?.attempt?.checks.length;
3629
+ const malformed = () => observeCheck(options, {
3630
+ mechanism: "objectIntegrity",
3631
+ proofId: typeof rawProof.id === "string" ? rawProof.id : typeof rawProof["@id"] === "string" ? rawProof["@id"] : null,
3632
+ proofIndex: previousChecks ?? null
3633
+ }, getDeclaredProofKeyId(rawProof), () => Promise.resolve(null));
3466
3634
  const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
3467
3635
  try {
3468
3636
  const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
3469
- if (candidate.proof == null) return null;
3637
+ if (candidate.proof == null) return await malformed();
3470
3638
  return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
3471
3639
  proofContextLoader,
3472
3640
  proofPropertyMode: "literal"
3473
3641
  }, candidate);
3474
3642
  } catch {
3643
+ if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
3475
3644
  return null;
3476
3645
  }
3477
3646
  }
3647
+ function getDeclaredProofKeyId(value, propertyNames = ["verificationMethod", "https://w3id.org/security#verificationMethod"]) {
3648
+ if (!isJsonLdNode(value)) return null;
3649
+ const method = Array.from(propertyNames, (name) => value[name]).find((v) => v != null);
3650
+ const candidate = Array.isArray(method) ? method[0] : method;
3651
+ if (typeof candidate === "string") return candidate;
3652
+ return isJsonLdNode(candidate) && typeof candidate["@id"] === "string" ? candidate["@id"] : null;
3653
+ }
3478
3654
  async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
3655
+ if (options[verificationObservation] == null) return await verifyProofWithMessageDigestCache(jsonLd, proof, {
3656
+ ...options,
3657
+ [verificationObservation]: {
3658
+ attempts: [],
3659
+ attempt: { checks: [] },
3660
+ captureRawKeyIds: false
3661
+ }
3662
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
3663
+ if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) return await observeCheck(options, {
3664
+ mechanism: "objectIntegrity",
3665
+ proofId: proof.id?.href ?? null,
3666
+ proofIndex: options[verificationObservation].attempt.checks.length
3667
+ }, rawProofCandidate?.declaredKeyId ?? getDeclaredProofKeyId(rawProofCandidate?.value) ?? proof.verificationMethodId?.href ?? null, (observation) => verifyProofWithMessageDigestCache(jsonLd, proof, {
3668
+ ...options,
3669
+ [verificationObservation]: observation
3670
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller));
3479
3671
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
3480
3672
  const start = performance.now();
3481
3673
  let verified = false;
@@ -3488,8 +3680,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
3488
3680
  }
3489
3681
  try {
3490
3682
  const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
3491
- if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
3492
- else verified = true;
3683
+ if (key == null) {
3684
+ span.setStatus({ code: SpanStatusCode.ERROR });
3685
+ span.setAttribute("activitypub.verification.failure_reason", options[verificationObservation]?.check?.reason?.type ?? "invalidSignature");
3686
+ } else verified = true;
3493
3687
  return key;
3494
3688
  } catch (error) {
3495
3689
  threw = true;
@@ -3500,7 +3694,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
3500
3694
  throw error;
3501
3695
  } finally {
3502
3696
  const classified = threw ? "error" : verified ? "verified" : "rejected";
3503
- getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, { cryptosuite });
3697
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
3698
+ cryptosuite,
3699
+ verificationFailureReason: verified || threw ? void 0 : options[verificationObservation]?.check?.reason?.type ?? "invalidSignature"
3700
+ });
3504
3701
  span.end();
3505
3702
  }
3506
3703
  });
@@ -3517,14 +3714,15 @@ function expandContextPropertyIri(activeContext, key) {
3517
3714
  if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
3518
3715
  return key;
3519
3716
  }
3520
- async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
3717
+ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
3521
3718
  const names = new Set(defaults);
3522
3719
  const context = jsonLd["@context"] ?? inheritedContext;
3523
- if (context == null) return names;
3720
+ if (context == null && contextState?.initial == null) return names;
3524
3721
  try {
3525
3722
  const options = { documentLoader };
3526
- let activeContext = await jsonld.processContext(null, null, options);
3527
- activeContext = await jsonld.processContext(activeContext, context, options);
3723
+ let activeContext = contextState?.initial ?? await jsonld.processContext(null, null, options);
3724
+ if (contextState?.scoped != null) activeContext = await jsonld.processContext(activeContext, contextState.scoped, options);
3725
+ if (context != null) activeContext = await jsonld.processContext(activeContext, context, options);
3528
3726
  const typeScopedContext = activeContext;
3529
3727
  for (const key of globalThis.Object.keys(jsonLd).sort()) {
3530
3728
  if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
@@ -3536,6 +3734,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
3536
3734
  if (scopedContext != null) activeContext = await jsonld.processContext(activeContext, scopedContext, options);
3537
3735
  }
3538
3736
  }
3737
+ if (contextState != null) contextState.active = activeContext;
3539
3738
  for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
3540
3739
  } catch {
3541
3740
  if (rejectOnContextError) return null;
@@ -3602,6 +3801,36 @@ function sameBytes(left, right) {
3602
3801
  function sameProof(left, right) {
3603
3802
  return left.cryptosuite === right.cryptosuite && left.verificationMethodId?.href === right.verificationMethodId?.href && left.proofPurpose === right.proofPurpose && sameBytes(left.proofValue, right.proofValue) && left.created?.toString() === right.created?.toString();
3604
3803
  }
3804
+ function recordProofContexts(documentLoader) {
3805
+ const documents = /* @__PURE__ */ new Map();
3806
+ return {
3807
+ loader: async (url, options) => {
3808
+ const document = await documentLoader(url, options);
3809
+ documents.set(url, document);
3810
+ return document;
3811
+ },
3812
+ replay: (url, options) => {
3813
+ const document = documents.get(url);
3814
+ return document == null ? preloadedOnlyDocumentLoader(url, options) : Promise.resolve(document);
3815
+ }
3816
+ };
3817
+ }
3818
+ async function getAliasedDeclaredProofKeyId(value, inheritedContext, contextLoader) {
3819
+ if (!isJsonLdNode(value)) return null;
3820
+ const contextState = {};
3821
+ const names = await getJsonLdPropertyNames(value, "https://w3id.org/security#verificationMethod", ["verificationMethod", "https://w3id.org/security#verificationMethod"], contextLoader, inheritedContext, false, contextState);
3822
+ const methodName = Array.from(names ?? []).find((name) => value[name] != null);
3823
+ const method = methodName == null ? void 0 : value[methodName];
3824
+ const candidate = Array.isArray(method) ? method[0] : method;
3825
+ if (typeof candidate === "string") return candidate;
3826
+ if (!isJsonLdNode(candidate)) return null;
3827
+ const idNames = await getJsonLdPropertyNames(candidate, "@id", ["@id"], contextLoader, void 0, false, {
3828
+ initial: contextState.active,
3829
+ scoped: contextState.active == null || methodName == null ? void 0 : jsonld.getContextValue(contextState.active, methodName, "@context")
3830
+ });
3831
+ for (const name of idNames ?? []) if (typeof candidate[name] === "string") return candidate[name];
3832
+ return null;
3833
+ }
3605
3834
  function normalizeDocumentUrl(url) {
3606
3835
  try {
3607
3836
  return formatIri(parseIri(url));
@@ -3620,6 +3849,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
3620
3849
  const candidates = [];
3621
3850
  for (const value of values) {
3622
3851
  let parsed = null;
3852
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
3623
3853
  if (isJsonLdNode(value)) {
3624
3854
  const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
3625
3855
  "@context": jsonLd["@context"],
@@ -3628,14 +3858,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
3628
3858
  try {
3629
3859
  parsed = await DataIntegrityProof.fromJsonLd(proofJsonLd, {
3630
3860
  ...options,
3631
- contextLoader: documentLoader
3861
+ contextLoader: contexts?.loader ?? documentLoader
3632
3862
  });
3633
3863
  } catch {}
3634
3864
  }
3635
3865
  candidates.push({
3636
3866
  value,
3637
3867
  proof: parsed,
3638
- reference: getRawProofReference(value) ?? parsed?.id?.href
3868
+ reference: getRawProofReference(value) ?? parsed?.id?.href,
3869
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(value, jsonLd["@context"], contexts.replay)
3639
3870
  });
3640
3871
  }
3641
3872
  return candidates;
@@ -3859,8 +4090,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
3859
4090
  const digest = new Uint8Array(proofDigest.byteLength + 32);
3860
4091
  digest.set(new Uint8Array(proofDigest), 0);
3861
4092
  const proofValue = proof.proofValue;
4093
+ let keyTried = false;
3862
4094
  const verifyCandidate = async (msgDigest) => {
3863
4095
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
4096
+ if (!keyTried) {
4097
+ triedKey(options, publicKey);
4098
+ keyTried = true;
4099
+ }
3864
4100
  return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
3865
4101
  };
3866
4102
  const messageDigestKey = serialize(proofConfiguration.context ?? null);
@@ -4318,12 +4554,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
4318
4554
  * @since 0.10.0
4319
4555
  */
4320
4556
  async function verifyObject(cls, jsonLd, options = {}) {
4557
+ if (options[verificationObservation]?.attempt == null) {
4558
+ const observedOptions = {
4559
+ ...options,
4560
+ [verificationObservation]: options[verificationObservation] ?? {
4561
+ attempts: [],
4562
+ captureRawKeyIds: false
4563
+ }
4564
+ };
4565
+ return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
4566
+ try {
4567
+ return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
4568
+ const object = await verifyObject(cls, jsonLd, {
4569
+ ...options,
4570
+ [verificationObservation]: observation
4571
+ });
4572
+ if (object == null) {
4573
+ span.setStatus({ code: SpanStatusCode.ERROR });
4574
+ span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
4575
+ }
4576
+ return object;
4577
+ }, (value) => value != null);
4578
+ } catch (error) {
4579
+ span.setStatus({
4580
+ code: SpanStatusCode.ERROR,
4581
+ message: String(error)
4582
+ });
4583
+ throw error;
4584
+ } finally {
4585
+ span.end();
4586
+ }
4587
+ });
4588
+ }
4589
+ const observation = options[verificationObservation];
4321
4590
  const logger = getLogger([
4322
4591
  "fedify",
4323
4592
  "sig",
4324
4593
  "proof"
4325
4594
  ]);
4326
4595
  const object = await cls.fromJsonLd(jsonLd, options);
4596
+ observation?.parsedObject?.(object);
4597
+ if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
4327
4598
  const defaultDocumentLoader = getDocumentLoader();
4328
4599
  const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
4329
4600
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
@@ -4350,10 +4621,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
4350
4621
  if (candidateIndex >= 0) {
4351
4622
  hydratedCandidates.add(candidateIndex);
4352
4623
  let parsed = null;
4624
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
4353
4625
  try {
4354
4626
  parsed = await DataIntegrityProof.fromJsonLd(remoteDocument.document, {
4355
4627
  documentLoader: baseDocumentLoader,
4356
- contextLoader: proofContextLoader,
4628
+ contextLoader: contexts?.loader ?? proofContextLoader,
4357
4629
  tracerProvider: options.tracerProvider,
4358
4630
  baseUrl: parseIri(remoteDocument.documentUrl)
4359
4631
  });
@@ -4361,7 +4633,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
4361
4633
  rawProofCandidates[candidateIndex] = {
4362
4634
  value: structuredClone(remoteDocument.document),
4363
4635
  proof: parsed,
4364
- reference
4636
+ reference,
4637
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
4365
4638
  };
4366
4639
  }
4367
4640
  return remoteDocument;
@@ -4389,6 +4662,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
4389
4662
  return null;
4390
4663
  }
4391
4664
  if (attributions.size > 0) {
4665
+ if (observation?.attempt != null) observation.attempt.reason = observation.attempt.checks.length === 0 ? { type: "noSignature" } : {
4666
+ type: "uncoveredAttribution",
4667
+ attributionIds: [...attributions].map((id) => new URL(id))
4668
+ };
4392
4669
  logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
4393
4670
  return null;
4394
4671
  }
@@ -5408,6 +5685,14 @@ async function verifyRequest(request, options = {}) {
5408
5685
  * @since 2.1.0
5409
5686
  */
5410
5687
  async function verifyRequestDetailed(request, options = {}) {
5688
+ if (options[verificationObservation] != null && options[verificationObservation]?.attempt == null) return await observeAttempt(options, "http", async (observation) => {
5689
+ const result = await verifyRequestDetailed(request, {
5690
+ ...options,
5691
+ [verificationObservation]: observation
5692
+ });
5693
+ if (!result.verified) observation.attempt.reason = result.reason.type === "noSignature" ? { type: "noSignature" } : { type: "signatureVerificationFailed" };
5694
+ return result;
5695
+ }, (result) => result.verified);
5411
5696
  if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
5412
5697
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
5413
5698
  if (span.isRecording()) {
@@ -5446,7 +5731,27 @@ async function verifyRequestDetailed(request, options = {}) {
5446
5731
  }
5447
5732
  });
5448
5733
  }
5449
- async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
5734
+ async function verifyRequestDraft(request, span, metricsContext, options = {}) {
5735
+ if (options[verificationObservation]?.check != null) return await verifyRequestDraftInternal(request, span, metricsContext, options);
5736
+ let result = noSignatureResult();
5737
+ const raw = request.headers.get("Signature");
5738
+ const declared = raw == null ? null : parseDraftSignature(raw)?.keyId ?? null;
5739
+ if (raw == null) return result;
5740
+ await observeCheck(options, {
5741
+ mechanism: "http",
5742
+ spec: "draft-cavage-http-signatures-12",
5743
+ label: null
5744
+ }, declared, async (observation) => {
5745
+ result = await verifyRequestDraftInternal(request, span, metricsContext, {
5746
+ ...options,
5747
+ [verificationObservation]: observation
5748
+ });
5749
+ if (!result.verified && result.reason.type === "keyFetchError" && observation.check != null) observation.check.reason = result.reason;
5750
+ return result.verified ? result.key : null;
5751
+ });
5752
+ return result;
5753
+ }
5754
+ async function verifyRequestDraftInternal(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider, [verificationObservation]: observation } = {}) {
5450
5755
  const logger = getLogger([
5451
5756
  "fedify",
5452
5757
  "sig",
@@ -5627,6 +5932,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
5627
5932
  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");
5628
5933
  const sig = decodeBase64(signature);
5629
5934
  span?.setAttribute("http_signatures.signature", encodeHex(sig));
5935
+ triedKey({ [verificationObservation]: observation }, key);
5630
5936
  if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
5631
5937
  if (cached) {
5632
5938
  logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
@@ -5641,7 +5947,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
5641
5947
  currentTime,
5642
5948
  keyCache: bypassKeyCacheReads(keyCache),
5643
5949
  meterProvider,
5644
- tracerProvider
5950
+ tracerProvider,
5951
+ [verificationObservation]: observation
5645
5952
  });
5646
5953
  }
5647
5954
  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}", {
@@ -5727,7 +6034,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
5727
6034
  * @returns Whether the signature is verified, and the algorithm of the
5728
6035
  * signature if it is supported.
5729
6036
  */
5730
- async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
6037
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
5731
6038
  const logger = getLogger([
5732
6039
  "fedify",
5733
6040
  "sig",
@@ -5783,6 +6090,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
5783
6090
  }
5784
6091
  let verified;
5785
6092
  try {
6093
+ triedKey(observationOptions, key);
5786
6094
  verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
5787
6095
  } catch (error) {
5788
6096
  logger.debug("Error during signature verification: {error}", {
@@ -5810,7 +6118,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
5810
6118
  mismatched: true
5811
6119
  };
5812
6120
  }
5813
- async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
6121
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, [verificationObservation]: observation, meterProvider, tracerProvider } = {}) {
5814
6122
  const logger = getLogger([
5815
6123
  "fedify",
5816
6124
  "sig",
@@ -5874,105 +6182,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
5874
6182
  let body = null;
5875
6183
  let digestValid = null;
5876
6184
  for (const sigName of signatureNames) {
5877
- if (!signatures[sigName]) {
5878
- setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
5879
- continue;
5880
- }
5881
- const sigInput = signatureInputs[sigName];
5882
- const sigBytes = signatures[sigName];
5883
- const keyId = parseKeyId(sigInput.keyId);
5884
- if (!sigInput.keyId) {
5885
- logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
5886
- signatureName: sigName,
5887
- signatureInput: signatureInputHeader
5888
- });
5889
- setFailure(invalidSignatureResult(null));
5890
- continue;
5891
- }
5892
- if (!sigInput.created) {
5893
- logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
5894
- signatureName: sigName,
5895
- signatureInput: signatureInputHeader
5896
- });
5897
- setFailure(invalidSignatureResult(keyId));
5898
- continue;
5899
- }
5900
- const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
5901
- const now = currentTime ?? Temporal.Now.instant();
5902
- if (timeWindow !== false) {
5903
- const tw = timeWindow ?? { hours: 1 };
5904
- if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
5905
- logger.debug("Failed to verify; signature created time is too far in the future.", {
5906
- created: signatureCreated.toString(),
5907
- now: now.toString()
5908
- });
5909
- setFailure(invalidSignatureResult(keyId));
5910
- continue;
5911
- } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
5912
- logger.debug("Failed to verify; signature created time is too far in the past.", {
5913
- created: signatureCreated.toString(),
5914
- now: now.toString()
5915
- });
5916
- setFailure(invalidSignatureResult(keyId));
5917
- continue;
6185
+ let winning;
6186
+ await observeCheck({ [verificationObservation]: observation }, {
6187
+ mechanism: "http",
6188
+ spec: "rfc9421",
6189
+ label: sigName
6190
+ }, signatureInputs[sigName]?.keyId ?? null, async (candidateObservation) => {
6191
+ if (!signatures[sigName]) {
6192
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
6193
+ return null;
5918
6194
  }
5919
- }
5920
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
5921
- const contentDigestHeader = request.headers.get("Content-Digest");
5922
- if (!contentDigestHeader) {
5923
- logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
5924
- setFailure(invalidSignatureResult(keyId));
5925
- continue;
6195
+ const sigInput = signatureInputs[sigName];
6196
+ const sigBytes = signatures[sigName];
6197
+ const keyId = parseKeyId(sigInput.keyId);
6198
+ if (!sigInput.keyId) {
6199
+ logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
6200
+ signatureName: sigName,
6201
+ signatureInput: signatureInputHeader
6202
+ });
6203
+ setFailure(invalidSignatureResult(null));
6204
+ return null;
5926
6205
  }
5927
- body ??= await request.arrayBuffer();
5928
- digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
5929
- if (!digestValid) {
5930
- logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
6206
+ if (!sigInput.created) {
6207
+ logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
6208
+ signatureName: sigName,
6209
+ signatureInput: signatureInputHeader
6210
+ });
5931
6211
  setFailure(invalidSignatureResult(keyId));
5932
- continue;
6212
+ return null;
5933
6213
  }
5934
- }
5935
- span?.setAttribute("http_signatures.key_id", sigInput.keyId);
5936
- span?.setAttribute("http_signatures.created", sigInput.created.toString());
5937
- if (keyId == null) {
5938
- setFailure(invalidSignatureResult(null));
5939
- continue;
5940
- }
5941
- let lookup = keyLookups.get(keyId.href);
5942
- if (lookup == null) {
5943
- lookup = await lookUpKey(keyId, keyCache);
5944
- keyLookups.set(keyId.href, lookup);
5945
- }
5946
- while (true) {
5947
- const { key, cached, fetchError } = lookup;
5948
- if (fetchError != null) {
5949
- setFailure(keyFetchErrorResult(keyId, fetchError));
5950
- break;
6214
+ const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
6215
+ const now = currentTime ?? Temporal.Now.instant();
6216
+ if (timeWindow !== false) {
6217
+ const tw = timeWindow ?? { hours: 1 };
6218
+ if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
6219
+ logger.debug("Failed to verify; signature created time is too far in the future.", {
6220
+ created: signatureCreated.toString(),
6221
+ now: now.toString()
6222
+ });
6223
+ setFailure(invalidSignatureResult(keyId));
6224
+ return null;
6225
+ } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
6226
+ logger.debug("Failed to verify; signature created time is too far in the past.", {
6227
+ created: signatureCreated.toString(),
6228
+ now: now.toString()
6229
+ });
6230
+ setFailure(invalidSignatureResult(keyId));
6231
+ return null;
6232
+ }
5951
6233
  }
5952
- if (!key) {
5953
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
5954
- setFailure(invalidSignatureResult(keyId));
5955
- break;
6234
+ if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
6235
+ const contentDigestHeader = request.headers.get("Content-Digest");
6236
+ if (!contentDigestHeader) {
6237
+ logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
6238
+ setFailure(invalidSignatureResult(keyId));
6239
+ return null;
6240
+ }
6241
+ body ??= await request.arrayBuffer();
6242
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
6243
+ if (!digestValid) {
6244
+ logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
6245
+ setFailure(invalidSignatureResult(keyId));
6246
+ return null;
6247
+ }
5956
6248
  }
5957
- const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
5958
- if (result.verified) {
5959
- metricsContext.algorithm = result.algorithm;
5960
- return {
5961
- verified: true,
5962
- key,
5963
- signatureLabel: sigName
5964
- };
6249
+ span?.setAttribute("http_signatures.key_id", sigInput.keyId);
6250
+ span?.setAttribute("http_signatures.created", sigInput.created.toString());
6251
+ if (keyId == null) {
6252
+ setFailure(invalidSignatureResult(null));
6253
+ return null;
5965
6254
  }
5966
- if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
5967
- logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
5968
- refreshedKeyIds.add(keyId.href);
5969
- lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
6255
+ let lookup = keyLookups.get(keyId.href);
6256
+ if (lookup == null) {
6257
+ lookup = await lookUpKey(keyId, keyCache);
5970
6258
  keyLookups.set(keyId.href, lookup);
5971
- continue;
5972
6259
  }
5973
- setFailure(invalidSignatureResult(keyId), result.algorithm);
5974
- break;
5975
- }
6260
+ while (true) {
6261
+ const { key, cached, fetchError } = lookup;
6262
+ if (fetchError != null) {
6263
+ setFailure(keyFetchErrorResult(keyId, fetchError));
6264
+ break;
6265
+ }
6266
+ if (!key) {
6267
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
6268
+ setFailure(invalidSignatureResult(keyId));
6269
+ break;
6270
+ }
6271
+ const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, { [verificationObservation]: candidateObservation });
6272
+ if (result.verified) {
6273
+ metricsContext.algorithm = result.algorithm;
6274
+ winning = {
6275
+ verified: true,
6276
+ key,
6277
+ signatureLabel: sigName
6278
+ };
6279
+ return key;
6280
+ }
6281
+ if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
6282
+ logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
6283
+ refreshedKeyIds.add(keyId.href);
6284
+ lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
6285
+ keyLookups.set(keyId.href, lookup);
6286
+ continue;
6287
+ }
6288
+ setFailure(invalidSignatureResult(keyId), result.algorithm);
6289
+ break;
6290
+ }
6291
+ if (!failure.verified && failure.reason.type === "keyFetchError" && candidateObservation.check != null) candidateObservation.check.reason = failure.reason;
6292
+ return null;
6293
+ });
6294
+ if (winning != null) return winning;
5976
6295
  }
5977
6296
  metricsContext.algorithm = failureAlgorithm;
5978
6297
  return failure;
@@ -6205,4 +6524,4 @@ function timingSafeEqual(a, b) {
6205
6524
  return result === 0;
6206
6525
  }
6207
6526
  //#endregion
6208
- export { instrumentDocumentLoader as $, hasSignature as A, name as At, fetchActorDocument as B, InvalidContextReferenceError as C, hasPortableActor as Ct, createSignature as D, isPortableUri as Dt, compactJsonLd as E, isPortableKeyId as Et, verifyCompactJsonLd as F, validateCryptoKey as G, fetchKeyDetailed as H, verifyJsonLd as I, getVerifiedKeyOwnerEvidence as J, verifyKeyOwnership as K, verifySignature as L, isClearlyMalformedContextReference as M, isInvalidUrlTypeError as N, detachSignature as O, isSameObjectId as Ot, signJsonLd as P, getRemoteHost as Q, wrapContextLoaderForJsonLd as R, normalizeOutgoingActivityJsonLd as S, getRawCanonicalPortableId as St, attachSignature as T, isPortableId as Tt, generateCryptoKeyPair as U, fetchKey as V, importJwk as W, getDurationMs as X, parseKeyOwnerEvidence as Y, getFederationMetrics as Z, verifyObject as _, validateAcceptSignature as _t, signRequest as a, recordCollectionTotalItems as at, verifyPortableObjectProofWithRoot as b, getGatewayKeyBase as bt, verifyRequestDetailed as c, recordInboxActivity as ct, InvalidPortableObjectIdError as d, recordWebFingerHandle as dt, isAbortError$1 as et, classifyFep2277CoreType as f, registerQueueDepthGauge as ft, verifyMapLocalProof as g, parseAcceptSignature as gt, signObject as h, fulfillAcceptSignature as ht, selectRequestSignature as i, recordCollectionRequest as it, hasSignatureLike as j, version as jt, getNormalizationContextLoader as k, isSamePublicKey as kt, fetchPortableGatewayKey as l, recordOutboxActivity as lt, hasProofLike as m, formatAcceptSignature as mt, listRequestSignatures as n, recordCollectionDispatchDuration as nt, validateMaxSignatures as o, recordDocumentCache as ot, createProof as p, preloadedOnlyDocumentLoader as pt, MAX_PORTABLE_KEY_TTL as q, parseRfc9421SignatureInput as r, recordCollectionPageItems as rt, verifyRequest as s, recordFanoutRecipients as st, doubleKnock as t, recordCircuitBreakerStateChange as tt, resolvePortableActorKey as u, recordOutboxEnqueue as ut, verifyPortableObjectProof as v, getAuthenticationOrigin as vt, assertSafeJsonLd as w, isCompatibleKeyId as wt, verifyProof as x, getPortableDid as xt, verifyPortableObjectProofPolicy as y, getCanonicalPortableId as yt, exportJwk as z };
6527
+ export { instrumentDocumentLoader as $, hasSignature as A, isPortableUri as At, fetchActorDocument as B, InvalidContextReferenceError as C, getGatewayKeyBase as Ct, createSignature as D, isCompatibleKeyId as Dt, compactJsonLd as E, hasPortableActor as Et, verifyCompactJsonLd as F, validateCryptoKey as G, fetchKeyDetailed as H, verifyJsonLd as I, getVerifiedKeyOwnerEvidence as J, verifyKeyOwnership as K, verifySignature as L, isClearlyMalformedContextReference as M, isSamePublicKey as Mt, isInvalidUrlTypeError as N, name as Nt, detachSignature as O, isPortableId as Ot, signJsonLd as P, version as Pt, getRemoteHost as Q, wrapContextLoaderForJsonLd as R, normalizeOutgoingActivityJsonLd as S, getCanonicalPortableId as St, attachSignature as T, getRawCanonicalPortableId as Tt, generateCryptoKeyPair as U, fetchKey as V, importJwk as W, getDurationMs as X, parseKeyOwnerEvidence as Y, getFederationMetrics as Z, verifyObject as _, validateAcceptSignature as _t, signRequest as a, recordCollectionTotalItems as at, verifyPortableObjectProofWithRoot as b, verificationObservation as bt, verifyRequestDetailed as c, recordInboxActivity as ct, InvalidPortableObjectIdError as d, recordWebFingerHandle as dt, isAbortError$1 as et, classifyFep2277CoreType as f, registerQueueDepthGauge as ft, verifyMapLocalProof as g, parseAcceptSignature as gt, signObject as h, fulfillAcceptSignature as ht, selectRequestSignature as i, recordCollectionRequest as it, hasSignatureLike as j, isSameObjectId as jt, getNormalizationContextLoader as k, isPortableKeyId as kt, fetchPortableGatewayKey as l, recordOutboxActivity as lt, hasProofLike as m, formatAcceptSignature as mt, listRequestSignatures as n, recordCollectionDispatchDuration as nt, validateMaxSignatures as o, recordDocumentCache as ot, createProof as p, preloadedOnlyDocumentLoader as pt, MAX_PORTABLE_KEY_TTL as q, parseRfc9421SignatureInput as r, recordCollectionPageItems as rt, verifyRequest as s, recordFanoutRecipients as st, doubleKnock as t, recordCircuitBreakerStateChange as tt, resolvePortableActorKey as u, recordOutboxEnqueue as ut, verifyPortableObjectProof as v, observeAttempt as vt, assertSafeJsonLd as w, getPortableDid as wt, verifyProof as x, getAuthenticationOrigin as xt, verifyPortableObjectProofPolicy as y, snapshotKey as yt, exportJwk as z };