@fedify/fedify 2.4.0-dev.2253 → 2.4.0-dev.2259

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 (110) hide show
  1. package/dist/{builder-DdUWFCwL.mjs → builder-CBNa1Uw2.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-CcWDD5VB.mjs → compound-proof-CMPv4UK0.mjs} +20 -3
  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-D9uhdIuD.mjs → deno-C8XaXmPB.mjs} +1 -1
  9. package/dist/{docloader-fbmPhCdS.mjs → docloader-CpdFLwAg.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 +5 -5
  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 +4 -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-B858-Ylk.cjs → federation-D6giSGUT.cjs} +524 -118
  40. package/dist/{http-CUyBaVsQ.mjs → http-BqSbUEiQ.mjs} +141 -98
  41. package/dist/{http-Bp0MKAK3.cjs → http-Cki-npuA.cjs} +456 -116
  42. package/dist/{http-BoGE-nk7.js → http-OMtKlbx1.js} +439 -117
  43. package/dist/{http-DGx0KOws.d.ts → key-C1nv7_jD.d.ts} +638 -229
  44. package/dist/{http-BrwKINQn.d.cts → key-DYxuyd-s.d.cts} +638 -229
  45. package/dist/{key-BlTaKABr.mjs → key-DecAtABm.mjs} +30 -3
  46. package/dist/{kv-cache-Dmqd7s2a.mjs → kv-cache-CWWlMv-s.mjs} +1 -1
  47. package/dist/{kv-cache-BnWemxH2.cjs → kv-cache-D73yVcE5.cjs} +1 -1
  48. package/dist/{kv-cache-DhMzUXEE.js → kv-cache-J0v3UbUn.js} +1 -1
  49. package/dist/{ld-Dx8r_qJU.mjs → ld-UsN-TIPh.mjs} +40 -5
  50. package/dist/{metrics-DFkZkLrO.mjs → metrics-DWCn4VXX.mjs} +2 -1
  51. package/dist/{middleware-DC018B4u.js → middleware-BqzfIlUO.js} +524 -118
  52. package/dist/{middleware-B0RRI88k.mjs → middleware-CkjwiEBd.mjs} +512 -127
  53. package/dist/{middleware-BNhF-Ayx.mjs → middleware-DHn2q0L1.mjs} +1 -1
  54. package/dist/{mod-C7_2j7VT.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  55. package/dist/{mod-Cy9VNHVw.d.ts → mod-CavhAgLw.d.cts} +5 -253
  56. package/dist/{mod-DoTWIc7F.d.cts → mod-CtpF2BU5.d.ts} +5 -253
  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-B61jjp5K.mjs → owner-CEBn81LD.mjs} +3 -3
  66. package/dist/{portable-collection-DhEA_nWT.cjs → portable-collection-DqTUBKY7.cjs} +1 -1
  67. package/dist/{portable-collection-BnktjQ04.js → portable-collection-ZkUNjtb7.js} +1 -1
  68. package/dist/{portable-collection-CyBJDEzs.mjs → portable-collection-qFSYw4TG.mjs} +2 -2
  69. package/dist/{portable-key-CPN0QZSA.mjs → portable-key-LlMkP-vz.mjs} +1 -1
  70. package/dist/{proof-CZrjvoJ3.mjs → proof-BHRf_jMG.mjs} +142 -22
  71. package/dist/{send-enxOnmZo.mjs → send-DjG8DDU_.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.d.mts +2 -0
  80. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  81. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  82. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  83. package/dist/sig/mod.cjs +2 -2
  84. package/dist/sig/mod.d.cts +2 -2
  85. package/dist/sig/mod.d.ts +2 -2
  86. package/dist/sig/mod.js +2 -2
  87. package/dist/sig/owner.test.mjs +1 -1
  88. package/dist/sig/portable-collection.test.mjs +1 -1
  89. package/dist/sig/portable-interop.test.d.mts +2 -0
  90. package/dist/sig/portable-interop.test.mjs +156 -0
  91. package/dist/sig/portable-key.test.mjs +6 -6
  92. package/dist/sig/proof.test.mjs +161 -3
  93. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  94. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  95. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  96. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  97. package/dist/sig/verification.test.d.mts +2 -0
  98. package/dist/sig/verification.test.mjs +310 -0
  99. package/dist/{temporal-CBVv0xRB.mjs → temporal-BRFH_dte.mjs} +1 -1
  100. package/dist/testing/kv.test.mjs +1 -1
  101. package/dist/testing/mod.d.mts +315 -28
  102. package/dist/utils/docloader.test.mjs +2 -2
  103. package/dist/utils/kv-cache.test.mjs +1 -1
  104. package/dist/utils/mod.cjs +1 -1
  105. package/dist/utils/mod.d.cts +1 -1
  106. package/dist/utils/mod.d.ts +1 -1
  107. package/dist/utils/mod.js +1 -1
  108. package/dist/verification-C3954hQh.mjs +5 -0
  109. package/dist/verification-Dpfcw8ml.mjs +105 -0
  110. package/package.json +6 -6
@@ -9,12 +9,13 @@ import { encodeHex } from "byte-encodings/hex";
9
9
  import jsonld from "@fedify/vocab-runtime/jsonld";
10
10
  import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
11
11
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
12
+ import { getPortableActorGateways } from "@fedify/vocab-runtime/internal/jsonld-cache";
12
13
  import { isPlainJsonTree, retainSignedRepresentation } from "@fedify/vocab-runtime/internal/signed-representation";
13
14
  import serialize from "json-canon";
14
15
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
15
16
  //#region deno.json
16
17
  var name = "@fedify/fedify";
17
- var version = "2.4.0-dev.2253+f5212c33";
18
+ var version = "2.4.0-dev.2259+d9c843fc";
18
19
  //#endregion
19
20
  //#region src/sig/portable-key-id.ts
20
21
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -257,6 +258,106 @@ async function isSamePublicKey(a, b) {
257
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;
258
259
  }
259
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
260
361
  //#region src/sig/accept.ts
261
362
  /**
262
363
  * `Accept-Signature` header parsing, serialization, and validation utilities
@@ -713,6 +814,7 @@ var FederationMetrics = class {
713
814
  };
714
815
  if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
715
816
  if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
817
+ if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
716
818
  if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
717
819
  if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
718
820
  this.signatureVerificationDuration.record(durationMs, attributes);
@@ -2216,12 +2318,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
2216
2318
  };
2217
2319
  }
2218
2320
  async function fetchKeyInternal(keyId, cls, options = {}) {
2219
- 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 {}
2220
2331
  return {
2221
2332
  key: null,
2222
2333
  cached: true
2223
2334
  };
2224
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
+ }
2225
2353
  logger.debug("Failed to fetch key {keyId}.", {
2226
2354
  keyId,
2227
2355
  error
@@ -2678,6 +2806,13 @@ function detachSignature(jsonLd) {
2678
2806
  */
2679
2807
  async function verifySignature(jsonLd, options = {}) {
2680
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
+ }
2681
2816
  const sig = jsonLd.signature;
2682
2817
  let signature;
2683
2818
  try {
@@ -2723,6 +2858,7 @@ async function verifySignature(jsonLd, options = {}) {
2723
2858
  const encoder = new TextEncoder();
2724
2859
  const message = sigOptsHash + docHash;
2725
2860
  const messageBytes = encoder.encode(message);
2861
+ triedKey(options, key);
2726
2862
  if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
2727
2863
  if (cached) {
2728
2864
  logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
@@ -2734,6 +2870,7 @@ async function verifySignature(jsonLd, options = {}) {
2734
2870
  keyCache: bypassKeyCacheReads(options.keyCache)
2735
2871
  }));
2736
2872
  if (key == null) return null;
2873
+ triedKey(options, key);
2737
2874
  return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
2738
2875
  }
2739
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}", {
@@ -2787,6 +2924,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
2787
2924
  return await verifyJsonLdInternal(jsonLd, options, false);
