@fedify/fedify 2.4.0-dev.2256 → 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 (105) hide show
  1. package/dist/{builder-DvSQqk2l.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-5bg_j_ye.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-AmcRMq5z.mjs → deno-C8XaXmPB.mjs} +1 -1
  9. package/dist/{docloader-CIP_SG39.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-BDzIgqOd.cjs → federation-D6giSGUT.cjs} +524 -118
  40. package/dist/{http-DkqWu-sm.mjs → http-BqSbUEiQ.mjs} +141 -98
  41. package/dist/{http-1fZ83jj7.cjs → http-Cki-npuA.cjs} +447 -110
  42. package/dist/{http-BGb3QioZ.js → http-OMtKlbx1.js} +430 -111
  43. package/dist/{http-BrwKINQn.d.cts → key-C1nv7_jD.d.ts} +638 -229
  44. package/dist/{http-DGx0KOws.d.ts → key-DYxuyd-s.d.cts} +638 -229
  45. package/dist/{key-CEsu7oaD.mjs → key-DecAtABm.mjs} +30 -3
  46. package/dist/{kv-cache-CJS4mImS.mjs → kv-cache-CWWlMv-s.mjs} +1 -1
  47. package/dist/{kv-cache-BEWuZSB4.cjs → kv-cache-D73yVcE5.cjs} +1 -1
  48. package/dist/{kv-cache-CGhEgt98.js → kv-cache-J0v3UbUn.js} +1 -1
  49. package/dist/{ld-Cf4CPWN_.mjs → ld-UsN-TIPh.mjs} +40 -5
  50. package/dist/{metrics-CocsZP0E.mjs → metrics-DWCn4VXX.mjs} +2 -1
  51. package/dist/{middleware-BfYke3YQ.js → middleware-BqzfIlUO.js} +524 -118
  52. package/dist/{middleware-Dxn0E9ES.mjs → middleware-CkjwiEBd.mjs} +512 -127
  53. package/dist/{middleware-Cne_jMZo.mjs → middleware-DHn2q0L1.mjs} +1 -1
  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-CEBn81LD.mjs} +3 -3
  66. package/dist/{portable-collection-r-mHg1PV.cjs → portable-collection-DqTUBKY7.cjs} +1 -1
  67. package/dist/{portable-collection-BuBV6xTo.js → portable-collection-ZkUNjtb7.js} +1 -1
  68. package/dist/{portable-collection-qskqXJrH.mjs → portable-collection-qFSYw4TG.mjs} +2 -2
  69. package/dist/{portable-key-woGH_1nn.mjs → portable-key-LlMkP-vz.mjs} +1 -1
  70. package/dist/{proof-8WiH_-Gq.mjs → proof-BHRf_jMG.mjs} +133 -16
  71. package/dist/{send-JKzZBgPU.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.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-BRFH_dte.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
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  //#region deno.json
19
19
  var name = "@fedify/fedify";
20
- var version = "2.4.0-dev.2256+683c50ba";
20
+ var version = "2.4.0-dev.2259+d9c843fc";
21
21
  //#endregion
22
22
  //#region src/sig/portable-key-id.ts
23
23
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -3824,6 +3824,106 @@ const Temporal$5 = /*@__PURE__*/ Object.defineProperties({}, {
3824
3824
  const Temporal$4 = NativeTemporal || Temporal$5;
3825
3825
  NativeTemporal && Date.prototype.toTemporalInstant;
3826
3826
  //#endregion
3827
+ //#region src/sig/verification.ts
3828
+ const verificationObservation = Symbol("verificationObservation");
3829
+ function snapshotKey(key) {
3830
+ if (key.publicKey == null) throw new TypeError("Verification key has no public key.");
3831
+ const id = key.id == null ? null : new URL(key.id.href);
3832
+ return key instanceof _fedify_vocab.CryptographicKey ? {
3833
+ type: "cryptographicKey",
3834
+ id,
3835
+ ownerId: key.ownerId == null ? null : new URL(key.ownerId.href),
3836
+ publicKey: key.publicKey
3837
+ } : {
3838
+ type: "multikey",
3839
+ id,
3840
+ controllerId: key.controllerId == null ? null : new URL(key.controllerId.href),
3841
+ publicKey: key.publicKey
3842
+ };
3843
+ }
3844
+ function triedKey(options, key) {
3845
+ const check = options[verificationObservation]?.check;
3846
+ if (check != null) check.triedKeys.push(snapshotKey(key));
3847
+ }
3848
+ async function observeCheck(options, identity, declaredKeyId, operation) {
3849
+ const observation = options[verificationObservation];
3850
+ if (observation?.attempt == null) return await operation(observation ?? { attempts: [] });
3851
+ const check = {
3852
+ identity,
3853
+ declaredKeyId,
3854
+ triedKeys: []
3855
+ };
3856
+ const base = {
3857
+ ...identity,
3858
+ declaredKeyId,
3859
+ triedKeys: check.triedKeys
3860
+ };
3861
+ try {
3862
+ const key = await operation({
3863
+ ...observation,
3864
+ check
3865
+ });
3866
+ observation.attempt.checks.push(key == null ? {
3867
+ ...base,
3868
+ status: "rejected",
3869
+ reason: check.reason ?? { type: "invalidSignature" }
3870
+ } : {
3871
+ ...base,
3872
+ status: "verified",
3873
+ key: check.triedKeys.at(-1) ?? snapshotKey(key)
3874
+ });
3875
+ return key;
3876
+ } catch (error) {
3877
+ observation.attempt.checks.push({
3878
+ ...base,
3879
+ status: "error",
3880
+ error
3881
+ });
3882
+ throw error;
3883
+ }
3884
+ }
3885
+ async function observeAttempt(options, mechanism, operation, isVerified) {
3886
+ const observation = options[verificationObservation];
3887
+ if (observation == null) return await operation({ attempts: [] });
3888
+ const attempt = { checks: [] };
3889
+ const base = {
3890
+ mechanism,
3891
+ subject: observation.subject == null ? {
3892
+ id: null,
3893
+ pointer: ""
3894
+ } : {
3895
+ id: observation.subject.id == null ? null : new URL(observation.subject.id.href),
3896
+ pointer: observation.subject.pointer
3897
+ },
3898
+ checks: attempt.checks
3899
+ };
3900
+ try {
3901
+ const value = await operation({
3902
+ ...observation,
3903
+ subject: base.subject,
3904
+ attempt,
3905
+ check: void 0
3906
+ });
3907
+ observation.attempts.push(isVerified(value) ? {
3908
+ ...base,
3909
+ status: "verified",
3910
+ signatures: attempt.checks.filter((check) => check.status === "verified")
3911
+ } : {
3912
+ ...base,
3913
+ status: "rejected",
3914
+ reason: attempt.reason ?? { type: "signatureVerificationFailed" }
3915
+ });
3916
+ return value;
3917
+ } catch (error) {
3918
+ observation.attempts.push({
3919
+ ...base,
3920
+ status: "error",
3921
+ error
3922
+ });
3923
+ throw error;
3924
+ }
3925
+ }
3926
+ //#endregion
3827
3927
  //#region src/sig/accept.ts
3828
3928
  /**
3829
3929
  * `Accept-Signature` header parsing, serialization, and validation utilities
@@ -4280,6 +4380,7 @@ var FederationMetrics = class {
4280
4380
  };
4281
4381
  if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
4282
4382
  if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
4383
+ if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
4283
4384
  if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
4284
4385
  if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
4285
4386
  this.signatureVerificationDuration.record(durationMs, attributes);
@@ -5785,12 +5886,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
5785
5886
  };
5786
5887
  }
5787
5888
  async function fetchKeyInternal(keyId, cls, options = {}) {
5788
- return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
5889
+ return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, async (_cacheKey, _keyId, _keyCache, _logger) => {
5890
+ const check = options[verificationObservation]?.check;
5891
+ if (check != null) try {
5892
+ const result = await _keyCache?.getFetchError?.(_cacheKey);
5893
+ if (result != null) check.reason = {
5894
+ type: "keyFetchError",
5895
+ keyId: new URL(_cacheKey.href),
5896
+ result
5897
+ };
5898
+ } catch {}
5789
5899
  return {
5790
5900
  key: null,
5791
5901
  cached: true
5792
5902
  };
5793
5903
  }, async (error, cacheKey, keyId, keyCache, logger) => {
5904
+ const check = options[verificationObservation]?.check;
5905
+ if (check != null) {
5906
+ let result;
5907
+ try {
5908
+ result = error instanceof _fedify_vocab_runtime.FetchError && error.response != null ? {
5909
+ status: error.response.status,
5910
+ response: error.response.clone()
5911
+ } : { error: error instanceof Error ? error : new Error(String(error)) };
5912
+ } catch {
5913
+ result = { error: error instanceof Error ? error : /* @__PURE__ */ new Error() };
5914
+ }
5915
+ check.reason = {
5916
+ type: "keyFetchError",
5917
+ keyId: new URL(cacheKey.href),
5918
+ result
5919
+ };
5920
+ }
5794
5921
  logger.debug("Failed to fetch key {keyId}.", {
5795
5922
  keyId,
5796
5923
  error
@@ -6247,6 +6374,13 @@ function detachSignature(jsonLd) {
6247
6374
  */
6248
6375
  async function verifySignature(jsonLd, options = {}) {
6249
6376
  if (!hasSignature(jsonLd)) return null;
6377
+ if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) {
6378
+ const declared = getLdSignatureObject(jsonLd)?.creator;
6379
+ return await observeCheck(options, { mechanism: "linkedData" }, typeof declared === "string" ? declared : null, (observation) => verifySignature(jsonLd, {
6380
+ ...options,
6381
+ [verificationObservation]: observation
6382
+ }));
6383
+ }
6250
6384
  const sig = jsonLd.signature;
6251
6385
  let signature;
6252
6386
  try {
@@ -6292,6 +6426,7 @@ async function verifySignature(jsonLd, options = {}) {
6292
6426
  const encoder = new TextEncoder();
6293
6427
  const message = sigOptsHash + docHash;
6294
6428
  const messageBytes = encoder.encode(message);
6429
+ triedKey(options, key);
6295
6430
  if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
6296
6431
  if (cached) {
6297
6432
  logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
@@ -6303,6 +6438,7 @@ async function verifySignature(jsonLd, options = {}) {
6303
6438
  keyCache: bypassKeyCacheReads(options.keyCache)
6304
6439
  }));
6305
6440
  if (key == null) return null;
6441
+ triedKey(options, key);
6306
6442
  return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
6307
6443
  }
6308
6444
  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}", {
@@ -6356,6 +6492,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
6356
6492
  return await verifyJsonLdInternal(jsonLd, options, false);
6357
6493
  }
6358
6494
  async function verifyJsonLdInternal(jsonLd, options, compact) {
6495
+ if (options[verificationObservation]?.attempt == null) return await observeAttempt({
6496
+ ...options,
6497
+ [verificationObservation]: options[verificationObservation] ?? { attempts: [] }
6498
+ }, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
6499
+ ...options,
6500
+ [verificationObservation]: observation
6501
+ }, compact), (value) => value);
6502
+ const observation = options[verificationObservation];
6359
6503
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
6360
6504
  const start = performance.now();
