@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
@@ -10,13 +10,14 @@ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
10
10
  _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
11
11
  let structured_field_values = require("structured-field-values");
12
12
  let byte_encodings_base64 = require("byte-encodings/base64");
13
+ let _fedify_vocab_runtime_internal_jsonld_cache = require("@fedify/vocab-runtime/internal/jsonld-cache");
13
14
  let _fedify_vocab_runtime_internal_signed_representation = require("@fedify/vocab-runtime/internal/signed-representation");
14
15
  let json_canon = require("json-canon");
15
16
  json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
16
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
18
  //#region deno.json
18
19
  var name = "@fedify/fedify";
19
- var version = "2.4.0-dev.2253+f5212c33";
20
+ var version = "2.4.0-dev.2259+d9c843fc";
20
21
  //#endregion
21
22
  //#region src/sig/portable-key-id.ts
22
23
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -3823,6 +3824,106 @@ const Temporal$5 = /*@__PURE__*/ Object.defineProperties({}, {
3823
3824
  const Temporal$4 = NativeTemporal || Temporal$5;
3824
3825
  NativeTemporal && Date.prototype.toTemporalInstant;
3825
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
3826
3927
  //#region src/sig/accept.ts
3827
3928
  /**
3828
3929
  * `Accept-Signature` header parsing, serialization, and validation utilities
@@ -4279,6 +4380,7 @@ var FederationMetrics = class {
4279
4380
  };
4280
4381
  if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
4281
4382
  if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
4383
+ if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
4282
4384
  if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
4283
4385
  if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
4284
4386
  this.signatureVerificationDuration.record(durationMs, attributes);
@@ -5784,12 +5886,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
5784
5886
  };
5785
5887
  }
5786
5888
  async function fetchKeyInternal(keyId, cls, options = {}) {
5787
- 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 {}
5788
5899
  return {
5789
5900
  key: null,
5790
5901
  cached: true
5791
5902
  };
5792
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
+ }
5793
5921
  logger.debug("Failed to fetch key {keyId}.", {
5794
5922
  keyId,
5795
5923
  error
@@ -6246,6 +6374,13 @@ function detachSignature(jsonLd) {
6246
6374
  */
6247
6375
  async function verifySignature(jsonLd, options = {}) {
6248
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
+ }
6249
6384
  const sig = jsonLd.signature;
6250
6385
  let signature;
6251
6386
  try {
@@ -6291,6 +6426,7 @@ async function verifySignature(jsonLd, options = {}) {
6291
6426
  const encoder = new TextEncoder();
6292
6427
  const message = sigOptsHash + docHash;
6293
6428
  const messageBytes = encoder.encode(message);
6429
+ triedKey(options, key);
6294
6430
  if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
6295
6431
  if (cached) {
6296
6432
  logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
@@ -6302,6 +6438,7 @@ async function verifySignature(jsonLd, options = {}) {
6302
6438
  keyCache: bypassKeyCacheReads(options.keyCache)
6303
6439
  }));
6304
6440
  if (key == null) return null;
6441
+ triedKey(options, key);
6305
6442
  return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
6306
6443
  }
6307
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}", {
@@ -6355,6 +6492,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
6355
6492
  return await verifyJsonLdInternal(jsonLd, options, false);
6356
6493
  }
6357
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];
6358
6503
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
6359
6504
  const start = performance.now();
6360
6505
  let verified = false;
@@ -6367,6 +6512,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6367
6512
  } : options;
6368
6513
  const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
6369
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);
6370
6517
  if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
6371
6518
  span.setAttribute("activitypub.object.type", (0, _fedify_vocab.getTypeId)(object).href);
6372
6519
  const sig = getLdSignatureObject(jsonLd);
@@ -6381,13 +6528,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6381
6528
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
6382
6529
  if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
6383
6530
  const key = await verifySignature(compacted, verificationOptions);
6384
- 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
+ }
6385
6535
  if (key.ownerId == null) {
6536
+ if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
6386
6537
  logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
6387
6538
  return false;
6388
6539
  }
6389
6540
  attributions.delete(key.ownerId.href);
6390
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
+ };
6391
6546
  logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
6392
6547
  return false;
6393
6548
  }
