@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
@@ -9,11 +9,11 @@ import { encodeHex } from "byte-encodings/hex";
9
9
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  import { Item, decodeDict, encodeItem } from "structured-field-values";
12
- import { getDocumentLoader } from "@fedify/vocab-runtime";
12
+ import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
13
13
 
14
14
  //#region deno.json
15
15
  var name = "@fedify/fedify";
16
- var version = "2.1.0-dev.503+1172d43b";
16
+ var version = "2.1.0-dev.523+150998a5";
17
17
  var license = "MIT";
18
18
  var exports = {
19
19
  ".": "./src/mod.ts",
@@ -207,24 +207,10 @@ async function importJwk(jwk, type) {
207
207
  validateCryptoKey(key, type);
208
208
  return key;
209
209
  }
210
- /**
211
- * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
212
- * If the given URL contains an {@link Actor} object, it tries to find
213
- * the corresponding key in the `publicKey` or `assertionMethod` property.
214
- * @template T The type of the key to fetch. Either {@link CryptographicKey}
215
- * or {@link Multikey}.
216
- * @param keyId The URL of the key.
217
- * @param cls The class of the key to fetch. Either {@link CryptographicKey}
218
- * or {@link Multikey}.
219
- * @param options Options for fetching the key. See {@link FetchKeyOptions}.
220
- * @returns The fetched key or `null` if the key is not found.
221
- * @since 1.3.0
222
- */
223
- function fetchKey(keyId, cls, options = {}) {
224
- const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
210
+ async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
211
+ tracerProvider ??= trace.getTracerProvider();
225
212
  const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
226
- keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
227
- return tracer.startActiveSpan("activitypub.fetch_key", {
213
+ return await tracer.startActiveSpan("activitypub.fetch_key", {
228
214
  kind: SpanKind.CLIENT,
229
215
  attributes: {
230
216
  "http.method": "GET",
@@ -237,7 +223,7 @@ function fetchKey(keyId, cls, options = {}) {
237
223
  }
238
224
  }, async (span) => {
239
225
  try {
240
- const result = await fetchKeyInternal(keyId, cls, options);
226
+ const result = await fetcher();
241
227
  span.setAttribute("activitypub.actor.key.cached", result.cached);
242
228
  return result;
243
229
  } catch (e) {
@@ -251,43 +237,105 @@ function fetchKey(keyId, cls, options = {}) {
251
237
  }
252
238
  });
253
239
  }
254
- async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider } = {}) {
255
- const logger = getLogger([
256
- "fedify",
257
- "sig",
258
- "key"
259
- ]);
240
+ /**
241
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
242
+ * If the given URL contains an {@link Actor} object, it tries to find
243
+ * the corresponding key in the `publicKey` or `assertionMethod` property.
244
+ * @template T The type of the key to fetch. Either {@link CryptographicKey}
245
+ * or {@link Multikey}.
246
+ * @param keyId The URL of the key.
247
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
248
+ * or {@link Multikey}.
249
+ * @param options Options for fetching the key. See {@link FetchKeyOptions}.
250
+ * @returns The fetched key or `null` if the key is not found.
251
+ * @since 1.3.0
252
+ */
253
+ function fetchKey(keyId, cls, options = {}) {
254
+ keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
255
+ return withFetchKeySpan(keyId, options.tracerProvider, () => fetchKeyInternal(keyId, cls, options));
256
+ }
257
+ /**
258
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL,
259
+ * preserving transport-level fetch failures for callers that need to inspect
260
+ * why the key could not be loaded.
261
+ *
262
+ * @template T The type of the key to fetch. Either {@link CryptographicKey}
263
+ * or {@link Multikey}.
264
+ * @param keyId The URL of the key.
265
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
266
+ * or {@link Multikey}.
267
+ * @param options Options for fetching the key.
268
+ * @returns The fetched key, or detailed fetch failure information.
269
+ * @since 2.1.0
270
+ */
271
+ async function fetchKeyDetailed(keyId, cls, options = {}) {
260
272
  const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
261
- keyId = typeof keyId === "string" ? keyId : keyId.href;
262
- if (keyCache != null) {
263
- const cachedKey = await keyCache.get(cacheKey);
264
- if (cachedKey instanceof cls && cachedKey.publicKey != null) {
265
- logger.debug("Key {keyId} found in cache.", { keyId });
273
+ return await withFetchKeySpan(cacheKey, options.tracerProvider, async () => {
274
+ return await fetchKeyWithResult(cacheKey, cls, options, async (cacheKey$1, keyId$1, keyCache, logger) => {
275
+ const fetchError = await keyCache?.getFetchError?.(cacheKey$1);
276
+ if (fetchError != null) {
277
+ logger.debug("Entry {keyId} found in cache with preserved fetch failure details.", { keyId: keyId$1 });
278
+ return {
279
+ key: null,
280
+ cached: true,
281
+ fetchError
282
+ };
283
+ }
284
+ logger.debug("Entry {keyId} found in cache, but no fetch failure details are available.", { keyId: keyId$1 });
266
285
  return {
267
- key: cachedKey,
286
+ key: null,
268
287
  cached: true
269
288
  };
270
- } else if (cachedKey === null) {
271
- logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
289
+ }, async (error, cacheKey$1, keyId$1, keyCache, logger) => {
290
+ logger.debug("Failed to fetch key {keyId}.", {
291
+ keyId: keyId$1,
292
+ error
293
+ });
294
+ await keyCache?.set(cacheKey$1, null);
295
+ if (error instanceof FetchError && error.response != null) {
296
+ const fetchError$1 = {
297
+ status: error.response.status,
298
+ response: error.response.clone()
299
+ };
300
+ await keyCache?.setFetchError?.(cacheKey$1, fetchError$1);
301
+ return {
302
+ key: null,
303
+ cached: false,
304
+ fetchError: fetchError$1
305
+ };
306
+ }
307
+ const fetchError = { error: error instanceof Error ? error : new Error(String(error)) };
308
+ await keyCache?.setFetchError?.(cacheKey$1, fetchError);
272
309
  return {
273
310
  key: null,
274
- cached: true
311
+ cached: false,
312
+ fetchError
275
313
  };
276
- }
277
- }
278
- logger.debug("Fetching key {keyId} to verify signature...", { keyId });
279
- let document;
280
- try {
281
- const remoteDocument = await (documentLoader ?? getDocumentLoader())(keyId);
282
- document = remoteDocument.document;
283
- } catch (_) {
284
- logger.debug("Failed to fetch key {keyId}.", { keyId });
285
- await keyCache?.set(cacheKey, null);
314
+ });
315
+ });
316
+ }
317
+ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
318
+ if (keyCache == null) return null;
319
+ const cachedKey = await keyCache.get(cacheKey);
320
+ if (cachedKey instanceof cls && cachedKey.publicKey != null) {
321
+ logger.debug("Key {keyId} found in cache.", { keyId });
322
+ return {
323
+ key: cachedKey,
324
+ cached: true
325
+ };
326
+ } else if (cachedKey === null) {
327
+ logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
286
328
  return {
287
329
  key: null,
288
- cached: false
330
+ cached: true
289
331
  };
290
332
  }
333
+ return null;
334
+ }
335
+ async function clearFetchErrorMetadata(keyId, keyCache) {
336
+ await keyCache?.setFetchError?.(keyId, null);
337
+ }
338
+ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
291
339
  let object;
292
340
  try {
293
341
  object = await Object$1.fromJsonLd(document, {
@@ -307,6 +355,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
307
355
  if (e$1 instanceof TypeError) {
308
356
  logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
309
357
  await keyCache?.set(cacheKey, null);
358
+ await clearFetchErrorMetadata(cacheKey, keyCache);
310
359
  return {
311
360
  key: null,
312
361
  cached: false
@@ -345,6 +394,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
345
394
  actorType: object.constructor.name
346
395
  });
347
396
  await keyCache?.set(cacheKey, null);
397
+ await clearFetchErrorMetadata(cacheKey, keyCache);
348
398
  return {
349
399
  key: null,
350
400
  cached: false
@@ -353,6 +403,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
353
403
  } else {
354
404
  logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
355
405
  await keyCache?.set(cacheKey, null);
406
+ await clearFetchErrorMetadata(cacheKey, keyCache);
356
407
  return {
357
408
  key: null,
358
409
  cached: false
@@ -361,6 +412,7 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
361
412
  if (key.publicKey == null) {
362
413
  logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
363
414
  await keyCache?.set(cacheKey, null);
415
+ await clearFetchErrorMetadata(cacheKey, keyCache);
364
416
  return {
365
417
  key: null,
366
418
  cached: false
@@ -370,11 +422,57 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
370
422
  await keyCache.set(cacheKey, key);
371
423
  logger.debug("Key {keyId} cached.", { keyId });
372
424
  }
425
+ await clearFetchErrorMetadata(cacheKey, keyCache);
373
426
  return {
374
427
  key,
375
428
  cached: false
376
429
  };
377
430
  }
431
+ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
432
+ const logger = getLogger([
433
+ "fedify",
434
+ "sig",
435
+ "key"
436
+ ]);
437
+ const keyId = cacheKey.href;
438
+ const keyCache = options.keyCache;
439
+ const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
440
+ if (cached?.key === null && cached.cached) return await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
441
+ if (cached != null) return cached;
442
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
443
+ let document;
444
+ try {
445
+ const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
446
+ document = remoteDocument.document;
447
+ } catch (error) {
448
+ return await onFetchError(error, cacheKey, keyId, keyCache, logger);
449
+ }
450
+ return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
451
+ }
452
+ async function fetchKeyInternal(keyId, cls, options = {}) {
453
+ const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
454
+ return await fetchKeyWithResult(cacheKey, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
455
+ return {
456
+ key: null,
457
+ cached: true
458
+ };
459
+ }, async (error, cacheKey$1, keyId$1, keyCache, logger) => {
460
+ logger.debug("Failed to fetch key {keyId}.", {
461
+ keyId: keyId$1,
462
+ error
463
+ });
464
+ await keyCache?.set(cacheKey$1, null);
465
+ if (error instanceof FetchError && error.response != null) await keyCache?.setFetchError?.(cacheKey$1, {
466
+ status: error.response.status,
467
+ response: error.response.clone()
468
+ });
469
+ else await keyCache?.setFetchError?.(cacheKey$1, { error: error instanceof Error ? error : new Error(String(error)) });
470
+ return {
471
+ key: null,
472
+ cached: false
473
+ };
474
+ });
475
+ }
378
476
 
379
477
  //#endregion
380
478
  //#region src/sig/http.ts
@@ -604,6 +702,55 @@ const supportedHashAlgorithms = {
604
702
  "sha-256": "SHA-256",
605
703
  "sha-512": "SHA-512"
606
704
  };
705
+ function noSignatureResult() {
706
+ return {
707
+ verified: false,
708
+ reason: { type: "noSignature" }
709
+ };
710
+ }
711
+ function invalidSignatureResult(keyId) {
712
+ return keyId == null ? {
713
+ verified: false,
714
+ reason: { type: "invalidSignature" }
715
+ } : {
716
+ verified: false,
717
+ reason: {
718
+ type: "invalidSignature",
719
+ keyId
720
+ }
721
+ };
722
+ }
723
+ function keyFetchErrorResult(keyId, result) {
724
+ return {
725
+ verified: false,
726
+ reason: {
727
+ type: "keyFetchError",
728
+ keyId,
729
+ result
730
+ }
731
+ };
732
+ }
733
+ function parseKeyId(value) {
734
+ if (value == null) return null;
735
+ try {
736
+ return new URL(value);
737
+ } catch {
738
+ return null;
739
+ }
740
+ }
741
+ function getKeyFetchErrorName(error) {
742
+ return error.name || error.constructor.name || "Error";
743
+ }
744
+ function recordVerificationResult(span, result) {
745
+ span.setAttribute("http_signatures.verified", result.verified);
746
+ if (result.verified === true) return;
747
+ const reason = result.reason;
748
+ span.setAttribute("http_signatures.failure_reason", reason.type);
749
+ if ("keyId" in reason && reason.keyId != null) span.setAttribute("http_signatures.key_id", reason.keyId.href);
750
+ if (reason.type !== "keyFetchError") return;
751
+ if ("status" in reason.result) span.setAttribute("http_signatures.key_fetch_status", reason.result.status);
752
+ else span.setAttribute("http_signatures.key_fetch_error", getKeyFetchErrorName(reason.result.error));
753
+ }
607
754
  /**
608
755
  * Verifies the signature of a request.
609
756
  *
@@ -618,6 +765,19 @@ const supportedHashAlgorithms = {
618
765
  * could not be verified.
619
766
  */
620
767
  async function verifyRequest(request, options = {}) {
768
+ const result = await verifyRequestDetailed(request, options);
769
+ return result.verified ? result.key : null;
770
+ }
771
+ /**
772
+ * Verifies the signature of a request and returns a structured failure reason
773
+ * when verification does not succeed.
774
+ *
775
+ * @param request The request to verify.
776
+ * @param options Options for verifying the request.
777
+ * @returns The verified public key, or a structured verification failure.
778
+ * @since 2.1.0
779
+ */
780
+ async function verifyRequestDetailed(request, options = {}) {
621
781
  const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
622
782
  const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
623
783
  return await tracer.startActiveSpan("http_signatures.verify", async (span) => {
@@ -629,11 +789,12 @@ async function verifyRequest(request, options = {}) {
629
789
  try {
630
790
  let spec = options.spec;
631
791
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
632
- let key;
633
- if (spec === "rfc9421") key = await verifyRequestRfc9421(request, span, options);
634
- else key = await verifyRequestDraft(request, span, options);
635
- if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
636
- return key;
792
+ let result;
793
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
794
+ else result = await verifyRequestDraft(request, span, options);
795
+ recordVerificationResult(span, result);
796
+ if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
797
+ return result;
637
798
  } catch (error) {
638
799
  span.setStatus({
639
800
  code: SpanStatusCode.ERROR,
@@ -653,27 +814,29 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
653
814
  ]);
654
815
  if (request.bodyUsed) {
655
816
  logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
656
- return null;
817
+ return noSignatureResult();
657
818
  } else if (request.body?.locked) {
658
819
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
659
- return null;
820
+ return noSignatureResult();
660
821
  }
661
822
  const originalRequest = request;
662
823
  request = request.clone();
663
- const dateHeader = request.headers.get("Date");
664
- if (dateHeader == null) {
665
- logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
666
- return null;
667
- }
668
824
  const sigHeader = request.headers.get("Signature");
669
825
  if (sigHeader == null) {
670
826
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
671
- return null;
827
+ return noSignatureResult();
828
+ }
829
+ 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]]));
830
+ const parsedKeyId = parseKeyId(sigValues.keyId);
831
+ const dateHeader = request.headers.get("Date");
832
+ if (dateHeader == null) {
833
+ logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
834
+ return invalidSignatureResult(parsedKeyId);
672
835
  }
673
836
  const digestHeader = request.headers.get("Digest");
674
837
  if (request.method !== "GET" && request.method !== "HEAD" && digestHeader == null) {
675
838
  logger.debug("Failed to verify; no Digest header found.", { headers: Object.fromEntries(request.headers.entries()) });
676
- return null;
839
+ return invalidSignatureResult(parsedKeyId);
677
840
  }
678
841
  let body = null;
679
842
  if (digestHeader != null) {
@@ -691,7 +854,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
691
854
  digest: digestBase64,
692
855
  error
693
856
  });
694
- return null;
857
+ return invalidSignatureResult(parsedKeyId);
695
858
  }
696
859
  if (span.isRecording()) span.setAttribute(`http_signatures.digest.${algo}`, encodeHex(digest));
697
860
  const expectedDigest = await crypto.subtle.digest(supportedHashAlgorithms[algo], body);
@@ -701,7 +864,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
701
864
  digest: digestBase64,
702
865
  expectedDigest: encodeBase64(expectedDigest)
703
866
  });
704
- return null;
867
+ return invalidSignatureResult(parsedKeyId);
705
868
  }
706
869
  matched = true;
707
870
  }
@@ -710,7 +873,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
710
873
  supportedAlgorithms: Object.keys(supportedHashAlgorithms),
711
874
  algorithms: digests.map(([algo]) => algo)
712
875
  });