6361
6505
  let verified = false;
@@ -6368,6 +6512,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6368
6512
  } : options;
6369
6513
  const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
6370
6514
  const object = await _fedify_vocab.Object.fromJsonLd(compacted, verificationOptions);
6515
+ observation?.parsedObject?.(object);
6516
+ if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
6371
6517
  if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
6372
6518
  span.setAttribute("activitypub.object.type", (0, _fedify_vocab.getTypeId)(object).href);
6373
6519
  const sig = getLdSignatureObject(jsonLd);
@@ -6382,13 +6528,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6382
6528
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
6383
6529
  if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
6384
6530
  const key = await verifySignature(compacted, verificationOptions);
6385
- if (key == null) return false;
6531
+ if (key == null) {
6532
+ if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
6533
+ return false;
6534
+ }
6386
6535
  if (key.ownerId == null) {
6536
+ if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
6387
6537
  logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
6388
6538
  return false;
6389
6539
  }
6390
6540
  attributions.delete(key.ownerId.href);
6391
6541
  if (attributions.size > 0) {
6542
+ if (observation?.attempt != null) observation.attempt.reason = {
6543
+ type: "uncoveredAttribution",
6544
+ attributionIds: [...attributions].map((id) => new URL(id))
6545
+ };
6392
6546
  logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
6393
6547
  return false;
6394
6548
  }
