@fedify/fedify 2.1.0-dev.503 → 2.1.0-dev.523

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 (78) hide show
  1. package/dist/{builder-BHUnSQtB.js → builder-CJkMYxxc.js} +9 -3
  2. package/dist/compat/mod.d.cts +3 -3
  3. package/dist/compat/mod.d.ts +3 -3
  4. package/dist/compat/transformers.test.js +12 -12
  5. package/dist/{context-DZJhUmzF.d.ts → context--RwChtri.d.ts} +54 -2
  6. package/dist/{context-D3QkEtZd.d.cts → context-DL0cPpPV.d.cts} +54 -2
  7. package/dist/{deno-BYerLnry.js → deno-CQdJQjC5.js} +1 -1
  8. package/dist/{docloader-MSkogD2T.js → docloader-Cyl0-S8m.js} +2 -2
  9. package/dist/federation/builder.test.js +14 -3
  10. package/dist/federation/handler.test.js +97 -13
  11. package/dist/federation/idempotency.test.js +12 -12
  12. package/dist/federation/inbox.test.js +2 -2
  13. package/dist/federation/keycache.test.js +46 -2
  14. package/dist/federation/middleware.test.js +206 -12
  15. package/dist/federation/mod.cjs +4 -4
  16. package/dist/federation/mod.d.cts +4 -4
  17. package/dist/federation/mod.d.ts +4 -4
  18. package/dist/federation/mod.js +4 -4
  19. package/dist/federation/send.test.js +5 -5
  20. package/dist/federation/webfinger.test.js +12 -12
  21. package/dist/{http-DkHdFfrc.d.ts → http-BbfOqHGG.d.ts} +80 -8
  22. package/dist/{http-DJT6NciB.cjs → http-D6a6mMc0.cjs} +305 -99
  23. package/dist/{http-CSX1-Mgi.js → http-DJmytoC2.js} +295 -101
  24. package/dist/{http-S2U3qDwN.js → http-DK0CTomU.js} +153 -57
  25. package/dist/{http-Cz3MlXAZ.d.cts → http-DsqqmkXi.d.cts} +80 -8
  26. package/dist/{inbox-BaA0g5I_.js → inbox-CWa6sqsk.js} +1 -1
  27. package/dist/{key-DCdTVZiK.js → key-DRgvVevp.js} +145 -47
  28. package/dist/keycache-C7k8s1Bk.js +102 -0
  29. package/dist/{kv-cache-Vtxhbo1W.cjs → kv-cache-DPtsJ1sL.cjs} +1 -1
  30. package/dist/{kv-cache-CQPL_aGY.js → kv-cache-MPcS_mGG.js} +1 -1
  31. package/dist/{ld-CrX7pQda.js → ld-s9_8WfBc.js} +2 -2
  32. package/dist/{middleware-CfI9C9Xy.js → middleware-2XtoTBq0.js} +12 -12
  33. package/dist/{middleware-MlO5iUeZ.js → middleware-Ajnk9qHB.js} +158 -22
  34. package/dist/middleware-BgCIhb_C.cjs +12 -0
  35. package/dist/{middleware-D4S6i4A_.cjs → middleware-BoCzk7-G.cjs} +158 -22
  36. package/dist/{middleware-C8PKuPrm.js → middleware-DGUNDGCl.js} +4 -4
  37. package/dist/{middleware-BelSJK7m.js → middleware-Dn9UDJZP.js} +100 -24
  38. package/dist/{mod-CwZXZJ9d.d.ts → mod-BugwI0JN.d.ts} +1 -1
  39. package/dist/{mod-DPkRU3EK.d.cts → mod-CFBU2OT3.d.cts} +1 -1
  40. package/dist/{mod-DUWcVv49.d.ts → mod-CvxylbuV.d.ts} +1 -1
  41. package/dist/{mod-DVwHUI_x.d.cts → mod-DE8MYisy.d.cts} +1 -1
  42. package/dist/{mod-DXsQakeS.d.cts → mod-DKG0ovjR.d.cts} +1 -1
  43. package/dist/{mod-DnSsduJF.d.ts → mod-DcfFNgYf.d.ts} +1 -1
  44. package/dist/{mod-Di3W5OdP.d.cts → mod-Dp0kK0hO.d.cts} +1 -1
  45. package/dist/{mod-DosD6NsG.d.ts → mod-Z7lIaCfo.d.ts} +1 -1
  46. package/dist/mod.cjs +8 -4
  47. package/dist/mod.d.cts +8 -8
  48. package/dist/mod.d.ts +8 -8
  49. package/dist/mod.js +7 -5
  50. package/dist/nodeinfo/handler.test.js +12 -12
  51. package/dist/otel/exporter.test.js +43 -2
  52. package/dist/otel/mod.cjs +7 -1
  53. package/dist/otel/mod.d.cts +12 -0
  54. package/dist/otel/mod.d.ts +12 -0
  55. package/dist/otel/mod.js +7 -1
  56. package/dist/{owner-BAlnLKMO.js → owner-Cx8gV-j4.js} +1 -1
  57. package/dist/{proof-DMgHaXNJ.js → proof-CDr3NP3R.js} +2 -2
  58. package/dist/{proof-BgUVmaJz.js → proof-Le4DAkqb.js} +1 -1
  59. package/dist/{proof-CR5RUAmy.cjs → proof-qHcNgE5i.cjs} +1 -1
  60. package/dist/{send-B2aZYf9A.js → send-DreBSY1U.js} +2 -2
  61. package/dist/sig/http.test.js +85 -5
  62. package/dist/sig/key.test.js +70 -3
  63. package/dist/sig/ld.test.js +3 -3
  64. package/dist/sig/mod.cjs +4 -2
  65. package/dist/sig/mod.d.cts +3 -3
  66. package/dist/sig/mod.d.ts +3 -3
  67. package/dist/sig/mod.js +3 -3
  68. package/dist/sig/owner.test.js +3 -3
  69. package/dist/sig/proof.test.js +3 -3
  70. package/dist/testing/mod.d.ts +92 -0
  71. package/dist/utils/docloader.test.js +4 -4
  72. package/dist/utils/mod.cjs +2 -2
  73. package/dist/utils/mod.d.cts +2 -2
  74. package/dist/utils/mod.d.ts +2 -2
  75. package/dist/utils/mod.js +2 -2
  76. package/package.json +5 -5
  77. package/dist/keycache-DRxpZ5r9.js +0 -48
  78. package/dist/middleware-D4XcpSBG.cjs +0 -12
