@fedify/fedify 2.4.0-dev.2169 → 2.4.0-dev.2190

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 (103) hide show
  1. package/dist/{builder-B2d_Pt0j.mjs → builder-CFF0YNTp.mjs} +3 -3
  2. package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D28gMFqP.mjs → compound-proof-F2jjkrt0.mjs} +3 -3
  7. package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
  8. package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
  9. package/dist/{deno-DU8dvnWk.mjs → deno-EopndaNb.mjs} +1 -1
  10. package/dist/{docloader-CghLH7Zi.mjs → docloader-u6zzOExF.mjs} +25 -17
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/circuit-breaker.test.mjs +50 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  15. package/dist/federation/handler.test.mjs +5 -5
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/keycache.test.mjs +1 -1
  19. package/dist/federation/metrics.test.mjs +1 -1
  20. package/dist/federation/middleware.test.mjs +137 -13
  21. package/dist/federation/mod.cjs +1 -1
  22. package/dist/federation/mod.d.cts +2 -2
  23. package/dist/federation/mod.d.ts +2 -2
  24. package/dist/federation/mod.js +1 -1
  25. package/dist/federation/outgoing-proof.test.mjs +5 -5
  26. package/dist/federation/portable-actor.test.mjs +3 -3
  27. package/dist/federation/portable-chain.test.mjs +3 -3
  28. package/dist/federation/portable-collection.test.mjs +4 -4
  29. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  30. package/dist/federation/portable-inbox.test.mjs +35 -5
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  33. package/dist/federation/portable-route-activity.test.mjs +259 -0
  34. package/dist/federation/portable.test.mjs +3 -3
  35. package/dist/federation/send.test.mjs +3 -3
  36. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  37. package/dist/federation/tasks/tasks.test.mjs +2 -2
  38. package/dist/federation/temporal.test.mjs +1 -1
  39. package/dist/federation/webfinger.test.mjs +1 -1
  40. package/dist/{federation-DrNgkLfJ.cjs → federation-BSS2dUO7.cjs} +38 -21
  41. package/dist/{http-BGpexPO7.js → http-BqN5kyUf.js} +219 -87
  42. package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
  43. package/dist/{http-5CJBdU8n.cjs → http-CCr2_ipG.cjs} +236 -86
  44. package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
  45. package/dist/{http-Bd6rZlJX.mjs → http-Y1ObT1A8.mjs} +180 -92
  46. package/dist/{key-gIhVZO3p.mjs → key-DyXf88bq.mjs} +4 -4
  47. package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
  48. package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
  49. package/dist/{kv-cache-iR55fqlk.js → kv-cache-C02XitwK.js} +24 -16
  50. package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-C8YjNAi_.mjs} +1 -1
  51. package/dist/{kv-cache-YRi_g8GB.cjs → kv-cache-DUXluAc0.cjs} +23 -15
  52. package/dist/{ld-BsTkRrwF.mjs → ld-oQj6j_hP.mjs} +3 -3
  53. package/dist/{metrics-jUDFfVgi.mjs → metrics-zMe834ID.mjs} +1 -1
  54. package/dist/{middleware-Coqgp-K0.mjs → middleware-Bdu2-OPD.mjs} +37 -25
  55. package/dist/{middleware-DlxMxVp5.js → middleware-CaR2MRWY.js} +39 -22
  56. package/dist/{middleware-Dq-OCmb6.mjs → middleware-Ck9yghxs.mjs} +1 -1
  57. package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
  58. package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
  59. package/dist/{mod--jv6vyrP.d.ts → mod-Ck9_lSJB.d.ts} +1 -1
  60. package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
  61. package/dist/{mod-CPPqkyV_.d.cts → mod-DHTuu3l8.d.cts} +1 -1
  62. package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
  63. package/dist/mod.cjs +4 -4
  64. package/dist/mod.d.cts +5 -5
  65. package/dist/mod.d.ts +5 -5
  66. package/dist/mod.js +4 -4
  67. package/dist/nodeinfo/handler.test.mjs +1 -1
  68. package/dist/{owner-D3GoetXS.mjs → owner-Dem4fbwc.mjs} +5 -5
  69. package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-BEqLrMW3.js} +1 -1
  70. package/dist/{portable-collection-C60_acmx.mjs → portable-collection-BL6aZlQ0.mjs} +2 -2
  71. package/dist/{portable-collection-CnlCamup.cjs → portable-collection-DJe_bk50.cjs} +1 -1
  72. package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Duue9Y2Y.mjs} +2 -2
  73. package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
  74. package/dist/{proof-CQAYqWdj.mjs → proof-C9115DX_.mjs} +5 -5
  75. package/dist/{send-DjkQrxt5.mjs → send-D0hxFFRB.mjs} +3 -3
  76. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  77. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  78. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  79. package/dist/sig/compound-proof.test.mjs +2 -2
  80. package/dist/sig/http.test.mjs +495 -23
  81. package/dist/sig/key-owner-evidence.test.mjs +2 -2
  82. package/dist/sig/key.test.mjs +1 -1
  83. package/dist/sig/ld.test.mjs +2 -2
  84. package/dist/sig/mod.cjs +2 -2
  85. package/dist/sig/mod.d.cts +2 -2
  86. package/dist/sig/mod.d.ts +2 -2
  87. package/dist/sig/mod.js +2 -2
  88. package/dist/sig/owner.test.mjs +1 -1
  89. package/dist/sig/portable-collection.test.mjs +1 -1
  90. package/dist/sig/portable-key-id.test.d.mts +2 -0
  91. package/dist/sig/portable-key-id.test.mjs +67 -0
  92. package/dist/sig/portable-key.test.mjs +7 -7
  93. package/dist/sig/proof.test.mjs +3 -3
  94. package/dist/{temporal-oRKP803M.mjs → temporal-DKchpKyp.mjs} +1 -1
  95. package/dist/testing/kv.test.mjs +1 -1
  96. package/dist/testing/mod.d.mts +52 -0
  97. package/dist/utils/docloader.test.mjs +93 -2
  98. package/dist/utils/kv-cache.test.mjs +1 -1
  99. package/dist/utils/mod.cjs +1 -1
  100. package/dist/utils/mod.d.cts +1 -1
  101. package/dist/utils/mod.d.ts +1 -1
  102. package/dist/utils/mod.js +1 -1
  103. package/package.json +7 -7
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
16
16
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
17
  //#region deno.json