@@ -6402,8 +6556,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6402
6556
  });
6403
6557
  throw error;
6404
6558
  } finally {
6559
+ if (observation?.attempt?.reason != null) {
6560
+ span.setAttribute("activitypub.verification.failure_reason", observation.attempt.reason.type);
6561
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
6562
+ }
6405
6563
  const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
6406
- getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, { ldType: signatureType });
6564
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
6565
+ ldType: signatureType,
6566
+ verificationFailureReason: verified || threw ? void 0 : observation?.attempt?.reason?.type ?? (hasLdSignatureProperty(jsonLd) ? "signatureVerificationFailed" : "noSignature")
6567
+ });
6407
6568
  span.end();
6408
6569
  }
6409
6570
  });
@@ -7032,19 +7193,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
7032
7193
  */
7033
7194
  async function verifyMapLocalProof(jsonLd, options = {}) {
7034
7195
  if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
7196
+ const rawProof = jsonLd.proof;
7197
+ const previousChecks = options[verificationObservation]?.attempt?.checks.length;
7198
+ const malformed = () => observeCheck(options, {
7199
+ mechanism: "objectIntegrity",
7200
+ proofId: typeof rawProof.id === "string" ? rawProof.id : typeof rawProof["@id"] === "string" ? rawProof["@id"] : null,
7201
+ proofIndex: previousChecks ?? null
7202
+ }, getDeclaredProofKeyId(rawProof), () => Promise.resolve(null));
7035
7203
  const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
7036
7204
  try {
7037
7205
  const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
7038
- if (candidate.proof == null) return null;
7206
+ if (candidate.proof == null) return await malformed();
7039
7207
  return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
7040
7208
  proofContextLoader,
7041
7209
  proofPropertyMode: "literal"
7042
7210
  }, candidate);
7043
7211
  } catch {
7212
+ if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
7044
7213
  return null;
7045
7214
  }
