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

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-0onlMY4u.mjs} +26 -6
  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-B-Fk6Slo.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-DB_Sh0LK.mjs} +1 -1
  10. package/dist/{docloader-CghLH7Zi.mjs → docloader-DpWYYY6a.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 +52 -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 +182 -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 +49 -5
  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-BsGo0EH2.cjs} +76 -35
  41. package/dist/{http-5CJBdU8n.cjs → http-BEivI0TS.cjs} +279 -87
  42. package/dist/{http-BGpexPO7.js → http-BGmxtpo2.js} +263 -89
  43. package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
  44. package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
  45. package/dist/{http-Bd6rZlJX.mjs → http-DQf9Mw4V.mjs} +180 -92
  46. package/dist/{key-gIhVZO3p.mjs → key-DA3Yx8p2.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-YRi_g8GB.cjs → kv-cache-1hafSoqH.cjs} +23 -15
  50. package/dist/{kv-cache-iR55fqlk.js → kv-cache-DduYhpee.js} +24 -16
  51. package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-OFBvrKh3.mjs} +1 -1
  52. package/dist/{ld-BsTkRrwF.mjs → ld-C15yzuY6.mjs} +3 -3
  53. package/dist/{metrics-jUDFfVgi.mjs → metrics-B1H23mlq.mjs} +1 -1
  54. package/dist/{middleware-Dq-OCmb6.mjs → middleware-BZQnf0nI.mjs} +1 -1
  55. package/dist/{middleware-Coqgp-K0.mjs → middleware-BqqycC0P.mjs} +52 -36
  56. package/dist/{middleware-DlxMxVp5.js → middleware-ClaUFaLb.js} +77 -36
  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-CPPqkyV_.d.cts → mod-Cy9VNHVw.d.ts} +14 -3
  60. package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
  61. package/dist/{mod--jv6vyrP.d.ts → mod-DoTWIc7F.d.cts} +14 -3
  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-BVpZBM-V.mjs} +5 -5
  69. package/dist/{portable-collection-CnlCamup.cjs → portable-collection-BFSShzwh.cjs} +1 -1
  70. package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-D535cSz_.js} +1 -1
  71. package/dist/{portable-collection-C60_acmx.mjs → portable-collection-Dn2GdZdI.mjs} +2 -2
  72. package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Bmwmq7Y6.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-DrJWKeOl.mjs} +49 -7
  75. package/dist/{send-DjkQrxt5.mjs → send-CT3a-0Vd.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 +141 -7
  94. package/dist/{temporal-oRKP803M.mjs → temporal-DNqluyEc.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.2193+402149b5";
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
@@ -7429,6 +7473,7 @@ const FEP_2277_COLLECTION_PROPERTIES = [
7429
7473
  "prev",
7430
7474
  "current"
7431
7475
  ].map((property) => AS_NAMESPACE + property);
7476
+ const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
7432
7477
  const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
7433
7478
  const FUNCTIONAL_PROOF_PROPERTIES = [
7434
7479
  `${SECURITY_NAMESPACE}cryptosuite`,
@@ -7455,6 +7500,35 @@ function hasValidPortableProofShape(proofValue) {
7455
7500
  });
7456
7501
  }
7457
7502
  /**
7503
+ * Checks whether an expanded portable actor node has the `gateways` that
7504
+ * FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
7505
+ * an empty path, query, and fragment. Like the generated vocabulary decoder,
7506
+ * this also tolerates a plain set of values instead of a `@list`, and gateway
7507
+ * strings given as `@value`s instead of `@id`s.
7508
+ */
7509
+ function hasValidPortableActorGateways(node) {
7510
+ const values = node[FEP_EF61_GATEWAYS];
7511
+ if (!Array.isArray(values)) return false;
7512
+ let items = values;
7513
+ if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
7514
+ if (values.length !== 1) return false;
7515
+ items = values[0]["@list"];
7516
+ }
7517
+ if (!Array.isArray(items) || items.length < 1) return false;
7518
+ return items.every((item) => {
7519
+ if (!isJsonLdNode(item)) return false;
7520
+ const gateway = typeof item["@id"] === "string" ? item["@id"] : item["@value"];
7521
+ if (typeof gateway !== "string") return false;
7522
+ try {
7523
+ (0, _fedify_vocab_runtime.parseGatewayUrl)(gateway);
7524
+ } catch (error) {
7525
+ if (error instanceof TypeError) return false;
7526
+ throw error;
7527
+ }
7528
+ return true;
7529
+ });
7530
+ }
7531
+ /**
7458
7532
  * Classifies an expanded JSON-LD node into an FEP-2277 core type by the
7459
7533
  * properties it has.
7460
7534
  * @internal
@@ -7658,6 +7732,15 @@ async function preparePortableObjectProof(jsonLd, options) {
7658
7732
  }
7659
7733
  };
7660
7734
  }
7735
+ if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
7736
+ prepared: false,
7737
+ root,
7738
+ objectType,
7739
+ result: {
7740
+ verified: false,
7741
+ reason: { type: "invalidGateways" }
7742
+ }
7743
+ };
7661
7744
  return {
7662
7745
  prepared: true,
7663
7746
  root,
@@ -7711,7 +7794,10 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
7711
7794
  *
7712
7795
  * Every proof must use a DID URL whose DID matches the portable object's
7713
7796
  * authority, i.e., the DID of its canonical portable ID, and every proof must
7714
- * pass {@link verifyProof}. The proofs are verified over the document as
7797
+ * pass {@link verifyProof}. A portable actor must also have a non-empty
7798
+ * `gateways` list whose items are all HTTP(S) URIs with an empty path, query,
7799
+ * and fragment; otherwise, it is rejected with the `invalidGateways` reason
7800
+ * before its proofs are verified. The proofs are verified over the document as
7715
7801
  * given; a compatible identifier is never rewritten into a portable ID.
7716
7802
  *
7717
7803
  * [FEP-ef61]: https://w3id.org/fep/ef61
@@ -8626,16 +8712,19 @@ function parseDraftSignature(signature) {
8626
8712
  * Lists the HTTP Signatures that a request claims to carry, without verifying
8627
8713
  * any of them. Signatures whose key IDs are not URLs are left out.
8628
8714
  * @param request The request.
8715
+ * @param maxSignatures The maximum number of RFC 9421 signatures to consider,
8716
+ * counted the same way as by {@link verifyRequest}, so
8717
+ * that the signatures it ignores are not listed either.
8629
8718
  * @returns The signatures; RFC 9421 ones if the request has
8630
8719
  * a `Signature-Input` header, or a draft-cavage one otherwise.
8631
8720
  * @internal
8632
8721
  */