18
18
  var name = "@fedify/fedify";
19
- var version = "2.4.0-dev.2169+0a45df07";
19
+ var version = "2.4.0-dev.2190+be3a8be0";
20
20
  //#endregion
21
21
  //#region src/sig/portable-key-id.ts
22
22
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -117,6 +117,50 @@ function getCanonicalPortableId(id) {
117
117
  }
118
118
  }
119
119
  /**
120
+ * Gets the [FEP-fe34] origin of an ID for same-origin checks. The origin of
121
+ * an `ap:` or `ap+ef61:` URI, or of a compatible identifier on any gateway,
122
+ * is the DID of its canonical portable ID, as FEP-ef61 treats a compatible
123
+ * identifier as the portable ID it stands for; unlike `getFe34Origin()`,
124
+ * which gives a compatible identifier the web origin of its gateway. The
125
+ * origin of any other HTTP(S) URL is its web origin, and that of a DID URL
126
+ * is its DID.
127
+ *
128
+ * Only the parsed `URL` is validated, so dot segments that URL parsing has
129
+ * already resolved cannot be detected here.
130
+ *
131
+ * [FEP-fe34]: https://w3id.org/fep/fe34
132
+ * @returns The origin, or `null` if the ID has none that is supported, or is
133
+ * a malformed portable ID or compatible identifier.
134
+ * @internal
135
+ */
136
+ function getAuthenticationOrigin(id) {
137
+ try {
138
+ if (isPortableId(id)) {
139
+ const canonical = getCanonicalPortableId(id);
140
+ return canonical == null ? null : (0, _fedify_vocab_runtime.getFe34Origin)(canonical);
141
+ }
142
+ return (0, _fedify_vocab_runtime.getFe34Origin)(id);
143
+ } catch (error) {
144
+ if (error instanceof TypeError) return null;
145
+ throw error;
146
+ }
147
+ }
148
+ /**
149
+ * Checks whether two IDs identify the same object. Two portable IDs, i.e.,
150
+ * `ap:` or `ap+ef61:` URIs or compatible identifiers, are compared by their
151
+ * canonical portable IDs, so that the same object on different gateways and
152
+ * with different location hints matches. Other IDs have to be equal as
153
+ * they are.
154
+ * @internal
155
+ */
156
+ function isSameObjectId(a, b) {
157
+ const aPortable = isPortableId(a);
158
+ if (aPortable !== isPortableId(b)) return false;
159
+ if (!aPortable) return a.href === b.href;
160
+ const canonical = getCanonicalPortableId(a);
161
+ return canonical != null && canonical === getCanonicalPortableId(b);
162
+ }
163
+ /**
120
164
  * Gets the canonical portable ID of an `ap:` or `ap+ef61:` URI or
121
165
  * a compatible identifier as it is written, before any URL parsing.
122
166
  * Parsing an ID into a `URL` resolves dot segments in its path, which would
@@ -8626,16 +8670,19 @@ function parseDraftSignature(signature) {
8626
8670
  * Lists the HTTP Signatures that a request claims to carry, without verifying
8627
8671
  * any of them. Signatures whose key IDs are not URLs are left out.
8628
8672
  * @param request The request.
8673
+ * @param maxSignatures The maximum number of RFC 9421 signatures to consider,
8674
+ * counted the same way as by {@link verifyRequest}, so
8675
+ * that the signatures it ignores are not listed either.
8629
8676
  * @returns The signatures; RFC 9421 ones if the request has
8630
8677
  * a `Signature-Input` header, or a draft-cavage one otherwise.
8631
8678
  * @internal
8632
8679
  */