7046
7215
  }
7216
+ function getDeclaredProofKeyId(value, propertyNames = ["verificationMethod", "https://w3id.org/security#verificationMethod"]) {
7217
+ if (!isJsonLdNode(value)) return null;
7218
+ const method = Array.from(propertyNames, (name) => value[name]).find((v) => v != null);
7219
+ const candidate = Array.isArray(method) ? method[0] : method;
7220
+ if (typeof candidate === "string") return candidate;
7221
+ return isJsonLdNode(candidate) && typeof candidate["@id"] === "string" ? candidate["@id"] : null;
7222
+ }
7047
7223
  async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
7224
+ if (options[verificationObservation] == null) return await verifyProofWithMessageDigestCache(jsonLd, proof, {
7225
+ ...options,
7226
+ [verificationObservation]: {
7227
+ attempts: [],
7228
+ attempt: { checks: [] },
7229
+ captureRawKeyIds: false
7230
+ }
7231
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
7232
+ if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) return await observeCheck(options, {
7233
+ mechanism: "objectIntegrity",
7234
+ proofId: proof.id?.href ?? null,
7235
+ proofIndex: options[verificationObservation].attempt.checks.length
7236
+ }, rawProofCandidate?.declaredKeyId ?? getDeclaredProofKeyId(rawProofCandidate?.value) ?? proof.verificationMethodId?.href ?? null, (observation) => verifyProofWithMessageDigestCache(jsonLd, proof, {
7237
+ ...options,
7238
+ [verificationObservation]: observation
7239
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller));
7048
7240
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
7049
7241
  const start = performance.now();
7050
7242
  let verified = false;
@@ -7057,8 +7249,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
7057
7249
  }
7058
7250
  try {
7059
7251
  const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
7060
- if (key == null) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
7061
- else verified = true;
7252
+ if (key == null) {
7253
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
7254
+ span.setAttribute("activitypub.verification.failure_reason", options[verificationObservation]?.check?.reason?.type ?? "invalidSignature");
7255
+ } else verified = true;
7062
7256
  return key;
7063
7257
  } catch (error) {
7064
7258
  threw = true;
@@ -7069,7 +7263,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
7069
7263
  throw error;
7070
7264
  } finally {
7071
7265
  const classified = threw ? "error" : verified ? "verified" : "rejected";
7072
- getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, { cryptosuite });
7266
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
7267
+ cryptosuite,
7268
+ verificationFailureReason: verified || threw ? void 0 : options[verificationObservation]?.check?.reason?.type ?? "invalidSignature"
7269
+ });
7073
7270
  span.end();
7074
7271
  }
7075
7272
  });
@@ -7086,14 +7283,15 @@ function expandContextPropertyIri(activeContext, key) {
7086
7283
  if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
7087
7284
  return key;
7088
7285
  }
7089
- async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
7286
+ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
7090
7287
  const names = new Set(defaults);
7091
7288
  const context = jsonLd["@context"] ?? inheritedContext;
7092
- if (context == null) return names;
7289
+ if (context == null && contextState?.initial == null) return names;
7093
7290
  try {
7094
7291
  const options = { documentLoader };
7095
- let activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
7096
- activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, context, options);
7292
+ let activeContext = contextState?.initial ?? await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
7293
+ if (contextState?.scoped != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, contextState.scoped, options);
7294
+ if (context != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, context, options);
7097
7295
  const typeScopedContext = activeContext;
7098
7296
  for (const key of globalThis.Object.keys(jsonLd).sort()) {
7099
7297
  if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
@@ -7105,6 +7303,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
7105
7303
  if (scopedContext != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, scopedContext, options);
7106
7304
  }
7107
7305
  }
7306
+ if (contextState != null) contextState.active = activeContext;
7108
7307
  for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