@@ -14,7 +14,7 @@ const __fedify_vocab_runtime = require_chunk.__toESM(require("@fedify/vocab-runt
14
14
 
15
15
  //#region deno.json
16
16
  var name = "@fedify/fedify";
17
- var version = "2.1.0-dev.503+1172d43b";
17
+ var version = "2.1.0-dev.523+150998a5";
18
18
  var license = "MIT";
19
19
  var exports$1 = {
20
20
  ".": "./src/mod.ts",
@@ -208,24 +208,10 @@ async function importJwk(jwk, type) {
208
208
  validateCryptoKey(key, type);
209
209
  return key;
210
210
  }
211
- /**
212
- * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
213
- * If the given URL contains an {@link Actor} object, it tries to find
214
- * the corresponding key in the `publicKey` or `assertionMethod` property.
215
- * @template T The type of the key to fetch. Either {@link CryptographicKey}
216
- * or {@link Multikey}.
217
- * @param keyId The URL of the key.
218
- * @param cls The class of the key to fetch. Either {@link CryptographicKey}
219
- * or {@link Multikey}.
220
- * @param options Options for fetching the key. See {@link FetchKeyOptions}.
221
- * @returns The fetched key or `null` if the key is not found.
222
- * @since 1.3.0
223
- */
224
- function fetchKey(keyId, cls, options = {}) {
225
- const tracerProvider = options.tracerProvider ?? __opentelemetry_api.trace.getTracerProvider();
211
+ async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
212
+ tracerProvider ??= __opentelemetry_api.trace.getTracerProvider();
226
213
  const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
227
- keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
228
- return tracer.startActiveSpan("activitypub.fetch_key", {
214
+ return await tracer.startActiveSpan("activitypub.fetch_key", {
229
215
  kind: __opentelemetry_api.SpanKind.CLIENT,
230
216
  attributes: {
231
217
  "http.method": "GET",
@@ -238,7 +224,7 @@ function fetchKey(keyId, cls, options = {}) {
238
224
  }
239
225
  }, async (span) => {
240
226
  try {
241
- const result = await fetchKeyInternal(keyId, cls, options);
227
+ const result = await fetcher();
242
228
  span.setAttribute("activitypub.actor.key.cached", result.cached);
243
229
  return result;
244
230
  } catch (e) {
@@ -252,43 +238,105 @@ function fetchKey(keyId, cls, options = {}) {
252
238
  }
253
239
  });
254
240
  }
255
- async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider } = {}) {
256
- const logger = (0, __logtape_logtape.getLogger)([
257
- "fedify",
258
- "sig",
259
- "key"
260
- ]);
241
+ /**
242
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
243
+ * If the given URL contains an {@link Actor} object, it tries to find
244
+ * the corresponding key in the `publicKey` or `assertionMethod` property.
245
+ * @template T The type of the key to fetch. Either {@link CryptographicKey}
246
+ * or {@link Multikey}.
247
+ * @param keyId The URL of the key.
248
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
249
+ * or {@link Multikey}.
250
+ * @param options Options for fetching the key. See {@link FetchKeyOptions}.
251
+ * @returns The fetched key or `null` if the key is not found.
252
+ * @since 1.3.0
253
+ */
254
+ function fetchKey(keyId, cls, options = {}) {
255
+ keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
256
+ return withFetchKeySpan(keyId, options.tracerProvider, () => fetchKeyInternal(keyId, cls, options));
257
+ }
258
+ /**
259
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL,
260
+ * preserving transport-level fetch failures for callers that need to inspect
261
+ * why the key could not be loaded.
262
+ *
263
+ * @template T The type of the key to fetch. Either {@link CryptographicKey}
264
+ * or {@link Multikey}.
265
+ * @param keyId The URL of the key.
266
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
267
+ * or {@link Multikey}.
268
+ * @param options Options for fetching the key.
269
+ * @returns The fetched key, or detailed fetch failure information.
270
+ * @since 2.1.0
271
+ */
272
+ async function fetchKeyDetailed(keyId, cls, options = {}) {
261
273
  const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
262
- keyId = typeof keyId === "string" ? keyId : keyId.href;
263
- if (keyCache != null) {
264
- const cachedKey = await keyCache.get(cacheKey);
265
- if (cachedKey instanceof cls && cachedKey.publicKey != null) {
266
- logger.debug("Key {keyId} found in cache.", { keyId });
274
+ return await withFetchKeySpan(cacheKey, options.tracerProvider, async () => {
275
+ return await fetchKeyWithResult(cacheKey, cls, options, async (cacheKey$1, keyId$1, keyCache, logger) => {
276
+ const fetchError = await keyCache?.getFetchError?.(cacheKey$1);
277
+ if (fetchError != null) {
278
+ logger.debug("Entry {keyId} found in cache with preserved fetch failure details.", { keyId: keyId$1 });
279
+ return {
280
+ key: null,
281
+ cached: true,
282
+ fetchError
283
+ };
284
+ }
285
+ logger.debug("Entry {keyId} found in cache, but no fetch failure details are available.", { keyId: keyId$1 });
267
286
  return {
268
- key: cachedKey,
287
+ key: null,
269
288
  cached: true
270
289
  };
271
- } else if (cachedKey === null) {
272
- logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
290
+ }, async (error, cacheKey$1, keyId$1, keyCache, logger) => {
291
+ logger.debug("Failed to fetch key {keyId}.", {
292
+ keyId: keyId$1,
293
+ error
294
+ });
295
+ await keyCache?.set(cacheKey$1, null);
296
+ if (error instanceof __fedify_vocab_runtime.FetchError && error.response != null) {
297
+ const fetchError$1 = {
298
+ status: error.response.status,
299
+ response: error.response.clone()
300
+ };
301
+ await keyCache?.setFetchError?.(cacheKey$1, fetchError$1);
302
+ return {
303
+ key: null,
304
+ cached: false,
305
+ fetchError: fetchError$1
306
+ };
307
+ }
308
+ const fetchError = { error: error instanceof Error ? error : new Error(String(error)) };
309
+ await keyCache?.setFetchError?.(cacheKey$1, fetchError);
273
310
  return {
274
311
  key: null,
275
- cached: true
312
+ cached: false,
313
+ fetchError
276
314
  };
277
- }
278
- }
279
- logger.debug("Fetching key {keyId} to verify signature...", { keyId });
280
- let document;
281
- try {
282
- const remoteDocument = await (documentLoader ?? (0, __fedify_vocab_runtime.getDocumentLoader)())(keyId);
283
- document = remoteDocument.document;
284
- } catch (_) {
285
- logger.debug("Failed to fetch key {keyId}.", { keyId });
286
- await keyCache?.set(cacheKey, null);
315
+ });
316
+ });
317
+ }
318
+ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
319
+ if (keyCache == null) return null;
320
+ const cachedKey = await keyCache.get(cacheKey);
321
+ if (cachedKey instanceof cls && cachedKey.publicKey != null) {
322
+ logger.debug("Key {keyId} found in cache.", { keyId });
323
+ return {
324
+ key: cachedKey,
325
+ cached: true
326
+ };
327
+ } else if (cachedKey === null) {
328
+ logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
287
329
  return {
288
330
  key: null,
289
- cached: false
331
+ cached: true
290
332
  };
291
333
  }
334
+ return null;
335
+ }
336
+ async function clearFetchErrorMetadata(keyId, keyCache) {
337
+ await keyCache?.setFetchError?.(keyId, null);
338
+ }
339
+ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
292
340
  let object;
293
341
  try {
294
342
  object = await __fedify_vocab.Object.fromJsonLd(document, {
@@ -308,6 +356,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
308
356
  if (e$1 instanceof TypeError) {
309
357
  logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
310
358
  await keyCache?.set(cacheKey, null);
359
+ await clearFetchErrorMetadata(cacheKey, keyCache);
311
360
  return {
312
361
  key: null,
313
362
  cached: false
@@ -346,6 +395,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
346
395
  actorType: object.constructor.name
347
396
  });
348
397
  await keyCache?.set(cacheKey, null);
398
+ await clearFetchErrorMetadata(cacheKey, keyCache);
349
399
  return {
350
400
  key: null,
351
401
  cached: false
@@ -354,6 +404,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
354
404
  } else {
355
405
  logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
356
406
  await keyCache?.set(cacheKey, null);
407
+ await clearFetchErrorMetadata(cacheKey, keyCache);
357
408
  return {
358
409
  key: null,
359
410
  cached: false
@@ -362,6 +413,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
362
413
  if (key.publicKey == null) {
363
414
  logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
364
415
  await keyCache?.set(cacheKey, null);
416
+ await clearFetchErrorMetadata(cacheKey, keyCache);
365
417
  return {
366
418
  key: null,
367
419
  cached: false
@@ -371,11 +423,57 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
371
423
  await keyCache.set(cacheKey, key);
372
424
  logger.debug("Key {keyId} cached.", { keyId });
373
425
  }
426
+ await clearFetchErrorMetadata(cacheKey, keyCache);
374
427
  return {
375
428
  key,
376
429
  cached: false
377
430
  };
378
431
  }
432
+ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
433
+ const logger = (0, __logtape_logtape.getLogger)([
434
+ "fedify",
435
+ "sig",
436
+ "key"
437
+ ]);
438
+ const keyId = cacheKey.href;
439
+ const keyCache = options.keyCache;
440
+ const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
441
+ if (cached?.key === null && cached.cached) return await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
442
+ if (cached != null) return cached;
443
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
444
+ let document;
445
+ try {
446
+ const remoteDocument = await (options.documentLoader ?? (0, __fedify_vocab_runtime.getDocumentLoader)())(keyId);
447
+ document = remoteDocument.document;
448
+ } catch (error) {
449
+ return await onFetchError(error, cacheKey, keyId, keyCache, logger);
450
+ }
451
+ return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
452
+ }
453
+ async function fetchKeyInternal(keyId, cls, options = {}) {
454
+ const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
455
+ return await fetchKeyWithResult(cacheKey, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
456
+ return {
457
+ key: null,
458
+ cached: true
459
+ };
460
+ }, async (error, cacheKey$1, keyId$1, keyCache, logger) => {
461
+ logger.debug("Failed to fetch key {keyId}.", {
462
+ keyId: keyId$1,
463
+ error
464
+ });
465
+ await keyCache?.set(cacheKey$1, null);
466
+ if (error instanceof __fedify_vocab_runtime.FetchError && error.response != null) await keyCache?.setFetchError?.(cacheKey$1, {
467
+ status: error.response.status,
468
+ response: error.response.clone()
469
+ });
470
+ else await keyCache?.setFetchError?.(cacheKey$1, { error: error instanceof Error ? error : new Error(String(error)) });
471
+ return {
472
+ key: null,
473
+ cached: false
474
+ };
475
+ });
476
+ }
379
477
 
380
478
  //#endregion
381
479
  //#region src/sig/http.ts
@@ -605,6 +703,55 @@ const supportedHashAlgorithms = {
605
703
  "sha-256": "SHA-256",
606
704
  "sha-512": "SHA-512"
607
705
  };
706
+ function noSignatureResult() {
707
+ return {
708
+ verified: false,
709
+ reason: { type: "noSignature" }
710
+ };
711
+ }
712
+ function invalidSignatureResult(keyId) {
713
+ return keyId == null ? {
714
+ verified: false,
715
+ reason: { type: "invalidSignature" }
716
+ } : {
717
+ verified: false,
718
+ reason: {
719
+ type: "invalidSignature",
720
+ keyId
721
+ }
722
+ };
723
+ }
724
+ function keyFetchErrorResult(keyId, result) {
725
+ return {
726
+ verified: false,
727
+ reason: {
728
+ type: "keyFetchError",
729
+ keyId,
730
+ result
731
+ }
732
+ };
733
+ }
734
+ function parseKeyId(value) {
735
+ if (value == null) return null;
736
+ try {
737
+ return new URL(value);
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+ function getKeyFetchErrorName(error) {
743
+ return error.name || error.constructor.name || "Error";
744
+ }
745
+ function recordVerificationResult(span, result) {
746
+ span.setAttribute("http_signatures.verified", result.verified);
747
+ if (result.verified === true) return;
748
+ const reason = result.reason;
749
+ span.setAttribute("http_signatures.failure_reason", reason.type);
750
+ if ("keyId" in reason && reason.keyId != null) span.setAttribute("http_signatures.key_id", reason.keyId.href);
751
+ if (reason.type !== "keyFetchError") return;
752
+ if ("status" in reason.result) span.setAttribute("http_signatures.key_fetch_status", reason.result.status);
753
+ else span.setAttribute("http_signatures.key_fetch_error", getKeyFetchErrorName(reason.result.error));
754
+ }
608
755
  /**
609
756
  * Verifies the signature of a request.
610
757
  *
@@ -619,6 +766,19 @@ const supportedHashAlgorithms = {
619
766
  * could not be verified.
620
767
  */
621
768
  async function verifyRequest(request, options = {}) {
769
+ const result = await verifyRequestDetailed(request, options);
770
+ return result.verified ? result.key : null;
771
+ }
772
+ /**
773
+ * Verifies the signature of a request and returns a structured failure reason
774
+ * when verification does not succeed.
775
+ *
776
+ * @param request The request to verify.
777
+ * @param options Options for verifying the request.
778
+ * @returns The verified public key, or a structured verification failure.
779
+ * @since 2.1.0
780
+ */
781
+ async function verifyRequestDetailed(request, options = {}) {
622
782
  const tracerProvider = options.tracerProvider ?? __opentelemetry_api.trace.getTracerProvider();
623
783
  const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
624
784
  return await tracer.startActiveSpan("http_signatures.verify", async (span) => {
@@ -630,11 +790,12 @@ async function verifyRequest(request, options = {}) {
630
790
  try {
631
791
  let spec = options.spec;
632
792
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
633
- let key;
634
- if (spec === "rfc9421") key = await verifyRequestRfc9421(request, span, options);
635
- else key = await verifyRequestDraft(request, span, options);
636
- if (key == null) span.setStatus({ code: __opentelemetry_api.SpanStatusCode.ERROR });
637
- return key;
793
+ let result;
794
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
795
+ else result = await verifyRequestDraft(request, span, options);
796
+ recordVerificationResult(span, result);
797
+ if (!result.verified) span.setStatus({ code: __opentelemetry_api.SpanStatusCode.ERROR });
798
+ return result;
638
799
  } catch (error) {
639
800
  span.setStatus({
640
801
  code: __opentelemetry_api.SpanStatusCode.ERROR,
@@ -654,27 +815,29 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
654
815
  ]);
655
816
  if (request.bodyUsed) {
656
817
  logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
657
- return null;
818
+ return noSignatureResult();
658
819
  } else if (request.body?.locked) {
659
820
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
660
- return null;
821
+ return noSignatureResult();
661
822
  }
662
823
  const originalRequest = request;
663
824
  request = request.clone();
664
- const dateHeader = request.headers.get("Date");
665
- if (dateHeader == null) {
666
- logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
667
- return null;
668
- }
669
825
  const sigHeader = request.headers.get("Signature");
670
826
  if (sigHeader == null) {
671
827
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
672
- return null;
828
+ return noSignatureResult();
829
+ }
830
+ const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
831
+ const parsedKeyId = parseKeyId(sigValues.keyId);
832
+ const dateHeader = request.headers.get("Date");
833
+ if (dateHeader == null) {
834
+ logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
835
+ return invalidSignatureResult(parsedKeyId);
673
836
  }
674
837
  const digestHeader = request.headers.get("Digest");
675
838
  if (request.method !== "GET" && request.method !== "HEAD" && digestHeader == null) {
676
839
  logger.debug("Failed to verify; no Digest header found.", { headers: Object.fromEntries(request.headers.entries()) });
677
- return null;
840
+ return invalidSignatureResult(parsedKeyId);
678
841
  }
679
842
  let body = null;
680
843
  if (digestHeader != null) {
@@ -692,7 +855,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
692
855
  digest: digestBase64,
693
856
  error
694
857
  });
695
- return null;
858
+ return invalidSignatureResult(parsedKeyId);
696
859
  }
697
860
  if (span.isRecording()) span.setAttribute(`http_signatures.digest.${algo}`, (0, byte_encodings_hex.encodeHex)(digest));
698
861
  const expectedDigest = await crypto.subtle.digest(supportedHashAlgorithms[algo], body);
@@ -702,7 +865,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
702
865
  digest: digestBase64,
703
866
  expectedDigest: (0, byte_encodings_base64.encodeBase64)(expectedDigest)
704
867
  });
705
- return null;
868
+ return invalidSignatureResult(parsedKeyId);
706
869
  }
707
870
  matched = true;
708
871
  }
@@ -711,7 +874,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
711
874
  supportedAlgorithms: Object.keys(supportedHashAlgorithms),
712
875
  algorithms: digests.map(([algo]) => algo)
713
876
  });
714
- return null;
877
+ return invalidSignatureResult(parsedKeyId);
715
878
  }
716
879
  }
717
880
  const date = Temporal.Instant.from(new Date(dateHeader).toISOString());
@@ -723,25 +886,24 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
723
886
  date: date.toString(),
724
887
  now: now.toString()
725
888
  });
726
- return null;
889
+ return invalidSignatureResult(parsedKeyId);
727
890
  } else if (Temporal.Instant.compare(date, now.subtract(tw)) < 0) {
728
891
  logger.debug("Failed to verify; Date is too far in the past.", {
729
892
  date: date.toString(),
730
893
  now: now.toString()
731
894
  });
732
- return null;
895
+ return invalidSignatureResult(parsedKeyId);
733
896
  }
734
897
  }
735
- const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
736
898
  if (!("keyId" in sigValues)) {
737
899
  logger.debug("Failed to verify; no keyId field found in the Signature header.", { signature: sigHeader });
738
- return null;
900
+ return invalidSignatureResult(null);
739
901
  } else if (!("headers" in sigValues)) {
740
902
  logger.debug("Failed to verify; no headers field found in the Signature header.", { signature: sigHeader });
741
- return null;
903
+ return invalidSignatureResult(parsedKeyId);
742
904
  } else if (!("signature" in sigValues)) {
743
905
  logger.debug("Failed to verify; no signature field found in the Signature header.", { signature: sigHeader });
744
- return null;
906
+ return invalidSignatureResult(parsedKeyId);
745
907
  }
746
908
  if ("expires" in sigValues) {
747
909
  const expiresSeconds = parseInt(sigValues.expires);
@@ -750,7 +912,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
750
912
  expires: sigValues.expires,
751
913
  signature: sigHeader
752
914
  });
753
- return null;
915
+ return invalidSignatureResult(parsedKeyId);
754
916
  }