8633
- function listRequestSignatures(request) {
8680
+ function listRequestSignatures(request, maxSignatures = 3) {
8634
8681
  const signatureInput = request.headers.get("Signature-Input");
8635
8682
  if (signatureInput != null) {
8636
8683
  const signatures = [];
8637
- const inputs = parseRfc9421SignatureInput(signatureInput);
8638
- for (const [label, input] of Object.entries(inputs)) {
8684
+ const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
8685
+ for (const [label, input] of inputs.slice(0, maxSignatures)) {
8639
8686
  const keyId = parseKeyId(input.keyId);
8640
8687
  if (keyId == null) continue;
8641
8688
  signatures.push({
@@ -8763,6 +8810,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
8763
8810
  body
8764
8811
  });
8765
8812
  }
8813
+ /**
8814
+ * Checks that a maximum number of signatures to verify is a positive safe
8815
+ * integer or `Infinity`.
8816
+ * @param value The value to check.
8817
+ * @param name The name of the option, used in the error message.
8818
+ * @throws {RangeError} Thrown if the value is not valid.
8819
+ * @internal
8820
+ */
8821
+ function validateMaxSignatures(value, name) {
8822
+ if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
8823
+ throw new RangeError(`${name} must be a positive integer or Infinity.`);
8824
+ }
8766
8825
  const supportedHashAlgorithms = {
8767
8826
  "sha": "SHA-1",
8768
8827
  "sha-256": "SHA-256",
@@ -8855,6 +8914,8 @@ function recordVerificationResult(span, result) {
8855
8914
  * @param options Options for verifying the request.
8856
8915
  * @returns The public key of the verified signature, or `null` if the signature
8857
8916
  * could not be verified.
8917
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
8918
+ * is not a positive integer or `Infinity`.
8858
8919
  */
8859
8920
  async function verifyRequest(request, options = {}) {
8860
8921
  const result = await verifyRequestDetailed(request, options);
@@ -8867,9 +8928,12 @@ async function verifyRequest(request, options = {}) {
8867
8928
  * @param request The request to verify.
8868
8929
  * @param options Options for verifying the request.
8869
8930
  * @returns The verified public key, or a structured verification failure.
8931
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
8932
+ * is not a positive integer or `Infinity`.
8870
8933
  * @since 2.1.0
8871
8934
  */
8872
8935
  async function verifyRequestDetailed(request, options = {}) {
8936
+ if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
8873
8937
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
8874
8938
  if (span.isRecording()) {
8875
8939
  span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
@@ -9178,7 +9242,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
9178
9242
  }
9179
9243
  return false;
9180
9244
  }
9181
- async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
9245
+ /**
9246
+ * Verifies one RFC 9421 signature of a request with a key.
9247
+ * @param request The request, whose body may be consumed already.
9248
+ * @param sigInput The parsed `Signature-Input` member of the signature.
9249
+ * @param sigBytes The bytes of the signature.
9250
+ * @param key The public key named by the signature.
9251
+ * @param span The span to record the algorithm and the signature on.
9252
+ * @returns Whether the signature is verified, and the algorithm of the
9253
+ * signature if it is supported.
9254
+ */
9255
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
9256
+ const logger = (0, _logtape_logtape.getLogger)([
9257
+ "fedify",
9258
+ "sig",
9259
+ "http"
9260
+ ]);
9261
+ let alg = sigInput.alg?.toLowerCase();
9262
+ if (alg == null) {
9263
+ if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
9264
+ else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
9265
+ else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
9266
+ else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
9267
+ }
9268
+ if (alg) span?.setAttribute("http_signatures.algorithm", alg);
9269
+ const algorithm = alg && rfc9421AlgorithmMap[alg];
9270
+ if (!algorithm) {
9271
+ logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
9272
+ algorithm: sigInput.alg,
9273
+ supported: Object.keys(rfc9421AlgorithmMap)
9274
+ });
9275
+ return {
9276
+ verified: false,
9277
+ mismatched: false
9278
+ };
9279
+ }
9280
+ const metricAlgorithm = alg;
9281
+ let signatureBase;
9282
+ try {
9283
+ signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
9284
+ } catch (error) {
9285
+ logger.debug("Failed to create signature base for verification: {error}", {
9286
+ error,
9287
+ signatureInput: sigInput
9288
+ });
9289
+ return {
9290
+ verified: false,
9291
+ algorithm: metricAlgorithm,
9292
+ mismatched: false
9293
+ };
9294
+ }
9295
+ const signatureBaseBytes = new TextEncoder().encode(signatureBase);
9296
+ span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
9297
+ const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
9298
+ if (key.publicKey.algorithm.name !== algorithmName) {
9299
+ logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
9300
+ keyId: sigInput.keyId,
9301
+ algorithm: alg
9302
+ });
9303
+ return {
9304
+ verified: false,
9305
+ algorithm: metricAlgorithm,
9306
+ mismatched: true
9307
+ };
9308
+ }
9309
+ let verified;
9310
+ try {
9311
+ verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
9312
+ } catch (error) {
9313
+ logger.debug("Error during signature verification: {error}", {
9314
+ error,
9315
+ keyId: sigInput.keyId,
9316
+ algorithm: sigInput.alg
9317
+ });
9318
+ return {
9319
+ verified: false,
9320
+ algorithm: metricAlgorithm,
9321
+ mismatched: false
9322
+ };
9323
+ }
9324
+ if (verified) return {
9325
+ verified: true,
9326
+ algorithm: metricAlgorithm
9327
+ };
9328
+ logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
9329
+ keyId: sigInput.keyId,
9330
+ signatureBase
9331
+ });
9332
+ return {
9333
+ verified: false,
9334
+ algorithm: metricAlgorithm,
9335
+ mismatched: true
9336
+ };
9337
+ }
9338
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
9182
9339
  const logger = (0, _logtape_logtape.getLogger)([
9183
9340
  "fedify",
9184
9341
  "sig",
@@ -9191,7 +9348,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9191
9348
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
9192
9349
  return noSignatureResult();
9193
9350
  }
9194
- const originalRequest = request;
9195
9351
  request = request.clone();
9196
9352
  const signatureInputHeader = request.headers.get("Signature-Input");
9197
9353
  if (!signatureInputHeader) {
@@ -9211,6 +9367,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9211
9367
  logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
9212
9368
  return invalidSignatureResult(null);
9213
9369
  }
9370
+ if (signatureNames.length > maxSignatures) {
9371
+ logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
9372
+ maxSignatures,
9373
+ count: signatureNames.length
9374
+ });
9375
+ signatureNames.splice(maxSignatures);
9376
+ }
9377
+ const keyLookups = /* @__PURE__ */ new Map();
9378
+ const refreshedKeyIds = /* @__PURE__ */ new Set();
9379
+ const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
9380
+ documentLoader,
9381
+ contextLoader,
9382
+ keyCache,
9383
+ tracerProvider,
9384
+ meterProvider,
9385
+ portableKeyResolvers: createPortableKeyResolvers({
9386
+ documentLoader,
9387
+ contextLoader,
9388
+ keyCache,
9389
+ tracerProvider,
9390
+ meterProvider
9391
+ })
9392
+ }));
9214
9393
  let failure = noSignatureResult();