2788
2925
  }
2789
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];
2790
2935
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
2791
2936
  const start = performance.now();
2792
2937
  let verified = false;
@@ -2799,6 +2944,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2799
2944
  } : options;
2800
2945
  const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
2801
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);
2802
2949
  if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
2803
2950
  span.setAttribute("activitypub.object.type", getTypeId(object).href);
2804
2951
  const sig = getLdSignatureObject(jsonLd);
@@ -2813,13 +2960,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2813
2960
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
2814
2961
  if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
2815
2962
  const key = await verifySignature(compacted, verificationOptions);
2816
- 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
+ }
2817
2967
  if (key.ownerId == null) {
2968
+ if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
2818
2969
  logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
2819
2970
  return false;
2820
2971
  }
2821
2972
  attributions.delete(key.ownerId.href);
2822
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
+ };
2823
2978
  logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
2824
2979
  return false;
2825
2980
  }
@@ -2833,8 +2988,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
2833
2988
  });
2834
2989
  throw error;
2835
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
+ }
2836
2995
  const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
2837
- 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
+ });
2838
3000
  span.end();
2839
3001
  }
2840
3002
  });
@@ -3462,19 +3624,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
3462
3624
  */
3463
3625
  async function verifyMapLocalProof(jsonLd, options = {}) {
3464
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));
3465
3634
  const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