755
917
  const expires = Temporal.Instant.fromEpochMilliseconds(expiresSeconds * 1e3);
756
918
  if (Temporal.Instant.compare(now, expires) > 0) {
@@ -759,7 +921,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
759
921
  now: now.toString(),
760
922
  signature: sigHeader
761
923
  });
762
- return null;
924
+ return invalidSignatureResult(parsedKeyId);
763
925
  }
764
926
  }
765
927
  if ("created" in sigValues) {
@@ -769,7 +931,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
769
931
  created: sigValues.created,
770
932
  signature: sigHeader
771
933
  });
772
- return null;
934
+ return invalidSignatureResult(parsedKeyId);
773
935
  }
774
936
  if (timeWindow !== false) {
775
937
  const created = Temporal.Instant.fromEpochMilliseconds(createdSeconds * 1e3);
@@ -779,34 +941,37 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
779
941
  created: created.toString(),
780
942
  now: now.toString()
781
943
  });
782
- return null;
944
+ return invalidSignatureResult(parsedKeyId);
783
945
  } else if (Temporal.Instant.compare(created, now.subtract(tw)) < 0) {
784
946
  logger.debug("Failed to verify; created is too far in the past.", {
785
947
  created: created.toString(),
786
948
  now: now.toString()
787
949
  });
788
- return null;
950
+ return invalidSignatureResult(parsedKeyId);
789
951
  }