9215
9394
  let failureAlgorithm;
9216
9395
  const setFailure = (result, algorithm) => {
@@ -9284,94 +9463,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9284
9463
  setFailure(invalidSignatureResult(null));
9285
9464
  continue;
9286
9465
  }
9287
- const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
9288
- documentLoader,
9289
- contextLoader,
9290
- keyCache,
9291
- tracerProvider,
9292
- meterProvider,
9293
- portableKeyResolvers: createPortableKeyResolvers({
9294
- documentLoader,
9295
- contextLoader,
9296
- keyCache,
9297
- tracerProvider,
9298
- meterProvider
9299
- })
9300
- }));
9301
- if (fetchError != null) {
9302
- setFailure(keyFetchErrorResult(keyId, fetchError));
9303
- continue;
9304
- }
9305
- if (!key) {
9306
- logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9307
- setFailure(invalidSignatureResult(keyId));
9308
- continue;
9309
- }
9310
- let alg = sigInput.alg?.toLowerCase();
9311
- if (alg == null) {
9312
- if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
9313
- else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
9314
- else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
9315
- else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
9316
- }
9317
- if (alg) span?.setAttribute("http_signatures.algorithm", alg);
9318
- const algorithm = alg && rfc9421AlgorithmMap[alg];
9319
- const candidateAlgorithm = algorithm ? alg : void 0;
9320
- if (!algorithm) {
9321
- logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
9322
- algorithm: sigInput.alg,
9323
- supported: Object.keys(rfc9421AlgorithmMap)
9324
- });
9325
- setFailure(invalidSignatureResult(keyId));
9326
- continue;
9327
- }
9328
- let signatureBase;
9329
- try {
9330
- signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
9331
- } catch (error) {
9332
- logger.debug("Failed to create signature base for verification: {error}", {
9333
- error,
9334
- signatureInput: sigInput
9335
- });
9336
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
9337
- continue;
9466
+ let lookup = keyLookups.get(keyId.href);
9467
+ if (lookup == null) {
9468
+ lookup = await lookUpKey(keyId, keyCache);
9469
+ keyLookups.set(keyId.href, lookup);
9338
9470
  }
9339
- const signatureBaseBytes = new TextEncoder().encode(signatureBase);
9340
- span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
9341
- try {
9342
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
9343
- metricsContext.algorithm = candidateAlgorithm;
9471
+ while (true) {
9472
+ const { key, cached, fetchError } = lookup;
9473
+ if (fetchError != null) {
9474
+ setFailure(keyFetchErrorResult(keyId, fetchError));
9475
+ break;
9476
+ }
9477
+ if (!key) {
9478
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9479
+ setFailure(invalidSignatureResult(keyId));
9480
+ break;
9481
+ }
9482
+ const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
9483
+ if (result.verified) {
9484
+ metricsContext.algorithm = result.algorithm;
9344
9485
  return {
9345
9486
  verified: true,
9346
9487
  key,
9347
9488
  signatureLabel: sigName
9348
9489
  };
9349
- } else if (cached) {
9490
+ }
9491
+ if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
9350
9492
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
9351
- return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
9352
- documentLoader,
9353
- contextLoader,
9354
- timeWindow,
9355
- currentTime,
9356
- keyCache: bypassKeyCacheReads(keyCache),
9357
- spec: "rfc9421",
9358
- meterProvider,
9359
- tracerProvider
9360
- });
9361
- } else {
9362
- logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
9363
- keyId: sigInput.keyId,
9364
- signatureBase
9365
- });
9366
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
9493
+ refreshedKeyIds.add(keyId.href);
9494
+ lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
9495
+ keyLookups.set(keyId.href, lookup);
9496
+ continue;
9367
9497
  }