3466
3635
  try {
3467
3636
  const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
3468
- if (candidate.proof == null) return null;
3637
+ if (candidate.proof == null) return await malformed();
3469
3638
  return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
3470
3639
  proofContextLoader,
3471
3640
  proofPropertyMode: "literal"
3472
3641
  }, candidate);
3473
3642
  } catch {
3643
+ if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
3474
3644
  return null;
3475
3645
  }
3476
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
+ }
3477
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));
3478
3671
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
3479
3672
  const start = performance.now();
3480
3673
  let verified = false;
@@ -3487,8 +3680,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
3487
3680
  }
3488
3681
  try {
3489
3682
  const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
3490
- if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
3491
- 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;
3492
3687
  return key;
3493
3688
  } catch (error) {
3494
3689
  threw = true;
@@ -3499,7 +3694,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
3499
3694
  throw error;
3500
3695
  } finally {
3501
3696
  const classified = threw ? "error" : verified ? "verified" : "rejected";
3502
- 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
+ });
3503
3701
  span.end();
3504
3702
  }
3505
3703
  });
@@ -3516,14 +3714,15 @@ function expandContextPropertyIri(activeContext, key) {
3516
3714
  if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
3517
3715
  return key;
3518
3716
  }
3519
- async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
3717
+ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
3520
3718
  const names = new Set(defaults);
3521
3719
  const context = jsonLd["@context"] ?? inheritedContext;
3522
- if (context == null) return names;
3720
+ if (context == null && contextState?.initial == null) return names;
3523
3721
  try {
3524
3722
  const options = { documentLoader };
3525
- let activeContext = await jsonld.processContext(null, null, options);
3526
- 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);
3527
3726
  const typeScopedContext = activeContext;
3528
3727
  for (const key of globalThis.Object.keys(jsonLd).sort()) {
3529
3728
  if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
@@ -3535,6 +3734,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
3535
3734
  if (scopedContext != null) activeContext = await jsonld.processContext(activeContext, scopedContext, options);
3536
3735
  }
3537
3736
  }
3737
+ if (contextState != null) contextState.active = activeContext;
3538
3738
  for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