@@ -6401,8 +6556,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
6401
6556
  });
6402
6557
  throw error;
6403
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
+ }
6404
6563
  const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
6405
- 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
+ });
6406
6568
  span.end();
6407
6569
  }
6408
6570
  });
@@ -7031,19 +7193,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
7031
7193
  */
7032
7194
  async function verifyMapLocalProof(jsonLd, options = {}) {
7033
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));
7034
7203
  const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
7035
7204
  try {
7036
7205
  const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
7037
- if (candidate.proof == null) return null;
7206
+ if (candidate.proof == null) return await malformed();
7038
7207
  return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
7039
7208
  proofContextLoader,
7040
7209
  proofPropertyMode: "literal"
7041
7210
  }, candidate);
7042
7211
  } catch {
7212
+ if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
7043
7213
  return null;
7044
7214
  }
7045
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
+ }
7046
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));
7047
7240
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
7048
7241
  const start = performance.now();
7049
7242
  let verified = false;
@@ -7056,8 +7249,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
7056
7249
  }
7057
7250
  try {
7058
7251
  const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
7059
- if (key == null) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
7060
- 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;
7061
7256
  return key;
7062
7257
  } catch (error) {
7063
7258
  threw = true;
@@ -7068,7 +7263,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
7068
7263
  throw error;
7069
7264
  } finally {
7070
7265
  const classified = threw ? "error" : verified ? "verified" : "rejected";
7071
- 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
+ });
7072
7270
  span.end();
7073
7271
  }
7074
7272
  });
@@ -7085,14 +7283,15 @@ function expandContextPropertyIri(activeContext, key) {
7085
7283
  if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
7086
7284
  return key;
7087
7285
  }
7088
- async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
7286
+ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
7089
7287
  const names = new Set(defaults);
7090
7288
  const context = jsonLd["@context"] ?? inheritedContext;
7091
- if (context == null) return names;
7289
+ if (context == null && contextState?.initial == null) return names;
7092
7290
  try {
7093
7291
  const options = { documentLoader };
7094
- let activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
7095
- 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);
7096
7295
  const typeScopedContext = activeContext;
7097
7296
  for (const key of globalThis.Object.keys(jsonLd).sort()) {
7098
7297
  if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
@@ -7104,6 +7303,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
7104
7303
  if (scopedContext != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, scopedContext, options);
7105
7304
  }
7106
7305
  }
7306
+ if (contextState != null) contextState.active = activeContext;
7107
7307
  for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