9368
- } catch (error) {
9369
- logger.debug("Error during signature verification: {error}", {
9370
- error,
9371
- keyId: sigInput.keyId,
9372
- algorithm: sigInput.alg
9373
- });
9374
- setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
9498
+ setFailure(invalidSignatureResult(keyId), result.algorithm);
9499
+ break;
9375
9500
  }
9376
9501
  }
9377
9502
  metricsContext.algorithm = failureAlgorithm;
@@ -9471,16 +9596,19 @@ async function doubleKnock(request, identity, options = {}) {
9471
9596
  }
9472
9597
  async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
9473
9598
  const { specDeterminer, log, tracerProvider, signal } = options;
9599
+ signal?.throwIfAborted();
9474
9600
  const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
9475
9601
  visited.add(request.url);
9476
9602
  const origin = new URL(request.url).origin;
9477
9603
  const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
9604
+ signal?.throwIfAborted();
9478
9605
  const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
9479
9606
  let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
9480
9607
  spec: firstTrySpec,
9481
9608
  tracerProvider,
9482
9609
  body
9483
9610
  });
9611
+ signal?.throwIfAborted();
9484
9612
  log?.(signedRequest);
9485
9613
  let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
9486
9614
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
@@ -9505,6 +9633,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9505
9633
  let fulfilled = false;