7109
7308
  } catch {
7110
7309
  if (rejectOnContextError) return null;
@@ -7171,6 +7370,36 @@ function sameBytes(left, right) {
7171
7370
  function sameProof(left, right) {
7172
7371
  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();
7173
7372
  }
7373
+ function recordProofContexts(documentLoader) {
7374
+ const documents = /* @__PURE__ */ new Map();
7375
+ return {
7376
+ loader: async (url, options) => {
7377
+ const document = await documentLoader(url, options);
7378
+ documents.set(url, document);
7379
+ return document;
7380
+ },
7381
+ replay: (url, options) => {
7382
+ const document = documents.get(url);
7383
+ return document == null ? preloadedOnlyDocumentLoader(url, options) : Promise.resolve(document);
7384
+ }
7385
+ };
7386
+ }
7387
+ async function getAliasedDeclaredProofKeyId(value, inheritedContext, contextLoader) {
7388
+ if (!isJsonLdNode(value)) return null;
7389
+ const contextState = {};
7390
+ const names = await getJsonLdPropertyNames(value, "https://w3id.org/security#verificationMethod", ["verificationMethod", "https://w3id.org/security#verificationMethod"], contextLoader, inheritedContext, false, contextState);
7391
+ const methodName = Array.from(names ?? []).find((name) => value[name] != null);
7392
+ const method = methodName == null ? void 0 : value[methodName];
7393
+ const candidate = Array.isArray(method) ? method[0] : method;
7394
+ if (typeof candidate === "string") return candidate;
7395
+ if (!isJsonLdNode(candidate)) return null;
7396
+ const idNames = await getJsonLdPropertyNames(candidate, "@id", ["@id"], contextLoader, void 0, false, {
7397
+ initial: contextState.active,
7398
+ scoped: contextState.active == null || methodName == null ? void 0 : _fedify_vocab_runtime_jsonld.default.getContextValue(contextState.active, methodName, "@context")
7399
+ });
7400
+ for (const name of idNames ?? []) if (typeof candidate[name] === "string") return candidate[name];
7401
+ return null;
7402
+ }
7174
7403
  function normalizeDocumentUrl(url) {
7175
7404
  try {
7176
7405
  return (0, _fedify_vocab_runtime.formatIri)((0, _fedify_vocab_runtime.parseIri)(url));
@@ -7189,6 +7418,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
7189
7418
  const candidates = [];
7190
7419
  for (const value of values) {
7191
7420
  let parsed = null;
7421
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
7192
7422
  if (isJsonLdNode(value)) {
7193
7423
  const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
7194
7424
  "@context": jsonLd["@context"],
@@ -7197,14 +7427,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
7197
7427
  try {
7198
7428
  parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofJsonLd, {
7199
7429
  ...options,
7200
- contextLoader: documentLoader
7430
+ contextLoader: contexts?.loader ?? documentLoader
7201
7431
  });
7202
7432
  } catch {}
7203
7433
  }
7204
7434
  candidates.push({
7205
7435
  value,
7206
7436
  proof: parsed,
7207
- reference: getRawProofReference(value) ?? parsed?.id?.href
7437
+ reference: getRawProofReference(value) ?? parsed?.id?.href,
7438
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(value, jsonLd["@context"], contexts.replay)
7208
7439
  });
7209
7440
  }
7210
7441
  return candidates;
@@ -7428,8 +7659,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
7428
7659
  const digest = new Uint8Array(proofDigest.byteLength + 32);
7429
7660
  digest.set(new Uint8Array(proofDigest), 0);
7430
7661
  const proofValue = proof.proofValue;
7662
+ let keyTried = false;
7431
7663
  const verifyCandidate = async (msgDigest) => {
7432
7664
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
7665
+ if (!keyTried) {
7666
+ triedKey(options, publicKey);
7667
+ keyTried = true;
7668
+ }
7433
7669
  return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
7434
7670
  };
7435
7671
  const messageDigestKey = (0, json_canon.default)(proofConfiguration.context ?? null);
@@ -7887,12 +8123,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
7887
8123
  * @since 0.10.0
7888
8124
  */
7889
8125
  async function verifyObject(cls, jsonLd, options = {}) {
8126
+ if (options[verificationObservation]?.attempt == null) {
8127
+ const observedOptions = {
8128
+ ...options,
8129
+ [verificationObservation]: options[verificationObservation] ?? {
8130
+ attempts: [],
8131
+ captureRawKeyIds: false
8132
+ }
8133
+ };
8134
+ return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
8135
+ try {
8136
+ return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
8137
+ const object = await verifyObject(cls, jsonLd, {
8138
+ ...options,
8139
+ [verificationObservation]: observation
8140
+ });
8141
+ if (object == null) {
8142
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
8143
+ span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
8144
+ }
8145
+ return object;
8146
+ }, (value) => value != null);
8147
+ } catch (error) {
8148
+ span.setStatus({
8149
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
8150
+ message: String(error)
8151
+ });
8152
+ throw error;
8153
+ } finally {
8154
+ span.end();
8155
+ }
8156
+ });
8157
+ }
8158
+ const observation = options[verificationObservation];
7890
8159
  const logger = (0, _logtape_logtape.getLogger)([
7891
8160
  "fedify",
7892
8161
  "sig",
7893
8162
  "proof"
7894
8163
  ]);
7895
8164
  const object = await cls.fromJsonLd(jsonLd, options);
8165
+ observation?.parsedObject?.(object);
8166
+ if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
7896
8167
  const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
7897
8168
  const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