7108
7308
  } catch {
7109
7309
  if (rejectOnContextError) return null;
@@ -7170,6 +7370,36 @@ function sameBytes(left, right) {
7170
7370
  function sameProof(left, right) {
7171
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();
7172
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
+ }
7173
7403
  function normalizeDocumentUrl(url) {
7174
7404
  try {
7175
7405
  return (0, _fedify_vocab_runtime.formatIri)((0, _fedify_vocab_runtime.parseIri)(url));
@@ -7188,6 +7418,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
7188
7418
  const candidates = [];
7189
7419
  for (const value of values) {
7190
7420
  let parsed = null;
7421
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
7191
7422
  if (isJsonLdNode(value)) {
7192
7423
  const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
7193
7424
  "@context": jsonLd["@context"],
@@ -7196,14 +7427,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
7196
7427
  try {
7197
7428
  parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofJsonLd, {
7198
7429
  ...options,
7199
- contextLoader: documentLoader
7430
+ contextLoader: contexts?.loader ?? documentLoader
7200
7431
  });
7201
7432
  } catch {}
7202
7433
  }
7203
7434
  candidates.push({
7204
7435
  value,
7205
7436
  proof: parsed,
7206
- 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)
7207
7439
  });
7208
7440
  }
7209
7441
  return candidates;
@@ -7427,8 +7659,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
7427
7659
  const digest = new Uint8Array(proofDigest.byteLength + 32);
7428
7660
  digest.set(new Uint8Array(proofDigest), 0);
7429
7661
  const proofValue = proof.proofValue;
7662
+ let keyTried = false;
7430
7663
  const verifyCandidate = async (msgDigest) => {
7431
7664
  digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
7665
+ if (!keyTried) {
7666
+ triedKey(options, publicKey);
7667
+ keyTried = true;
7668
+ }
7432
7669
  return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
7433
7670
  };
7434
7671
  const messageDigestKey = (0, json_canon.default)(proofConfiguration.context ?? null);
@@ -7473,7 +7710,6 @@ const FEP_2277_COLLECTION_PROPERTIES = [
7473
7710
  "prev",
7474
7711
  "current"
7475
7712
  ].map((property) => AS_NAMESPACE + property);
7476
- const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
7477
7713
  const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
7478
7714
  const FUNCTIONAL_PROOF_PROPERTIES = [
7479
7715
  `${SECURITY_NAMESPACE}cryptosuite`,
@@ -7504,14 +7740,15 @@ function hasValidPortableProofShape(proofValue) {
7504
7740
  * FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
7505
7741
  * an empty path, query, and fragment. Like the generated vocabulary decoder,
7506
7742
  * this also tolerates a plain set of values instead of a `@list`, and gateway
7507
- * strings given as `@value`s instead of `@id`s.
7743
+ * strings given as `@value`s instead of `@id`s. JCS-authenticated actors may
7744
+ * also supply an unmapped literal `gateways` list.
7508
7745
  */
7509
- function hasValidPortableActorGateways(node) {
7510
- const values = node[FEP_EF61_GATEWAYS];
7746
+ function hasValidPortableActorGateways(node, allowUnmapped) {
7747
+ const values = (0, _fedify_vocab_runtime_internal_jsonld_cache.getPortableActorGateways)(node, allowUnmapped);
7511
7748
  if (!Array.isArray(values)) return false;
7512
7749
  let items = values;
7513
7750
  if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
7514
- if (values.length !== 1) return false;
7751
+ if (values.length !== 1 || !isJsonLdNode(values[0])) return false;
7515
7752
  items = values[0]["@list"];
7516
7753
  }
7517
7754
  if (!Array.isArray(items) || items.length < 1) return false;
@@ -7732,7 +7969,7 @@ async function preparePortableObjectProof(jsonLd, options) {
7732
7969
  }
7733
7970
  };
7734
7971
  }
7735
- if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
7972
+ if (objectType === "actor" && !hasValidPortableActorGateways(root, proofs.every((proof) => proof.cryptosuite === "eddsa-jcs-2022"))) return {
7736
7973
  prepared: false,
7737
7974
  root,
7738
7975
  objectType,
@@ -7799,6 +8036,8 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
7799
8036
  * and fragment; otherwise, it is rejected with the `invalidGateways` reason
7800
8037
  * before its proofs are verified. The proofs are verified over the document as
7801
8038
  * given; a compatible identifier is never rewritten into a portable ID.
8039
+ * An unmapped literal `gateways` property is also accepted when every proof
8040
+ * uses `eddsa-jcs-2022`, which authenticates the original JSON properties.
7802
8041
  *
7803
8042
  * [FEP-ef61]: https://w3id.org/fep/ef61
7804
8043
  *
@@ -7884,12 +8123,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
7884
8123
  * @since 0.10.0
7885
8124
  */
7886
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];
7887
8159
  const logger = (0, _logtape_logtape.getLogger)([
7888
8160
  "fedify",
7889
8161
  "sig",
7890
8162
  "proof"
7891
8163
  ]);
7892
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);
7893
8167
  const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
7894
8168
  const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
7895
8169
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
@@ -7916,10 +8190,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
7916
8190
  if (candidateIndex >= 0) {
7917
8191
  hydratedCandidates.add(candidateIndex);
7918
8192
  let parsed = null;
8193
+ const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
7919
8194
  try {
7920
8195
  parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(remoteDocument.document, {
7921
8196
  documentLoader: baseDocumentLoader,
7922
- contextLoader: proofContextLoader,
8197
+ contextLoader: contexts?.loader ?? proofContextLoader,
7923
8198
  tracerProvider: options.tracerProvider,
7924
8199
  baseUrl: (0, _fedify_vocab_runtime.parseIri)(remoteDocument.documentUrl)
7925
8200
  });
@@ -7927,7 +8202,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
7927
8202
  rawProofCandidates[candidateIndex] = {
7928
8203
  value: structuredClone(remoteDocument.document),
7929
8204
  proof: parsed,
7930
- reference
8205
+ reference,
8206
+ declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
7931
8207
  };
7932
8208
  }
7933
8209
  return remoteDocument;
@@ -7955,6 +8231,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
7955
8231
  return null;
7956
8232
  }
7957
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
+ };
7958
8238
  logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
7959
8239
  return null;
7960
8240
  }