9506
9634
  let challengeRequest;
9507
9635
  for (const entry of entries) {
9636
+ signal?.throwIfAborted();
9508
9637
  const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
9509
9638
  if (rfc9421 == null) continue;
9510
9639
  logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
@@ -9527,6 +9656,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9527
9656
  }
9528
9657
  }
9529
9658
  if (fulfilled && challengeRequest != null) {
9659
+ signal?.throwIfAborted();
9530
9660
  signedRequest = challengeRequest;
9531
9661
  log?.(signedRequest);
9532
9662
  response = await fetch(signedRequest, {
@@ -9551,11 +9681,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9551
9681
  status: response.status,
9552
9682
  statusText: response.statusText
9553
9683
  });
9684
+ signal?.throwIfAborted();
9554
9685
  signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
9555
9686
  spec,
9556
9687
  tracerProvider,
9557
9688
  body
9558
9689
  });
9690
+ signal?.throwIfAborted();
9559
9691
  log?.(signedRequest);
9560
9692
  response = await fetchDoubleKnockRequest(request, signedRequest, signal);
9561
9693
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
@@ -9718,6 +9850,12 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
9718
9850
  return generateCryptoKeyPair;
9719
9851
  }
9720
9852
  });
9853
+ Object.defineProperty(exports, "getAuthenticationOrigin", {
9854
+ enumerable: true,
9855
+ get: function() {
9856
+ return getAuthenticationOrigin;
9857
+ }
9858
+ });
9721
9859
  Object.defineProperty(exports, "getCanonicalPortableId", {
9722
9860
  enumerable: true,
9723
9861
  get: function() {
@@ -9850,6 +9988,12 @@ Object.defineProperty(exports, "isPortableUri", {
9850
9988
  return isPortableUri;
9851
9989
  }
9852
9990
  });
9991
+ Object.defineProperty(exports, "isSameObjectId", {
9992
+ enumerable: true,
9993
+ get: function() {
9994
+ return isSameObjectId;
9995
+ }
9996
+ });
9853
9997
  Object.defineProperty(exports, "isSamePublicKey", {
9854
9998
  enumerable: true,
9855
9999
  get: function() {
@@ -10012,6 +10156,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
10012
10156
  return validateCryptoKey;
10013
10157
  }
10014
10158
  });
10159
+ Object.defineProperty(exports, "validateMaxSignatures", {
10160
+ enumerable: true,
10161
+ get: function() {
10162
+ return validateMaxSignatures;
10163
+ }
10164
+ });
10015
10165
  Object.defineProperty(exports, "verifyCompactJsonLd", {
10016
10166
  enumerable: true,
10017
10167
  get: function() {
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
3
- import { DocumentLoader } from "@fedify/vocab-runtime";
4
3
  import { MeterProvider, TracerProvider } from "@opentelemetry/api";
4
+ import { DocumentLoader } from "@fedify/vocab-runtime";
5
5
 
6
6
  //#region src/sig/portable-key.d.ts
7
7
  /**
@@ -601,6 +601,30 @@ interface VerifyRequestOptions {
601
601
  */
602
602
  spec?: HttpMessageSignaturesSpec;
603
603
  /**
604
+ * The maximum number of [RFC 9421] signatures of a request to verify.
605
+ * A request can carry several signatures, each of which may make Fedify
606
+ * fetch the key that it names, so only the first ones in the order of
607
+ * the `Signature-Input` header are verified, and the rest are ignored as
608
+ * if they were absent. Every signature among the first ones counts, even
609
+ * if it fails before its key is fetched, e.g., because it is too old, or
610
+ * names the same key as another one. The key of a key ID is looked up
611
+ * only once for all the signatures that name it.
612
+ *
613
+ * It has to be a positive integer, or `Infinity` to verify every
614
+ * signature, which lets a single request make Fedify fetch any number of
615
+ * keys. Draft-cavage HTTP Signatures carry a single signature, so this
616
+ * option does not affect them.
617
+ *
618
+ * Three by default.
619
+ *
620
+ * [RFC 9421]: https://www.rfc-editor.org/rfc/rfc9421
621
+ * @throws {RangeError} Thrown by {@link verifyRequest} and
622
+ * {@link verifyRequestDetailed} if the value is not a positive
623
+ * integer or `Infinity`.
624
+ * @since 2.4.0
625
+ */
626
+ maxSignatures?: number;
627
+ /**
604
628
  * The OpenTelemetry tracer provider. If omitted, the global tracer provider
605
629
  * is used.
606
630
  * @since 1.3.0
@@ -651,6 +675,8 @@ type VerifyRequestDetailedResult = {
651
675
  * @param options Options for verifying the request.
652
676
  * @returns The public key of the verified signature, or `null` if the signature
653
677
  * could not be verified.
678
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
679
+ * is not a positive integer or `Infinity`.
654
680
  */
655
681
  declare function verifyRequest(request: Request, options?: VerifyRequestOptions): Promise<CryptographicKey | null>;
656
682
  /**
@@ -660,6 +686,8 @@ declare function verifyRequest(request: Request, options?: VerifyRequestOptions)
660
686
  * @param request The request to verify.
661
687
  * @param options Options for verifying the request.
662
688
  * @returns The verified public key, or a structured verification failure.
689
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
690
+ * is not a positive integer or `Infinity`.
663
691
  * @since 2.1.0
664
692
  */
665
693
  declare function verifyRequestDetailed(request: Request, options?: VerifyRequestOptions): Promise<VerifyRequestDetailedResult>;