3539
3739
  } catch {
3540
3740
  if (rejectOnContextError) return null;
@@ -3601,6 +3801,36 @@ function sameBytes(left, right) {
3601
3801
  function sameProof(left, right) {
3602
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();
3603
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
+ }
3604
3834
  function normalizeDocumentUrl(url) {
3605
3835
  try {
3606
3836
  return formatIri(parseIri(url));
@@ -3619,6 +3849,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
3619
3849
  const candidates = [];
3620
3850
  for (const value of values) {
3621
3851
  let parsed = null;
3852
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
3622
3853
  if (isJsonLdNode(value)) {
3623
3854
  const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
3624
3855
  "@context": jsonLd["@context"],
@@ -3627,14 +3858,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
3627
3858
  try {
3628
3859
  parsed = await DataIntegrityProof.fromJsonLd(proofJsonLd, {
3629
3860
  ...options,
3630
- contextLoader: documentLoader
3861
+ contextLoader: contexts?.loader ?? documentLoader
3631
3862
  });
3632
3863
  } catch {}
3633
3864
  }
3634
3865
  candidates.push({
3635
3866
  value,
3636
3867
  proof: parsed,
3637
- 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)
3638
3870
  });
3639
3871
  }
3640
3872
  return candidates;
@@ -3858,8 +4090,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
3858
4090
  const digest = new Uint8Array(proofDigest.byteLength + 32);
3859
4091
  digest.set(new Uint8Array(proofDigest), 0);
3860
4092
  const proofValue = proof.proofValue;
4093
+ let keyTried = false;
3861
4094
  const verifyCandidate = async (msgDigest) => {
3862
4095
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
4096
+ if (!keyTried) {
4097
+ triedKey(options, publicKey);
4098
+ keyTried = true;
4099
+ }
3863
4100
  return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
3864
4101
  };
3865
4102
  const messageDigestKey = serialize(proofConfiguration.context ?? null);
@@ -3904,7 +4141,6 @@ const FEP_2277_COLLECTION_PROPERTIES = [
3904
4141
  "prev",
3905
4142
  "current"
3906
4143
  ].map((property) => AS_NAMESPACE + property);
3907
- const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
3908
4144
  const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
3909
4145
  const FUNCTIONAL_PROOF_PROPERTIES = [
3910
4146
  `${SECURITY_NAMESPACE}cryptosuite`,
@@ -3935,14 +4171,15 @@ function hasValidPortableProofShape(proofValue) {
3935
4171
  * FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
3936
4172
  * an empty path, query, and fragment. Like the generated vocabulary decoder,
3937
4173
  * this also tolerates a plain set of values instead of a `@list`, and gateway
3938
- * strings given as `@value`s instead of `@id`s.
4174
+ * strings given as `@value`s instead of `@id`s. JCS-authenticated actors may
4175
+ * also supply an unmapped literal `gateways` list.
3939
4176
  */
3940
- function hasValidPortableActorGateways(node) {
3941
- const values = node[FEP_EF61_GATEWAYS];
4177
+ function hasValidPortableActorGateways(node, allowUnmapped) {
4178
+ const values = getPortableActorGateways(node, allowUnmapped);
3942
4179
  if (!Array.isArray(values)) return false;
3943
4180
  let items = values;
3944
4181
  if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
3945
- if (values.length !== 1) return false;
4182
+ if (values.length !== 1 || !isJsonLdNode(values[0])) return false;
3946
4183
  items = values[0]["@list"];
3947
4184
  }
3948
4185
  if (!Array.isArray(items) || items.length < 1) return false;
@@ -4163,7 +4400,7 @@ async function preparePortableObjectProof(jsonLd, options) {
4163
4400
  }
4164
4401
  };
4165
4402
  }
4166
- if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
4403
+ if (objectType === "actor" && !hasValidPortableActorGateways(root, proofs.every((proof) => proof.cryptosuite === "eddsa-jcs-2022"))) return {
4167
4404
  prepared: false,
4168
4405
  root,
4169
4406
  objectType,
@@ -4230,6 +4467,8 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
4230
4467
  * and fragment; otherwise, it is rejected with the `invalidGateways` reason
4231
4468
  * before its proofs are verified. The proofs are verified over the document as
4232
4469
  * given; a compatible identifier is never rewritten into a portable ID.
4470
+ * An unmapped literal `gateways` property is also accepted when every proof
4471
+ * uses `eddsa-jcs-2022`, which authenticates the original JSON properties.
4233
4472
  *
4234
4473
  * [FEP-ef61]: https://w3id.org/fep/ef61
4235
4474
  *
@@ -4315,12 +4554,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
4315
4554
  * @since 0.10.0
4316
4555
  */
4317
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];
4318
4590
  const logger = getLogger([
4319
4591
  "fedify",
4320
4592
  "sig",
4321
4593
  "proof"
4322
4594
  ]);
4323
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);
4324
4598
  const defaultDocumentLoader = getDocumentLoader();
4325
4599
  const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
4326
4600
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
@@ -4347,10 +4621,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
4347
4621
  if (candidateIndex >= 0) {
4348
4622
  hydratedCandidates.add(candidateIndex);
4349
4623
  let parsed = null;
4624
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
4350
4625
  try {
4351
4626
  parsed = await DataIntegrityProof.fromJsonLd(remoteDocument.document, {
4352
4627
  documentLoader: baseDocumentLoader,
4353
- contextLoader: proofContextLoader,
4628
+ contextLoader: contexts?.loader ?? proofContextLoader,
4354
4629
  tracerProvider: options.tracerProvider,
4355
4630
  baseUrl: parseIri(remoteDocument.documentUrl)
4356
4631
  });
@@ -4358,7 +4633,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
4358
4633
  rawProofCandidates[candidateIndex] = {
4359
4634
  value: structuredClone(remoteDocument.document),
4360
4635
  proof: parsed,
4361
- reference
4636
+ reference,
4637
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
4362
4638
  };
4363
4639
  }
4364
4640
  return remoteDocument;
@@ -4386,6 +4662,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
4386
4662
  return null;
4387
4663
  }
4388
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
+ };
4389
4669
  logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
4390
4670
  return null;
4391
4671
  }
@@ -5405,6 +5685,14 @@ async function verifyRequest(request, options = {}) {
5405
5685
  * @since 2.1.0
5406
5686
  */
5407
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);
5408
5696
  if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
5409
5697
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
5410
5698
  if (span.isRecording()) {
@@ -5443,7 +5731,27 @@ async function verifyRequestDetailed(request, options = {}) {
5443
5731
  }
5444
5732
  });