7898
8169
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
@@ -7919,10 +8190,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
7919
8190
  if (candidateIndex >= 0) {
7920
8191
  hydratedCandidates.add(candidateIndex);
7921
8192
  let parsed = null;
8193
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
7922
8194
  try {
7923
8195
  parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(remoteDocument.document, {
7924
8196
  documentLoader: baseDocumentLoader,
7925
- contextLoader: proofContextLoader,
8197
+ contextLoader: contexts?.loader ?? proofContextLoader,
7926
8198
  tracerProvider: options.tracerProvider,
7927
8199
  baseUrl: (0, _fedify_vocab_runtime.parseIri)(remoteDocument.documentUrl)
7928
8200
  });
@@ -7930,7 +8202,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
7930
8202
  rawProofCandidates[candidateIndex] = {
7931
8203
  value: structuredClone(remoteDocument.document),
7932
8204
  proof: parsed,
7933
- reference
8205
+ reference,
8206
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
7934
8207
  };
7935
8208
  }
7936
8209
  return remoteDocument;
@@ -7958,6 +8231,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
7958
8231
  return null;
7959
8232
  }
7960
8233
  if (attributions.size > 0) {
8234
+ if (observation?.attempt != null) observation.attempt.reason = observation.attempt.checks.length === 0 ? { type: "noSignature" } : {
8235
+ type: "uncoveredAttribution",
8236
+ attributionIds: [...attributions].map((id) => new URL(id))
8237
+ };
7961
8238
  logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
7962
8239
  return null;
7963
8240
  }
@@ -8978,6 +9255,14 @@ async function verifyRequest(request, options = {}) {
8978
9255
  * @since 2.1.0
8979
9256
  */
8980
9257
  async function verifyRequestDetailed(request, options = {}) {
9258
+ if (options[verificationObservation] != null && options[verificationObservation]?.attempt == null) return await observeAttempt(options, "http", async (observation) => {
9259
+ const result = await verifyRequestDetailed(request, {
9260
+ ...options,
9261
+ [verificationObservation]: observation
9262
+ });
9263
+ if (!result.verified) observation.attempt.reason = result.reason.type === "noSignature" ? { type: "noSignature" } : { type: "signatureVerificationFailed" };
9264
+ return result;
9265
+ }, (result) => result.verified);
8981
9266
  if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
8982
9267
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
8983
9268
  if (span.isRecording()) {
@@ -9016,7 +9301,27 @@ async function verifyRequestDetailed(request, options = {}) {
9016
9301
  }
9017
9302
  });
9018
9303
  }
9019
- async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
9304
+ async function verifyRequestDraft(request, span, metricsContext, options = {}) {
9305
+ if (options[verificationObservation]?.check != null) return await verifyRequestDraftInternal(request, span, metricsContext, options);
9306
+ let result = noSignatureResult();
9307
+ const raw = request.headers.get("Signature");
9308
+ const declared = raw == null ? null : parseDraftSignature(raw)?.keyId ?? null;
9309
+ if (raw == null) return result;
9310
+ await observeCheck(options, {
9311
+ mechanism: "http",
9312
+ spec: "draft-cavage-http-signatures-12",
9313
+ label: null
9314
+ }, declared, async (observation) => {
9315
+ result = await verifyRequestDraftInternal(request, span, metricsContext, {
9316
+ ...options,
9317
+ [verificationObservation]: observation
9318
+ });
9319
+ if (!result.verified && result.reason.type === "keyFetchError" && observation.check != null) observation.check.reason = result.reason;
9320
+ return result.verified ? result.key : null;
9321
+ });
9322
+ return result;
9323
+ }
9324
+ async function verifyRequestDraftInternal(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider, [verificationObservation]: observation } = {}) {
9020
9325
  const logger = (0, _logtape_logtape.getLogger)([
9021
9326
  "fedify",
9022
9327
  "sig",
@@ -9197,6 +9502,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
9197
9502
  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");
9198
9503
  const sig = (0, byte_encodings_base64.decodeBase64)(signature);
9199
9504
  span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
9505
+ triedKey({ [verificationObservation]: observation }, key);
9200
9506
  if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
9201
9507
  if (cached) {
9202
9508
  logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
@@ -9211,7 +9517,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
9211
9517
  currentTime,
9212
9518
  keyCache: bypassKeyCacheReads(keyCache),
9213
9519
  meterProvider,
9214
- tracerProvider
9520
+ tracerProvider,
9521
+ [verificationObservation]: observation
9215
9522
  });
9216
9523
  }
9217
9524
  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}", {
@@ -9297,7 +9604,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
9297
9604
  * @returns Whether the signature is verified, and the algorithm of the
9298
9605
  * signature if it is supported.
9299
9606
  */