713
- return null;
876
+ return invalidSignatureResult(parsedKeyId);
714
877
  }
715
878
  }
716
879
  const date = Temporal.Instant.from(new Date(dateHeader).toISOString());
@@ -722,25 +885,24 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
722
885
  date: date.toString(),
723
886
  now: now.toString()
724
887
  });
725
- return null;
888
+ return invalidSignatureResult(parsedKeyId);
726
889
  } else if (Temporal.Instant.compare(date, now.subtract(tw)) < 0) {
727
890
  logger.debug("Failed to verify; Date is too far in the past.", {
728
891
  date: date.toString(),
729
892
  now: now.toString()
730
893
  });
731
- return null;
894
+ return invalidSignatureResult(parsedKeyId);
732
895
  }
733
896
  }
734
- 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]]));
735
897
  if (!("keyId" in sigValues)) {
736
898
  logger.debug("Failed to verify; no keyId field found in the Signature header.", { signature: sigHeader });
737
- return null;
899
+ return invalidSignatureResult(null);
738
900
  } else if (!("headers" in sigValues)) {
739
901
  logger.debug("Failed to verify; no headers field found in the Signature header.", { signature: sigHeader });
740
- return null;
902
+ return invalidSignatureResult(parsedKeyId);
741
903
  } else if (!("signature" in sigValues)) {
742
904
  logger.debug("Failed to verify; no signature field found in the Signature header.", { signature: sigHeader });
743
- return null;
905
+ return invalidSignatureResult(parsedKeyId);
744
906
  }