5445
5733
  }
5446
- 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 } = {}) {
5447
5755
  const logger = getLogger([
5448
5756
  "fedify",
5449
5757
  "sig",
@@ -5624,6 +5932,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
5624
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");
5625
5933
  const sig = decodeBase64(signature);
5626
5934
  span?.setAttribute("http_signatures.signature", encodeHex(sig));
5935
+ triedKey({ [verificationObservation]: observation }, key);
5627
5936
  if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
5628
5937
  if (cached) {
5629
5938
  logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
@@ -5638,7 +5947,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
5638
5947
  currentTime,
5639
5948
  keyCache: bypassKeyCacheReads(keyCache),
5640
5949
  meterProvider,
5641
- tracerProvider
5950
+ tracerProvider,
5951
+ [verificationObservation]: observation
5642
5952
  });
5643
5953
  }
5644
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}", {
@@ -5724,7 +6034,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
5724
6034
  * @returns Whether the signature is verified, and the algorithm of the
5725
6035
  * signature if it is supported.
5726
6036
  */
5727
- async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
6037
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
5728
6038
  const logger = getLogger([
5729
6039
  "fedify",
5730
6040
  "sig",
@@ -5780,6 +6090,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
5780
6090
  }
5781
6091
  let verified;
5782
6092
  try {
6093
+ triedKey(observationOptions, key);
5783
6094
  verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
5784
6095
  } catch (error) {
5785
6096
  logger.debug("Error during signature verification: {error}", {
@@ -5807,7 +6118,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
5807
6118
  mismatched: true
5808
6119
  };
5809
6120
  }
5810
- 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 } = {}) {
5811
6122
  const logger = getLogger([
5812
6123
  "fedify",
5813
6124
  "sig",
@@ -5871,105 +6182,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
5871
6182
  let body = null;
5872
6183
  let digestValid = null;
5873
6184
  for (const sigName of signatureNames) {
5874
- if (!signatures[sigName]) {
5875
- setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
5876
- continue;
5877
- }
5878
- const sigInput = signatureInputs[sigName];
5879
- const sigBytes = signatures[sigName];
5880
- const keyId = parseKeyId(sigInput.keyId);
5881
- if (!sigInput.keyId) {
5882
- logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
5883
- signatureName: sigName,
5884
- signatureInput: signatureInputHeader
5885
- });
5886
- setFailure(invalidSignatureResult(null));
5887
- continue;
5888
- }
5889
- if (!sigInput.created) {
5890
- logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
5891
- signatureName: sigName,
5892
- signatureInput: signatureInputHeader
5893
- });
5894
- setFailure(invalidSignatureResult(keyId));
5895
- continue;
5896
- }
5897
- const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
5898
- const now = currentTime ?? Temporal.Now.instant();
5899
- if (timeWindow !== false) {
5900
- const tw = timeWindow ?? { hours: 1 };
5901
- if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
5902
- logger.debug("Failed to verify; signature created time is too far in the future.", {
5903
- created: signatureCreated.toString(),
5904
- now: now.toString()
5905
- });
5906
- setFailure(invalidSignatureResult(keyId));
5907
- continue;
5908
- } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
5909
- logger.debug("Failed to verify; signature created time is too far in the past.", {
5910
- created: signatureCreated.toString(),
5911
- now: now.toString()
5912
- });
5913
- setFailure(invalidSignatureResult(keyId));
5914
- 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;
5915
6194
  }