9300
- async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
9607
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
9301
9608
  const logger = (0, _logtape_logtape.getLogger)([
9302
9609
  "fedify",
9303
9610
  "sig",
@@ -9353,6 +9660,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
9353
9660
  }
9354
9661
  let verified;
9355
9662
  try {
9663
+ triedKey(observationOptions, key);
9356
9664
  verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
9357
9665
  } catch (error) {
9358
9666
  logger.debug("Error during signature verification: {error}", {
@@ -9380,7 +9688,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
9380
9688
  mismatched: true
9381
9689
  };
9382
9690
  }
9383
- async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
9691
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, [verificationObservation]: observation, meterProvider, tracerProvider } = {}) {
9384
9692
  const logger = (0, _logtape_logtape.getLogger)([
9385
9693
  "fedify",
9386
9694
  "sig",
@@ -9444,105 +9752,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9444
9752
  let body = null;
9445
9753
  let digestValid = null;
9446
9754
  for (const sigName of signatureNames) {
9447
- if (!signatures[sigName]) {
9448
- setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
9449
- continue;
9450
- }
9451
- const sigInput = signatureInputs[sigName];
9452
- const sigBytes = signatures[sigName];
9453
- const keyId = parseKeyId(sigInput.keyId);
9454
- if (!sigInput.keyId) {
9455
- logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
9456
- signatureName: sigName,
9457
- signatureInput: signatureInputHeader
9458
- });
9459
- setFailure(invalidSignatureResult(null));
9460
- continue;
9461
- }
9462
- if (!sigInput.created) {
9463
- logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
9464
- signatureName: sigName,
9465
- signatureInput: signatureInputHeader
9466
- });
9467
- setFailure(invalidSignatureResult(keyId));
9468
- continue;
9469
- }
9470
- const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
9471
- const now = currentTime ?? Temporal.Now.instant();
9472
- if (timeWindow !== false) {
9473
- const tw = timeWindow ?? { hours: 1 };
9474
- if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
9475
- logger.debug("Failed to verify; signature created time is too far in the future.", {
9476
- created: signatureCreated.toString(),
9477
- now: now.toString()
9478
- });
9479
- setFailure(invalidSignatureResult(keyId));
9480
- continue;
9481
- } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
9482
- logger.debug("Failed to verify; signature created time is too far in the past.", {
9483
- created: signatureCreated.toString(),
9484
- now: now.toString()
9485
- });
9486
- setFailure(invalidSignatureResult(keyId));
9487
- continue;
9755
+ let winning;
9756
+ await observeCheck({ [verificationObservation]: observation }, {
9757
+ mechanism: "http",
9758
+ spec: "rfc9421",
9759
+ label: sigName
9760
+ }, signatureInputs[sigName]?.keyId ?? null, async (candidateObservation) => {
9761
+ if (!signatures[sigName]) {
9762
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
9763
+ return null;
9488
9764
  }
9489
- }
9490
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
9491
- const contentDigestHeader = request.headers.get("Content-Digest");
9492
- if (!contentDigestHeader) {
9493
- logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
9494
- setFailure(invalidSignatureResult(keyId));
9495
- continue;
9765
+ const sigInput = signatureInputs[sigName];
9766
+ const sigBytes = signatures[sigName];
9767
+ const keyId = parseKeyId(sigInput.keyId);
9768
+ if (!sigInput.keyId) {
9769
+ logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
9770
+ signatureName: sigName,
9771
+ signatureInput: signatureInputHeader
9772
+ });
9773
+ setFailure(invalidSignatureResult(null));
9774
+ return null;
9496
9775
  }
9497
- body ??= await request.arrayBuffer();
9498
- digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
9499
- if (!digestValid) {
9500
- logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
9776
+ if (!sigInput.created) {
9777
+ logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
9778
+ signatureName: sigName,
9779
+ signatureInput: signatureInputHeader
9780
+ });
9501
9781
  setFailure(invalidSignatureResult(keyId));
9502
- continue;
9782
+ return null;
9503
9783
  }
9504
- }
9505
- span?.setAttribute("http_signatures.key_id", sigInput.keyId);
9506
- span?.setAttribute("http_signatures.created", sigInput.created.toString());
9507
- if (keyId == null) {
9508
- setFailure(invalidSignatureResult(null));
9509
- continue;
9510
- }
9511
- let lookup = keyLookups.get(keyId.href);
9512
- if (lookup == null) {
9513
- lookup = await lookUpKey(keyId, keyCache);
9514
- keyLookups.set(keyId.href, lookup);
9515
- }
9516
- while (true) {
9517
- const { key, cached, fetchError } = lookup;
9518
- if (fetchError != null) {
9519
- setFailure(keyFetchErrorResult(keyId, fetchError));
9520
- break;
9784
+ const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
9785
+ const now = currentTime ?? Temporal.Now.instant();
9786
+ if (timeWindow !== false) {
9787
+ const tw = timeWindow ?? { hours: 1 };
9788
+ if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
9789
+ logger.debug("Failed to verify; signature created time is too far in the future.", {
9790
+ created: signatureCreated.toString(),
9791
+ now: now.toString()
9792
+ });
9793
+ setFailure(invalidSignatureResult(keyId));
9794
+ return null;
9795
+ } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
9796
+ logger.debug("Failed to verify; signature created time is too far in the past.", {
9797
+ created: signatureCreated.toString(),
9798
+ now: now.toString()
9799
+ });
9800
+ setFailure(invalidSignatureResult(keyId));
9801
+ return null;
9802
+ }
9521
9803
  }