745
907
  if ("expires" in sigValues) {
746
908
  const expiresSeconds = parseInt(sigValues.expires);
@@ -749,7 +911,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
749
911
  expires: sigValues.expires,
750
912
  signature: sigHeader
751
913
  });
752
- return null;
914
+ return invalidSignatureResult(parsedKeyId);
753
915
  }
754
916
  const expires = Temporal.Instant.fromEpochMilliseconds(expiresSeconds * 1e3);
755
917
  if (Temporal.Instant.compare(now, expires) > 0) {
@@ -758,7 +920,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
758
920
  now: now.toString(),
759
921
  signature: sigHeader
760
922
  });
761
- return null;
923
+ return invalidSignatureResult(parsedKeyId);
762
924
  }
763
925
  }
764
926
  if ("created" in sigValues) {
@@ -768,7 +930,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
768
930
  created: sigValues.created,
769
931
  signature: sigHeader
770
932
  });
771
- return null;
933
+ return invalidSignatureResult(parsedKeyId);
772
934
  }
773
935
  if (timeWindow !== false) {
774
936
  const created = Temporal.Instant.fromEpochMilliseconds(createdSeconds * 1e3);
@@ -778,34 +940,37 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
778
940
  created: created.toString(),
779
941
  now: now.toString()
780
942
  });