8633
- function listRequestSignatures(request) {
8722
+ function listRequestSignatures(request, maxSignatures = 3) {
8634
8723
  const signatureInput = request.headers.get("Signature-Input");
8635
8724
  if (signatureInput != null) {
8636
8725
  const signatures = [];
8637
- const inputs = parseRfc9421SignatureInput(signatureInput);
8638
- for (const [label, input] of Object.entries(inputs)) {
8726
+ const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
8727
+ for (const [label, input] of inputs.slice(0, maxSignatures)) {
8639
8728
  const keyId = parseKeyId(input.keyId);
8640
8729
  if (keyId == null) continue;
8641
8730
  signatures.push({
@@ -8763,6 +8852,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
8763
8852
  body
8764
8853
  });
8765
8854
  }
8855
+ /**
8856
+ * Checks that a maximum number of signatures to verify is a positive safe
8857
+ * integer or `Infinity`.
8858
+ * @param value The value to check.
8859
+ * @param name The name of the option, used in the error message.
8860
+ * @throws {RangeError} Thrown if the value is not valid.
8861
+ * @internal
8862
+ */
8863
+ function validateMaxSignatures(value, name) {
8864
+ if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
8865
+ throw new RangeError(`${name} must be a positive integer or Infinity.`);
8866
+ }
8766
8867
  const supportedHashAlgorithms = {
8767
8868
  "sha": "SHA-1",
8768
8869
  "sha-256": "SHA-256",
@@ -8855,6 +8956,8 @@ function recordVerificationResult(span, result) {
8855
8956
  * @param options Options for verifying the request.
8856
8957
  * @returns The public key of the verified signature, or `null` if the signature
8857
8958
  * could not be verified.
8959
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
8960
+ * is not a positive integer or `Infinity`.
8858
8961
  */
8859
8962
  async function verifyRequest(request, options = {}) {
8860
8963
  const result = await verifyRequestDetailed(request, options);
@@ -8867,9 +8970,12 @@ async function verifyRequest(request, options = {}) {
8867
8970
  * @param request The request to verify.
8868
8971
  * @param options Options for verifying the request.
8869
8972
  * @returns The verified public key, or a structured verification failure.
8973
+ * @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
8974
+ * is not a positive integer or `Infinity`.
8870
8975
  * @since 2.1.0
8871
8976
  */
8872
8977
  async function verifyRequestDetailed(request, options = {}) {
8978
+ if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
8873
8979
  return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
8874
8980
  if (span.isRecording()) {
8875
8981
  span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
@@ -9178,7 +9284,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
9178
9284
  }
9179
9285
  return false;
9180
9286
  }
9181
- async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
9287
+ /**
9288
+ * Verifies one RFC 9421 signature of a request with a key.
9289
+ * @param request The request, whose body may be consumed already.
9290
+ * @param sigInput The parsed `Signature-Input` member of the signature.
9291
+ * @param sigBytes The bytes of the signature.
9292
+ * @param key The public key named by the signature.
9293
+ * @param span The span to record the algorithm and the signature on.
9294
+ * @returns Whether the signature is verified, and the algorithm of the
9295
+ * signature if it is supported.
9296
+ */
9297
+ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
9298
+ const logger = (0, _logtape_logtape.getLogger)([
9299
+ "fedify",
9300
+ "sig",
9301
+ "http"
9302
+ ]);
9303
+ let alg = sigInput.alg?.toLowerCase();
9304
+ if (alg == null) {
9305
+ 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";
9306
+ else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
9307
+ 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";
9308
+ else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
9309
+ }
9310
+ if (alg) span?.setAttribute("http_signatures.algorithm", alg);
9311
+ const algorithm = alg && rfc9421AlgorithmMap[alg];
9312
+ if (!algorithm) {
9313
+ logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
9314
+ algorithm: sigInput.alg,
9315
+ supported: Object.keys(rfc9421AlgorithmMap)
9316
+ });
9317
+ return {
9318
+ verified: false,
9319
+ mismatched: false
9320
+ };
9321
+ }
9322
+ const metricAlgorithm = alg;
9323
+ let signatureBase;
9324
+ try {
9325
+ signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
9326
+ } catch (error) {
9327
+ logger.debug("Failed to create signature base for verification: {error}", {
9328
+ error,
9329
+ signatureInput: sigInput
9330
+ });
9331
+ return {
9332
+ verified: false,
9333
+ algorithm: metricAlgorithm,
9334
+ mismatched: false
9335
+ };
9336
+ }
9337
+ const signatureBaseBytes = new TextEncoder().encode(signatureBase);
9338
+ span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
9339
+ const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
9340
+ if (key.publicKey.algorithm.name !== algorithmName) {
9341
+ logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
9342
+ keyId: sigInput.keyId,
9343
+ algorithm: alg
9344
+ });
9345
+ return {
9346
+ verified: false,
9347
+ algorithm: metricAlgorithm,
9348
+ mismatched: true
9349
+ };
9350
+ }
9351
+ let verified;
9352
+ try {
9353
+ verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
9354
+ } catch (error) {
9355
+ logger.debug("Error during signature verification: {error}", {
9356
+ error,
9357
+ keyId: sigInput.keyId,
9358
+ algorithm: sigInput.alg
9359
+ });
9360
+ return {
9361
+ verified: false,
9362
+ algorithm: metricAlgorithm,
9363
+ mismatched: false
9364
+ };
9365
+ }
9366
+ if (verified) return {
9367
+ verified: true,
9368
+ algorithm: metricAlgorithm
9369
+ };
9370
+ logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
9371
+ keyId: sigInput.keyId,
9372
+ signatureBase
9373
+ });
9374
+ return {
9375
+ verified: false,
9376
+ algorithm: metricAlgorithm,
9377
+ mismatched: true
9378
+ };
9379
+ }
9380
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
9182
9381
  const logger = (0, _logtape_logtape.getLogger)([
9183
9382
  "fedify",
9184
9383
  "sig",
@@ -9191,7 +9390,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9191
9390
  logger.error("Failed to verify; the request body is locked.", { url: request.url });
9192
9391
  return noSignatureResult();
9193
9392
  }
9194
- const originalRequest = request;
9195
9393
  request = request.clone();
9196
9394
  const signatureInputHeader = request.headers.get("Signature-Input");
9197
9395
  if (!signatureInputHeader) {
@@ -9211,6 +9409,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9211
9409
  logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
9212
9410
  return invalidSignatureResult(null);
9213
9411
  }
9412
+ if (signatureNames.length > maxSignatures) {
9413
+ logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
9414
+ maxSignatures,
9415
+ count: signatureNames.length
9416
+ });
9417
+ signatureNames.splice(maxSignatures);
9418
+ }
9419
+ const keyLookups = /* @__PURE__ */ new Map();
9420
+ const refreshedKeyIds = /* @__PURE__ */ new Set();
9421
+ const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
9422
+ documentLoader,
9423
+ contextLoader,
9424
+ keyCache,
9425
+ tracerProvider,
9426
+ meterProvider,
9427
+ portableKeyResolvers: createPortableKeyResolvers({
9428
+ documentLoader,
9429
+ contextLoader,
9430
+ keyCache,
9431
+ tracerProvider,
9432
+ meterProvider
9433
+ })
9434
+ }));
9214
9435
  let failure = noSignatureResult();