5916
- }
5917
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
5918
- const contentDigestHeader = request.headers.get("Content-Digest");
5919
- if (!contentDigestHeader) {
5920
- logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
5921
- setFailure(invalidSignatureResult(keyId));
5922
- 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;
5923
6205
  }
5924
- body ??= await request.arrayBuffer();
5925
- digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
5926
- if (!digestValid) {
5927
- 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
+ });
5928
6211
  setFailure(invalidSignatureResult(keyId));
5929
- continue;
6212
+ return null;
5930
6213
  }
5931
- }
5932
- span?.setAttribute("http_signatures.key_id", sigInput.keyId);
5933
- span?.setAttribute("http_signatures.created", sigInput.created.toString());
5934
- if (keyId == null) {
5935
- setFailure(invalidSignatureResult(null));
5936
- continue;
5937
- }
5938
- let lookup = keyLookups.get(keyId.href);
5939
- if (lookup == null) {
5940
- lookup = await lookUpKey(keyId, keyCache);
5941
- keyLookups.set(keyId.href, lookup);
5942
- }
5943
- while (true) {
5944
- const { key, cached, fetchError } = lookup;
5945
- if (fetchError != null) {
5946
- setFailure(keyFetchErrorResult(keyId, fetchError));
5947
- 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
+ }
5948
6233
  }
5949
- if (!key) {
5950
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
5951
- setFailure(invalidSignatureResult(keyId));
5952
- 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
+ }
5953
6248
  }
5954
- const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
5955
- if (result.verified) {
5956
- metricsContext.algorithm = result.algorithm;
5957
- return {
5958
- verified: true,
5959
- key,
5960
- signatureLabel: sigName
5961
- };
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;
5962
6254
  }
5963
- if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
5964
- logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
5965
- refreshedKeyIds.add(keyId.href);
5966
- lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
6255
+ let lookup = keyLookups.get(keyId.href);
6256
+ if (lookup == null) {
6257
+ lookup = await lookUpKey(keyId, keyCache);
5967
6258
  keyLookups.set(keyId.href, lookup);
5968
- continue;
5969
6259
  }
5970
- setFailure(invalidSignatureResult(keyId), result.algorithm);
5971
- break;
5972
- }
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;
5973
6295
  }
5974
6296
  metricsContext.algorithm = failureAlgorithm;
5975
6297
  return failure;
@@ -6202,4 +6524,4 @@ function timingSafeEqual(a, b) {
6202
6524
  return result === 0;
6203
6525
  }
6204
6526
  //#endregion
6205
- 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 };