781
- return null;
943
+ return invalidSignatureResult(parsedKeyId);
782
944
  } else if (Temporal.Instant.compare(created, now.subtract(tw)) < 0) {
783
945
  logger.debug("Failed to verify; created is too far in the past.", {
784
946
  created: created.toString(),
785
947
  now: now.toString()
786
948
  });
787
- return null;
949
+ return invalidSignatureResult(parsedKeyId);
788
950
  }
789
951
  }
790
952
  }
791
953
  const { keyId, headers, signature } = sigValues;
954
+ const keyIdUrl = parseKeyId(keyId);
955
+ if (keyIdUrl == null) return invalidSignatureResult(null);
792
956
  span?.setAttribute("http_signatures.key_id", keyId);
793
957
  if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
794
- const { key, cached } = await fetchKey(new URL(keyId), CryptographicKey, {
958
+ const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, CryptographicKey, {
795
959
  documentLoader,
796
960
  contextLoader,
797
961
  keyCache,
798
962
  tracerProvider
799
963
  });
800
- if (key == null) return null;
964
+ if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
965
+ if (key == null) return invalidSignatureResult(keyIdUrl);
801
966
  const headerNames = headers.split(/\s+/g);
802
967
  if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
803
968
  logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
804
- return null;
969
+ return invalidSignatureResult(keyIdUrl);
805
970
  }