9522
- if (!key) {
9523
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9524
- setFailure(invalidSignatureResult(keyId));
9525
- break;
9804
+ if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
9805
+ const contentDigestHeader = request.headers.get("Content-Digest");
9806
+ if (!contentDigestHeader) {
9807
+ logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
9808
+ setFailure(invalidSignatureResult(keyId));
9809
+ return null;
9810
+ }
9811
+ body ??= await request.arrayBuffer();
9812
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
9813
+ if (!digestValid) {
9814
+ logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
9815
+ setFailure(invalidSignatureResult(keyId));
9816
+ return null;
9817
+ }
9526
9818
  }
9527
- const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
9528
- if (result.verified) {
9529
- metricsContext.algorithm = result.algorithm;
9530
- return {
9531
- verified: true,
9532
- key,
9533
- signatureLabel: sigName
9534
- };
9819
+ span?.setAttribute("http_signatures.key_id", sigInput.keyId);
9820
+ span?.setAttribute("http_signatures.created", sigInput.created.toString());
9821
+ if (keyId == null) {
9822
+ setFailure(invalidSignatureResult(null));
9823
+ return null;
9535
9824
  }
9536
- if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
9537
- logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
9538
- refreshedKeyIds.add(keyId.href);
9539
- lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
9825
+ let lookup = keyLookups.get(keyId.href);
9826
+ if (lookup == null) {
9827
+ lookup = await lookUpKey(keyId, keyCache);
9540
9828
  keyLookups.set(keyId.href, lookup);
9541
- continue;
9542
9829
  }
9543
- setFailure(invalidSignatureResult(keyId), result.algorithm);
9544
- break;
9545
- }
9830
+ while (true) {
9831
+ const { key, cached, fetchError } = lookup;
9832
+ if (fetchError != null) {
9833
+ setFailure(keyFetchErrorResult(keyId, fetchError));
9834
+ break;
9835
+ }
9836
+ if (!key) {
9837
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9838
+ setFailure(invalidSignatureResult(keyId));
9839
+ break;
9840
+ }
9841
+ const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, { [verificationObservation]: candidateObservation });
9842
+ if (result.verified) {
9843
+ metricsContext.algorithm = result.algorithm;
9844
+ winning = {
9845
+ verified: true,
9846
+ key,
9847
+ signatureLabel: sigName
9848
+ };
9849
+ return key;
9850
+ }
9851
+ if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
9852
+ logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
9853
+ refreshedKeyIds.add(keyId.href);
9854
+ lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
9855
+ keyLookups.set(keyId.href, lookup);
9856
+ continue;
9857
+ }
9858
+ setFailure(invalidSignatureResult(keyId), result.algorithm);
9859
+ break;
9860
+ }
9861
+ if (!failure.verified && failure.reason.type === "keyFetchError" && candidateObservation.check != null) candidateObservation.check.reason = failure.reason;
9862
+ return null;
9863
+ });
9864
+ if (winning != null) return winning;
9546
9865
  }
9547
9866
  metricsContext.algorithm = failureAlgorithm;
9548
9867
  return failure;
@@ -10063,6 +10382,12 @@ Object.defineProperty(exports, "normalizeOutgoingActivityJsonLd", {
10063
10382
  return normalizeOutgoingActivityJsonLd;
10064
10383
  }
10065
10384
  });
10385
+ Object.defineProperty(exports, "observeAttempt", {
10386
+ enumerable: true,
10387
+ get: function() {
10388
+ return observeAttempt;
10389
+ }
10390
+ });
10066
10391
  Object.defineProperty(exports, "parseAcceptSignature", {
10067
10392
  enumerable: true,
10068
10393
  get: function() {
@@ -10189,6 +10514,12 @@ Object.defineProperty(exports, "signRequest", {
10189
10514
  return signRequest;
10190
10515
  }
10191
10516
  });
10517
+ Object.defineProperty(exports, "snapshotKey", {
10518
+ enumerable: true,
10519
+ get: function() {
10520
+ return snapshotKey;
10521
+ }
10522
+ });
10192
10523
  Object.defineProperty(exports, "validateAcceptSignature", {
10193
10524
  enumerable: true,
10194
10525
  get: function() {
@@ -10207,6 +10538,12 @@ Object.defineProperty(exports, "validateMaxSignatures", {
10207
10538
  return validateMaxSignatures;
10208
10539
  }
10209
10540
  });
10541
+ Object.defineProperty(exports, "verificationObservation", {
10542
+ enumerable: true,
10543
+ get: function() {
10544
+ return verificationObservation;
10545
+ }
10546
+ });
10210
10547
  Object.defineProperty(exports, "verifyCompactJsonLd", {
10211
10548
  enumerable: true,
10212
10549
  get: function() {