9215
9436
  let failureAlgorithm;
9216
9437
  const setFailure = (result, algorithm) => {
@@ -9284,94 +9505,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
9284
9505
  setFailure(invalidSignatureResult(null));
9285
9506
  continue;
9286
9507
  }
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";
9508
+ let lookup = keyLookups.get(keyId.href);
9509
+ if (lookup == null) {
9510
+ lookup = await lookUpKey(keyId, keyCache);
9511
+ keyLookups.set(keyId.href, lookup);
9316
9512
  }
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;
9338
- }
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;
9513
+ while (true) {
9514
+ const { key, cached, fetchError } = lookup;
9515
+ if (fetchError != null) {
9516
+ setFailure(keyFetchErrorResult(keyId, fetchError));
9517
+ break;
9518
+ }
9519
+ if (!key) {
9520
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
9521
+ setFailure(invalidSignatureResult(keyId));
9522
+ break;
9523
+ }
9524
+ const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
9525
+ if (result.verified) {
9526
+ metricsContext.algorithm = result.algorithm;
9344
9527
  return {
9345
9528
  verified: true,
9346
9529
  key,
9347
9530
  signatureLabel: sigName
9348
9531
  };
9349
- } else if (cached) {
9532
+ }
9533
+ if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
9350
9534
  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);