806
971
  if (body != null && !headerNames.includes("digest")) {
807
972
  logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
808
- return null;
973
+ return invalidSignatureResult(keyIdUrl);
809
974
  }
810
975
  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");
811
976
  const sig = decodeBase64(signature);
@@ -818,7 +983,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
818
983
  signature,
819
984
  message
820
985
  });
821
- return await verifyRequest(originalRequest, {
986
+ return await verifyRequestDetailed(originalRequest, {
822
987
  documentLoader,
823
988
  contextLoader,
824
989
  timeWindow,
@@ -834,9 +999,12 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
834
999
  signature,
835
1000
  message
836
1001
  });
837
- return null;
1002
+ return invalidSignatureResult(keyIdUrl);
838
1003
  }
839
- return key;
1004
+ return {
1005
+ verified: true,
1006
+ key
1007
+ };
840
1008
  }
841
1009
  /**
842
1010
  * RFC 9421 map of algorithm identifiers to WebCrypto algorithms
@@ -907,22 +1075,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
907
1075
  ]);
908
1076
  if (request.bodyUsed) {
909
1077
  logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
910
- return null;
1078
+ return noSignatureResult();
911
1079
  } else if (request.body?.locked) {
912
1080
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
913
- return null;
1081
+ return noSignatureResult();
914
1082
  }
915
1083
  const originalRequest = request;
916
1084
  request = request.clone();
917
1085
  const signatureInputHeader = request.headers.get("Signature-Input");
918
1086
  if (!signatureInputHeader) {
919
1087
  logger.debug("Failed to verify; no Signature-Input header found.", { headers: Object.fromEntries(request.headers.entries()) });
920
- return null;
1088
+ return noSignatureResult();
921
1089
  }
922
1090
  const signatureHeader = request.headers.get("Signature");
923
1091
  if (!signatureHeader) {
924
1092
  logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
925
- return null;
1093
+ return noSignatureResult();
926
1094
  }
927
1095
  const signatureInputs = parseRfc9421SignatureInput(signatureInputHeader);
928
1096
  logger.debug("Parsed Signature-Input header: {signatureInputs}", { signatureInputs });
@@ -930,18 +1098,23 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
930
1098
  const signatureNames = Object.keys(signatureInputs);
931
1099
  if (signatureNames.length === 0) {
932
1100
  logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
933
- return null;
1101
+ return invalidSignatureResult(null);
934
1102
  }
935
- let validKey = null;
1103
+ let failure = noSignatureResult();
936
1104
  for (const sigName of signatureNames) {
937
- if (!signatures[sigName]) continue;
1105
+ if (!signatures[sigName]) {
1106
+ failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
1107
+ continue;
1108
+ }
938
1109
  const sigInput = signatureInputs[sigName];
939
1110
  const sigBytes = signatures[sigName];
1111
+ const keyId = parseKeyId(sigInput.keyId);
940
1112
  if (!sigInput.keyId) {
941
1113
  logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
942
1114
  signatureName: sigName,
943
1115
  signatureInput: signatureInputHeader
944
1116
  });
1117
+ failure = invalidSignatureResult(null);
945
1118
  continue;
946
1119
  }
947
1120
  if (!sigInput.created) {
@@ -949,6 +1122,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
949
1122
  signatureName: sigName,
950
1123
  signatureInput: signatureInputHeader
951
1124
  });
1125
+ failure = invalidSignatureResult(keyId);
952
1126
  continue;
953
1127
  }
954
1128
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -960,12 +1134,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
960
1134
  created: signatureCreated.toString(),
961
1135
  now: now.toString()
962
1136
  });
1137
+ failure = invalidSignatureResult(keyId);
963
1138
  continue;
964
1139
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
965
1140
  logger.debug("Failed to verify; signature created time is too far in the past.", {
966
1141
  created: signatureCreated.toString(),
967
1142
  now: now.toString()
968
1143
  });
1144
+ failure = invalidSignatureResult(keyId);
969
1145
  continue;
970
1146
  }
971
1147
  }
@@ -973,25 +1149,36 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
973
1149
  const contentDigestHeader = request.headers.get("Content-Digest");
974
1150
  if (!contentDigestHeader) {
975
1151
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1152
+ failure = invalidSignatureResult(keyId);
976
1153
  continue;
977
1154
  }
978
1155
  const body = await request.arrayBuffer();
979
1156
  const digestValid = await verifyRfc9421ContentDigest(contentDigestHeader, body);
980
1157
  if (!digestValid) {
981
1158
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1159
+ failure = invalidSignatureResult(keyId);
982
1160
  continue;
983
1161
  }
984
1162
  }
985
1163
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
986
1164
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
987
- const { key, cached } = await fetchKey(new URL(sigInput.keyId), CryptographicKey, {
1165
+ if (keyId == null) {
1166
+ failure = invalidSignatureResult(null);
1167
+ continue;
1168
+ }
1169
+ const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
988
1170
  documentLoader,
989
1171
  contextLoader,
990
1172
  keyCache,
991
1173
  tracerProvider
992
1174
  });
1175
+ if (fetchError != null) {
1176
+ failure = keyFetchErrorResult(keyId, fetchError);
1177
+ continue;
1178
+ }
993
1179
  if (!key) {
994
1180
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1181
+ failure = invalidSignatureResult(keyId);
995
1182
  continue;
996
1183
  }
997
1184
  let alg = sigInput.alg?.toLowerCase();
@@ -1008,6 +1195,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1008
1195
  algorithm: sigInput.alg,
1009
1196
  supported: Object.keys(rfc9421AlgorithmMap)
1010
1197
  });
1198
+ failure = invalidSignatureResult(keyId);
1011
1199
  continue;
1012
1200
  }
1013
1201
  let signatureBase;
@@ -1018,41 +1206,47 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1018
1206
  error,
1019
1207
  signatureInput: sigInput
1020
1208
  });
1209
+ failure = invalidSignatureResult(keyId);
1021
1210
  continue;
1022
1211
  }
1023
1212
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1024
1213
  span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
1025
1214
  try {
1026
1215
  const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
1027
- if (verified) {
1028
- validKey = key;
1029
- break;
1030
- } else if (cached) {
1216
+ if (verified) return {
1217
+ verified: true,
1218
+ key
1219
+ };
1220
+ else if (cached) {
1031
1221
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1032
- return await verifyRequest(originalRequest, {
1222
+ return await verifyRequestDetailed(originalRequest, {
1033
1223
  documentLoader,
1034
1224
  contextLoader,
1035
1225
  timeWindow,
1036
1226
  currentTime,
1037
1227
  keyCache: {
1038
1228
  get: () => Promise.resolve(void 0),
1039
- set: async (keyId, key$1) => await keyCache?.set(keyId, key$1)
1229
+ set: async (keyId$1, key$1) => await keyCache?.set(keyId$1, key$1)
1040
1230
  },
1041
1231
  spec: "rfc9421"
1042
1232
  });
1043
- } else logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1044
- keyId: sigInput.keyId,
1045
- signatureBase
1046
- });
1233
+ } else {
1234
+ logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1235
+ keyId: sigInput.keyId,
1236
+ signatureBase
1237
+ });
1238
+ failure = invalidSignatureResult(keyId);
1239
+ }
1047
1240
  } catch (error) {
1048
1241
  logger.debug("Error during signature verification: {error}", {
1049
1242
  error,
1050
1243
  keyId: sigInput.keyId,
1051
1244
  algorithm: sigInput.alg
1052
1245
  });
1246
+ failure = invalidSignatureResult(keyId);
1053
1247
  }
1054
1248
  }
1055
- return validKey;
1249
+ return failure;
1056
1250
  }
1057
1251
  /**
1058
1252
  * Helper function to create a new Request for redirect handling.
@@ -1170,4 +1364,4 @@ function timingSafeEqual(a, b) {
1170
1364
  }
1171
1365
 
1172
1366
  //#endregion
1173
- export { deno_default, doubleKnock, exportJwk, fetchKey, generateCryptoKeyPair, importJwk, signRequest, validateCryptoKey, verifyRequest };
1367
+ export { deno_default, doubleKnock, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, validateCryptoKey, verifyRequest, verifyRequestDetailed };