@@ -8975,6 +9255,14 @@ async function verifyRequest(request, options = {}) {
8975
9255
  * @since 2.1.0
8976
9256
  */
8977
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);
8978
9266
  if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
8979
9267
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
8980
9268
  if (span.isRecording()) {
@@ -9013,7 +9301,27 @@ async function verifyRequestDetailed(request, options = {}) {
9013
9301
  }
9014
9302
  });
9015
9303
  }
9016
- 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 } = {}) {
9017
9325
  const logger = (0, _logtape_logtape.getLogger)([
9018
9326
  "fedify",
9019
9327
  "sig",
@@ -9194,6 +9502,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
9194
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");
9195
9503
  const sig = (0, byte_encodings_base64.decodeBase64)(signature);
9196
9504
  span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
9505
+ triedKey({ [verificationObservation]: observation }, key);
9197
9506
  if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
9198
9507
  if (cached) {
9199
9508
  logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
@@ -9208,7 +9517,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
9208
9517
  currentTime,
9209
9518
  keyCache: bypassKeyCacheReads(keyCache),
9210
9519
  meterProvider,
9211
- tracerProvider
9520
+ tracerProvider,
9521
+ [verificationObservation]: observation
9212
9522
  });
9213
9523
  }
9214
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}", {
@@ -9294,7 +9604,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
9294
9604
  * @returns Whether the signature is verified, and the algorithm of the
9295
9605
  * signature if it is supported.
9296
9606
  */
9297
- async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
9607
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
9298
9608
  const logger = (0, _logtape_logtape.getLogger)([
9299
9609
  "fedify",
9300
9610
  "sig",
@@ -9350,6 +9660,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
9350
9660
  }
9351
9661
  let verified;
9352
9662
  try {
9663
+ triedKey(observationOptions, key);
9353
9664
  verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
9354
9665
  } catch (error) {
9355
9666
  logger.debug("Error during signature verification: {error}", {
@@ -9377,7 +9688,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
9377
9688
  mismatched: true
9378
9689
  };
9379
9690
  }
9380
- 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 } = {}) {
9381
9692
  const logger = (0, _logtape_logtape.getLogger)([
9382
9693
  "fedify",
9383
9694
  "sig",
@@ -9441,105 +9752,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9441
9752
  let body = null;
9442
9753
  let digestValid = null;
9443
9754
  for (const sigName of signatureNames) {
9444
- if (!signatures[sigName]) {
9445
- setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
9446
- continue;
9447
- }
9448
- const sigInput = signatureInputs[sigName];
9449
- const sigBytes = signatures[sigName];
9450
- const keyId = parseKeyId(sigInput.keyId);
9451
- if (!sigInput.keyId) {
9452
- logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
9453
- signatureName: sigName,
9454
- signatureInput: signatureInputHeader
9455
- });
9456
- setFailure(invalidSignatureResult(null));
9457
- continue;
9458
- }
9459
- if (!sigInput.created) {
9460
- logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
9461
- signatureName: sigName,
9462
- signatureInput: signatureInputHeader
9463
- });
9464
- setFailure(invalidSignatureResult(keyId));
9465
- continue;
9466
- }
9467
- const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
9468
- const now = currentTime ?? Temporal.Now.instant();
9469
- if (timeWindow !== false) {
9470
- const tw = timeWindow ?? { hours: 1 };
9471
- if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
9472
- logger.debug("Failed to verify; signature created time is too far in the future.", {
9473
- created: signatureCreated.toString(),
9474
- now: now.toString()
9475
- });
9476
- setFailure(invalidSignatureResult(keyId));
9477
- continue;
9478
- } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
9479
- logger.debug("Failed to verify; signature created time is too far in the past.", {
9480
- created: signatureCreated.toString(),
9481
- now: now.toString()
9482
- });
9483
- setFailure(invalidSignatureResult(keyId));
9484
- 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;
9485
9764
  }