9535
+ refreshedKeyIds.add(keyId.href);
9536
+ lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
9537
+ keyLookups.set(keyId.href, lookup);
9538
+ continue;
9367
9539
  }
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);
9540
+ setFailure(invalidSignatureResult(keyId), result.algorithm);
9541
+ break;
9375
9542
  }
9376
9543
  }
9377
9544
  metricsContext.algorithm = failureAlgorithm;
@@ -9471,16 +9638,19 @@ async function doubleKnock(request, identity, options = {}) {
9471
9638
  }
9472
9639
  async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
9473
9640
  const { specDeterminer, log, tracerProvider, signal } = options;
9641
+ signal?.throwIfAborted();
9474
9642
  const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
9475
9643
  visited.add(request.url);
9476
9644
  const origin = new URL(request.url).origin;
9477
9645
  const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
9646
+ signal?.throwIfAborted();
9478
9647
  const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
9479
9648
  let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
9480
9649
  spec: firstTrySpec,
9481
9650
  tracerProvider,
9482
9651
  body
9483
9652
  });
9653
+ signal?.throwIfAborted();
9484
9654
  log?.(signedRequest);
9485
9655
  let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
9486
9656
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
@@ -9505,6 +9675,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9505
9675
  let fulfilled = false;
9506
9676
  let challengeRequest;
9507
9677
  for (const entry of entries) {
9678
+ signal?.throwIfAborted();
9508
9679
  const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
9509
9680
  if (rfc9421 == null) continue;
9510
9681
  logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
@@ -9527,6 +9698,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9527
9698
  }
9528
9699
  }
9529
9700
  if (fulfilled && challengeRequest != null) {
9701
+ signal?.throwIfAborted();
9530
9702
  signedRequest = challengeRequest;
9531
9703
  log?.(signedRequest);
9532
9704
  response = await fetch(signedRequest, {
@@ -9551,11 +9723,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
9551
9723
  status: response.status,
9552
9724
  statusText: response.statusText
9553
9725
  });
9726
+ signal?.throwIfAborted();
9554
9727
  signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
9555
9728
  spec,
9556
9729
  tracerProvider,
9557
9730
  body
9558
9731
  });
9732
+ signal?.throwIfAborted();
9559
9733
  log?.(signedRequest);
9560
9734
  response = await fetchDoubleKnockRequest(request, signedRequest, signal);
9561
9735
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
@@ -9718,6 +9892,12 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
9718
9892
  return generateCryptoKeyPair;
9719
9893
  }
9720
9894
  });
9895
+ Object.defineProperty(exports, "getAuthenticationOrigin", {
9896
+ enumerable: true,
9897
+ get: function() {
9898
+ return getAuthenticationOrigin;
9899
+ }
9900
+ });
9721
9901
  Object.defineProperty(exports, "getCanonicalPortableId", {
9722
9902
  enumerable: true,
9723
9903
  get: function() {
@@ -9850,6 +10030,12 @@ Object.defineProperty(exports, "isPortableUri", {
9850
10030
  return isPortableUri;
9851
10031
  }
9852
10032
  });
10033
+ Object.defineProperty(exports, "isSameObjectId", {
10034
+ enumerable: true,
10035
+ get: function() {
10036
+ return isSameObjectId;
10037
+ }
10038
+ });
9853
10039
  Object.defineProperty(exports, "isSamePublicKey", {
9854
10040
  enumerable: true,
9855
10041
  get: function() {
@@ -10012,6 +10198,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
10012
10198
  return validateCryptoKey;
10013
10199
  }
10014
10200
  });
10201
+ Object.defineProperty(exports, "validateMaxSignatures", {
10202
+ enumerable: true,
10203
+ get: function() {
10204
+ return validateMaxSignatures;
10205
+ }
10206
+ });
10015
10207
  Object.defineProperty(exports, "verifyCompactJsonLd", {
10016
10208
  enumerable: true,
10017
10209
  get: function() {