790
952
  }
791
953
  }
792
954
  const { keyId, headers, signature } = sigValues;
955
+ const keyIdUrl = parseKeyId(keyId);
956
+ if (keyIdUrl == null) return invalidSignatureResult(null);
793
957
  span?.setAttribute("http_signatures.key_id", keyId);
794
958
  if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
795
- const { key, cached } = await fetchKey(new URL(keyId), __fedify_vocab.CryptographicKey, {
959
+ const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, __fedify_vocab.CryptographicKey, {
796
960
  documentLoader,
797
961
  contextLoader,
798
962
  keyCache,
799
963
  tracerProvider
800
964
  });
801
- if (key == null) return null;
965
+ if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
966
+ if (key == null) return invalidSignatureResult(keyIdUrl);
802
967
  const headerNames = headers.split(/\s+/g);
803
968
  if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
804
969
  logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
805
- return null;
970
+ return invalidSignatureResult(keyIdUrl);
806
971
  }
807
972
  if (body != null && !headerNames.includes("digest")) {
808
973
  logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
809
- return null;
974
+ return invalidSignatureResult(keyIdUrl);
810
975
  }
811
976
  const message = headerNames.map((name$1) => `${name$1}: ` + (name$1 === "(request-target)" ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}` : name$1 === "(created)" ? sigValues.created ?? "" : name$1 === "(expires)" ? sigValues.expires ?? "" : name$1 === "host" ? request.headers.get("host") ?? new URL(request.url).host : request.headers.get(name$1))).join("\n");
812
977
  const sig = (0, byte_encodings_base64.decodeBase64)(signature);
@@ -819,7 +984,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
819
984
  signature,
820
985
  message
821
986
  });
822
- return await verifyRequest(originalRequest, {
987
+ return await verifyRequestDetailed(originalRequest, {
823
988
  documentLoader,
824
989
  contextLoader,
825
990
  timeWindow,
@@ -835,9 +1000,12 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
835
1000
  signature,
836
1001
  message
837
1002
  });
838
- return null;
1003
+ return invalidSignatureResult(keyIdUrl);
839
1004
  }
840
- return key;
1005
+ return {
1006
+ verified: true,
1007
+ key
1008
+ };
841
1009
  }
842
1010
  /**
843
1011
  * RFC 9421 map of algorithm identifiers to WebCrypto algorithms
@@ -908,22 +1076,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
908
1076
  ]);
909
1077
  if (request.bodyUsed) {
910
1078
  logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
911
- return null;
1079
+ return noSignatureResult();
912
1080
  } else if (request.body?.locked) {
913
1081
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
914
- return null;
1082
+ return noSignatureResult();
915
1083
  }
916
1084
  const originalRequest = request;
917
1085
  request = request.clone();
918
1086
  const signatureInputHeader = request.headers.get("Signature-Input");
919
1087
  if (!signatureInputHeader) {
920
1088
  logger.debug("Failed to verify; no Signature-Input header found.", { headers: Object.fromEntries(request.headers.entries()) });
921
- return null;
1089
+ return noSignatureResult();
922
1090
  }
923
1091
  const signatureHeader = request.headers.get("Signature");
924
1092
  if (!signatureHeader) {
925
1093
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
926
- return null;
1094
+ return noSignatureResult();
927
1095
  }
928
1096
  const signatureInputs = parseRfc9421SignatureInput(signatureInputHeader);
929
1097
  logger.debug("Parsed Signature-Input header: {signatureInputs}", { signatureInputs });
@@ -931,18 +1099,23 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
931
1099
  const signatureNames = Object.keys(signatureInputs);
932
1100
  if (signatureNames.length === 0) {
933
1101
  logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
934
- return null;
1102
+ return invalidSignatureResult(null);
935
1103
  }
936
- let validKey = null;
1104
+ let failure = noSignatureResult();
937
1105
  for (const sigName of signatureNames) {
938
- if (!signatures[sigName]) continue;
1106
+ if (!signatures[sigName]) {
1107
+ failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
1108
+ continue;
1109
+ }
939
1110
  const sigInput = signatureInputs[sigName];
940
1111
  const sigBytes = signatures[sigName];
1112
+ const keyId = parseKeyId(sigInput.keyId);
941
1113
  if (!sigInput.keyId) {
942
1114
  logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
943
1115
  signatureName: sigName,
944
1116
  signatureInput: signatureInputHeader
945
1117
  });
1118
+ failure = invalidSignatureResult(null);
946
1119
  continue;
947
1120
  }
948
1121
  if (!sigInput.created) {
@@ -950,6 +1123,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
950
1123
  signatureName: sigName,
951
1124
  signatureInput: signatureInputHeader
952
1125
  });
1126
+ failure = invalidSignatureResult(keyId);
953
1127
  continue;
954
1128
  }
955
1129
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -961,12 +1135,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
961
1135
  created: signatureCreated.toString(),
962
1136
  now: now.toString()
963
1137
  });
1138
+ failure = invalidSignatureResult(keyId);
964
1139
  continue;
965
1140
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
966
1141
  logger.debug("Failed to verify; signature created time is too far in the past.", {
967
1142
  created: signatureCreated.toString(),
968
1143
  now: now.toString()
969
1144
  });
1145
+ failure = invalidSignatureResult(keyId);
970
1146
  continue;
971
1147
  }
972
1148
  }
@@ -974,25 +1150,36 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
974
1150
  const contentDigestHeader = request.headers.get("Content-Digest");
975
1151
  if (!contentDigestHeader) {
976
1152
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1153
+ failure = invalidSignatureResult(keyId);
977
1154
  continue;
978
1155
  }
979
1156
  const body = await request.arrayBuffer();
980
1157
  const digestValid = await verifyRfc9421ContentDigest(contentDigestHeader, body);
981
1158
  if (!digestValid) {
982
1159
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1160
+ failure = invalidSignatureResult(keyId);
983
1161
  continue;
984
1162
  }
985
1163
  }
986
1164
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
987
1165
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
988
- const { key, cached } = await fetchKey(new URL(sigInput.keyId), __fedify_vocab.CryptographicKey, {
1166
+ if (keyId == null) {
1167
+ failure = invalidSignatureResult(null);
1168
+ continue;
1169
+ }
1170
+ const { key, cached, fetchError } = await fetchKeyDetailed(keyId, __fedify_vocab.CryptographicKey, {
989
1171
  documentLoader,
990
1172
  contextLoader,
991
1173
  keyCache,
992
1174
  tracerProvider
993
1175
  });
1176
+ if (fetchError != null) {
1177
+ failure = keyFetchErrorResult(keyId, fetchError);
1178
+ continue;
1179
+ }
994
1180
  if (!key) {
995
1181
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1182
+ failure = invalidSignatureResult(keyId);
996
1183
  continue;
997
1184
  }
998
1185
  let alg = sigInput.alg?.toLowerCase();
@@ -1009,6 +1196,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1009
1196
  algorithm: sigInput.alg,
1010
1197
  supported: Object.keys(rfc9421AlgorithmMap)
1011
1198
  });
1199
+ failure = invalidSignatureResult(keyId);
1012
1200
  continue;
1013
1201
  }
1014
1202
  let signatureBase;
@@ -1019,41 +1207,47 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1019
1207
  error,
1020
1208
  signatureInput: sigInput
1021
1209
  });
1210
+ failure = invalidSignatureResult(keyId);
1022
1211
  continue;
1023
1212
  }
1024
1213
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1025
1214
  span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
1026
1215
  try {
1027
1216
  const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
1028
- if (verified) {
1029
- validKey = key;
1030
- break;
1031
- } else if (cached) {
1217
+ if (verified) return {
1218
+ verified: true,
1219
+ key
1220
+ };
1221
+ else if (cached) {
1032
1222
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1033
- return await verifyRequest(originalRequest, {
1223
+ return await verifyRequestDetailed(originalRequest, {
1034
1224
  documentLoader,
1035
1225
  contextLoader,
1036
1226
  timeWindow,
1037
1227
  currentTime,
1038
1228
  keyCache: {
1039
1229
  get: () => Promise.resolve(void 0),
1040
- set: async (keyId, key$1) => await keyCache?.set(keyId, key$1)
1230
+ set: async (keyId$1, key$1) => await keyCache?.set(keyId$1, key$1)
1041
1231
  },
1042
1232
  spec: "rfc9421"
1043
1233
  });
1044
- } else logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1045
- keyId: sigInput.keyId,
1046
- signatureBase
1047
- });
1234
+ } else {
1235
+ logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1236
+ keyId: sigInput.keyId,
1237
+ signatureBase
1238
+ });
1239
+ failure = invalidSignatureResult(keyId);
1240
+ }
1048
1241
  } catch (error) {
1049
1242
  logger.debug("Error during signature verification: {error}", {
1050
1243
  error,
1051
1244
  keyId: sigInput.keyId,
1052
1245
  algorithm: sigInput.alg
1053
1246
  });
1247
+ failure = invalidSignatureResult(keyId);
1054
1248
  }
1055
1249
  }
1056
- return validKey;
1250
+ return failure;
1057
1251
  }
1058
1252
  /**
1059
1253
  * Helper function to create a new Request for redirect handling.
@@ -1195,6 +1389,12 @@ Object.defineProperty(exports, 'fetchKey', {
1195
1389
  return fetchKey;
1196
1390
  }
1197
1391
  });
1392
+ Object.defineProperty(exports, 'fetchKeyDetailed', {
1393
+ enumerable: true,
1394
+ get: function () {
1395
+ return fetchKeyDetailed;
1396
+ }
1397
+ });
1198
1398
  Object.defineProperty(exports, 'generateCryptoKeyPair', {
1199
1399
  enumerable: true,
1200
1400
  get: function () {
@@ -1224,4 +1424,10 @@ Object.defineProperty(exports, 'verifyRequest', {
1224
1424
  get: function () {
1225
1425
  return verifyRequest;
1226
1426
  }
1427
+ });
1428
+ Object.defineProperty(exports, 'verifyRequestDetailed', {
1429
+ enumerable: true,
1430
+ get: function () {
1431
+ return verifyRequestDetailed;
1432
+ }
1227
1433
  });