9486
- }
9487
- if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
9488
- const contentDigestHeader = request.headers.get("Content-Digest");
9489
- if (!contentDigestHeader) {
9490
- logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
9491
- setFailure(invalidSignatureResult(keyId));
9492
- 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;
9493
9775
  }
9494
- body ??= await request.arrayBuffer();
9495
- digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
9496
- if (!digestValid) {
9497
- 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
+ });
9498
9781
  setFailure(invalidSignatureResult(keyId));
9499
- continue;
9782
+ return null;
9500
9783
  }
9501
- }
9502
- span?.setAttribute("http_signatures.key_id", sigInput.keyId);
9503
- span?.setAttribute("http_signatures.created", sigInput.created.toString());
9504
- if (keyId == null) {
9505
- setFailure(invalidSignatureResult(null));
9506
- continue;
9507
- }
9508
- let lookup = keyLookups.get(keyId.href);
9509
- if (lookup == null) {
9510
- lookup = await lookUpKey(keyId, keyCache);
9511
- keyLookups.set(keyId.href, lookup);
9512
- }
9513
- while (true) {
9514
- const { key, cached, fetchError } = lookup;
9515
- if (fetchError != null) {
9516
- setFailure(keyFetchErrorResult(keyId, fetchError));
9517
- 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
+ }
9518
9803
  }
9519
- if (!key) {
9520
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9521
- setFailure(invalidSignatureResult(keyId));
9522
- 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
+ }
9523
9818
  }
9524
- const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
9525
- if (result.verified) {
9526
- metricsContext.algorithm = result.algorithm;
9527
- return {
9528
- verified: true,
9529
- key,
9530
- signatureLabel: sigName
9531
- };
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;
9532
9824
  }
9533
- if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
9534
- logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
9535
- refreshedKeyIds.add(keyId.href);
9536
- lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
9825
+ let lookup = keyLookups.get(keyId.href);
9826
+ if (lookup == null) {
9827
+ lookup = await lookUpKey(keyId, keyCache);
9537
9828
  keyLookups.set(keyId.href, lookup);
9538
- continue;
9539
9829
  }
9540
- setFailure(invalidSignatureResult(keyId), result.algorithm);
9541
- break;
9542
- }
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;
9543
9865
  }
9544
9866
  metricsContext.algorithm = failureAlgorithm;
9545
9867
  return failure;
@@ -10060,6 +10382,12 @@ Object.defineProperty(exports, "normalizeOutgoingActivityJsonLd", {
10060
10382
  return normalizeOutgoingActivityJsonLd;
10061
10383
  }
10062
10384
  });
10385
+ Object.defineProperty(exports, "observeAttempt", {
10386
+ enumerable: true,
10387
+ get: function() {
10388
+ return observeAttempt;
10389
+ }
10390
+ });
10063
10391
  Object.defineProperty(exports, "parseAcceptSignature", {
10064
10392
  enumerable: true,
10065
10393
  get: function() {
@@ -10186,6 +10514,12 @@ Object.defineProperty(exports, "signRequest", {
10186
10514
  return signRequest;
10187
10515
  }
10188
10516
  });
10517
+ Object.defineProperty(exports, "snapshotKey", {
10518
+ enumerable: true,
10519
+ get: function() {
10520
+ return snapshotKey;
10521
+ }
10522
+ });
10189
10523
  Object.defineProperty(exports, "validateAcceptSignature", {
10190
10524
  enumerable: true,
10191
10525
  get: function() {
@@ -10204,6 +10538,12 @@ Object.defineProperty(exports, "validateMaxSignatures", {
10204
10538
  return validateMaxSignatures;
10205
10539
  }
10206
10540
  });
10541
+ Object.defineProperty(exports, "verificationObservation", {
10542
+ enumerable: true,
10543
+ get: function() {
10544
+ return verificationObservation;
10545
+ }
10546
+ });
10207
10547
  Object.defineProperty(exports, "verifyCompactJsonLd", {
10208
10548
  enumerable: true,
10209
10549
  get: function() {