@fedify/fedify 2.0.28-dev.1974 → 2.0.28-dev.1982

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 (96) hide show
  1. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert-BhQd8QHP.mjs} +19 -1
  2. package/dist/{builder-DBKy92BX.mjs → builder-DG5rS_Zv.mjs} +3 -3
  3. package/dist/{client-HLLGHiTU.mjs → client-glFHmpn4.mjs} +78 -4
  4. package/dist/compat/mod.d.cts +1 -1
  5. package/dist/compat/mod.d.ts +1 -1
  6. package/dist/compat/transformers.test.mjs +3 -4
  7. package/dist/{context-DnsFj3yK.d.ts → context-B21klrfz.d.ts} +4 -3
  8. package/dist/{context-CJZHWLmz.d.cts → context-M_MxoQgC.d.cts} +4 -3
  9. package/dist/{deno-CGtm12Ft.mjs → deno-eqaJj6JL.mjs} +1 -1
  10. package/dist/{docloader-_hXTUC-b.mjs → docloader-BXUqhXNd.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +2 -2
  12. package/dist/federation/collection.test.mjs +1 -1
  13. package/dist/federation/handler.test.mjs +125 -8
  14. package/dist/federation/idempotency.test.mjs +4 -4
  15. package/dist/federation/inbox.test.mjs +1 -1
  16. package/dist/federation/keycache.test.mjs +44 -10
  17. package/dist/federation/kv.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +99 -11
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/mq.test.mjs +1 -1
  24. package/dist/federation/negotiation.test.mjs +2 -2
  25. package/dist/federation/retry.test.mjs +1 -1
  26. package/dist/federation/router.test.mjs +1 -1
  27. package/dist/federation/send.test.mjs +138 -6
  28. package/dist/federation/temporal.test.mjs +2 -2
  29. package/dist/federation/webfinger.test.mjs +2 -2
  30. package/dist/{http-BuaGj55w.cjs → http-BgOlx4n1.cjs} +163 -3
  31. package/dist/{http-C4D0ajx0.d.ts → http-Bs6thuCC.d.cts} +2 -2
  32. package/dist/{http--P33qRwW.mjs → http-Bu3NxfGb.mjs} +2 -2
  33. package/dist/{http-RYvZHWMc.d.cts → http-CDupyfOc.d.ts} +2 -2
  34. package/dist/{http-BP1pVF9m.js → http-CwcG2wDs.js} +152 -4
  35. package/dist/{inbox-BZNyUPzW.mjs → inbox-Hou2ZAjv.mjs} +1 -1
  36. package/dist/{key-rC9mW1U5.mjs → key-bSphUqhB.mjs} +152 -4
  37. package/dist/{keycache-DaQ3ndaJ.mjs → keycache-D239uGT9.mjs} +12 -4
  38. package/dist/{kv-cache-BVqMp1RC.cjs → kv-cache-B8GaLaNy.cjs} +1 -1
  39. package/dist/{kv-cache-CKg5W012.js → kv-cache-lbswb9pg.js} +1 -1
  40. package/dist/{ld-CN2nacLy.mjs → ld-D0uFMLmq.mjs} +2 -2
  41. package/dist/{middleware-DcmlYd2c.cjs → middleware-BKpP3cnZ.cjs} +1 -1
  42. package/dist/{middleware-DpEJSmjy.mjs → middleware-BuVM1K-J.mjs} +30 -19
  43. package/dist/{middleware-sbzAVAzo.mjs → middleware-DXPBIbm6.mjs} +1 -1
  44. package/dist/{middleware-BXwn4gnl.cjs → middleware-D_7wZH27.cjs} +90 -14
  45. package/dist/{middleware-CwIT91Nc.js → middleware-DomxFoLd.js} +90 -14
  46. package/dist/{mod-B2zB0rDg.d.cts → mod-Bt0eg_Lv.d.cts} +1 -1
  47. package/dist/{mod-COgmn-UO.d.ts → mod-D24V_w-K.d.ts} +1 -1
  48. package/dist/{mod-43DJyETz.d.cts → mod-Dp5dfH-i.d.cts} +1 -1
  49. package/dist/{mod-CiQNNAQL.d.ts → mod-E7YM72JZ.d.ts} +1 -1
  50. package/dist/{mod-CB6pc2pw.d.cts → mod-IOWUF5Yr.d.cts} +1 -1
  51. package/dist/{mod-CD1youdZ.d.ts → mod-XucBiDNd.d.ts} +1 -1
  52. package/dist/mod.cjs +5 -5
  53. package/dist/mod.d.cts +6 -6
  54. package/dist/mod.d.ts +6 -6
  55. package/dist/mod.js +5 -5
  56. package/dist/nodeinfo/client.test.mjs +28 -1
  57. package/dist/nodeinfo/handler.test.mjs +2 -2
  58. package/dist/nodeinfo/mod.cjs +1 -1
  59. package/dist/nodeinfo/mod.js +1 -1
  60. package/dist/nodeinfo/types.test.mjs +1 -1
  61. package/dist/otel/exporter.test.mjs +1 -1
  62. package/dist/{owner-DEvZuyOE.d.cts → owner-B3hNQCG8.d.cts} +11 -0
  63. package/dist/owner-Bzg6w7eA.mjs +175 -0
  64. package/dist/{owner-CnngXDNJ.d.ts → owner-CiBPcC6F.d.ts} +11 -0
  65. package/dist/{proof-CpEH24hi.mjs → proof-CgLA51pO.mjs} +2 -2
  66. package/dist/{proof-ftbx50PI.js → proof-DsJHFTkx.js} +112 -58
  67. package/dist/{proof-BTKH7fe9.cjs → proof-xCEe8t7U.cjs} +112 -58
  68. package/dist/{send-pXXMu19o.mjs → send-D89-FDX-.mjs} +64 -6
  69. package/dist/sig/http.test.mjs +4 -4
  70. package/dist/sig/key.test.mjs +169 -2
  71. package/dist/sig/ld.test.mjs +56 -4
  72. package/dist/sig/mod.cjs +2 -2
  73. package/dist/sig/mod.d.cts +3 -3
  74. package/dist/sig/mod.d.ts +3 -3
  75. package/dist/sig/mod.js +2 -2
  76. package/dist/sig/owner.test.mjs +105 -6
  77. package/dist/sig/proof.test.mjs +72 -3
  78. package/dist/{temporal-B4rBDksp.mjs → temporal-DPJlkHh3.mjs} +1 -1
  79. package/dist/{types-m8u9L86X.cjs → types-DWs-qZaY.cjs} +94 -2
  80. package/dist/{types-DTONwNqL.js → types-sXITXcWa.js} +78 -4
  81. package/dist/utils/docloader.test.mjs +28 -4
  82. package/dist/utils/kv-cache.test.mjs +1 -1
  83. package/dist/utils/mod.cjs +1 -1
  84. package/dist/utils/mod.d.cts +1 -1
  85. package/dist/utils/mod.d.ts +1 -1
  86. package/dist/utils/mod.js +1 -1
  87. package/package.json +5 -5
  88. package/dist/assert-ddO5KLpe.mjs +0 -23
  89. package/dist/owner-CFgyZgkz.mjs +0 -120
  90. /package/dist/{collection-ChgDTHLz.mjs → collection-DI6YzYpd.mjs} +0 -0
  91. /package/dist/{keys-CtZLJq76.mjs → keys-CCxRJhWa.mjs} +0 -0
  92. /package/dist/{kv-cache-DqEKe5E0.mjs → kv-cache-BrjuJue8.mjs} +0 -0
  93. /package/dist/{kv-BrZHNugx.mjs → kv-mI4Ix2EU.mjs} +0 -0
  94. /package/dist/{negotiation-BehA2uul.mjs → negotiation-TWEs6Cl8.mjs} +0 -0
  95. /package/dist/{retry-B_E3V_Dx.mjs → retry-Ddbq3AcK.mjs} +0 -0
  96. /package/dist/{types-C37hquWI.mjs → types-MNlFfxjv.mjs} +0 -0
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { CryptographicKey, Multikey } from "@fedify/vocab";
3
- import { DocumentLoader } from "@fedify/vocab-runtime";
2
+ import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
4
3
  import { TracerProvider } from "@opentelemetry/api";
4
+ import { DocumentLoader } from "@fedify/vocab-runtime";
5
5
 
6
6
  //#region src/sig/key.d.ts
7
7
  /**
@@ -10,7 +10,7 @@ import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
10
10
  import { Item, decodeDict, encodeItem } from "structured-field-values";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.0.28-dev.1974+d70e3148";
13
+ var version = "2.0.28-dev.1982+a57dec3f";
14
14
  //#endregion
15
15
  //#region src/sig/key.ts
16
16
  /**
@@ -92,6 +92,118 @@ async function importJwk(jwk, type) {
92
92
  return key;
93
93
  }
94
94
  /**
95
+ * Fetches the document that the given actor URI dereferences to, and returns
96
+ * the actor only if the host that served the document is authoritative for
97
+ * the id the document claims.
98
+ *
99
+ * Only the origin that serves an actor id can speak for it. Without that
100
+ * rule any host could serve a document describing somebody else's actor—and
101
+ * listing its own keys as that actor's.
102
+ *
103
+ * @param actorId The URI of the actor to fetch.
104
+ * @param options Options for fetching the document.
105
+ * @returns The actor, or `null` if the document cannot be fetched, is not an
106
+ * actor, or belongs to another origin.
107
+ * @internal
108
+ */
109
+ async function fetchActorDocument(actorId, options = {}) {
110
+ const logger = getLogger([
111
+ "fedify",
112
+ "sig",
113
+ "key"
114
+ ]);
115
+ const documentLoader = options.documentLoader ?? getDocumentLoader();
116
+ const contextLoader = options.contextLoader ?? getDocumentLoader();
117
+ const { tracerProvider } = options;
118
+ let document;
119
+ let documentUrl = actorId;
120
+ try {
121
+ const remoteDocument = await documentLoader(actorId.href);
122
+ document = remoteDocument.document;
123
+ documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
124
+ } catch (error) {
125
+ logger.debug("Failed to fetch the actor {actorId}: {error}", {
126
+ actorId: actorId.href,
127
+ error
128
+ });
129
+ return null;
130
+ }
131
+ let object;
132
+ try {
133
+ object = await Object$1.fromJsonLd(document, {
134
+ documentLoader,
135
+ contextLoader,
136
+ tracerProvider,
137
+ baseUrl: documentUrl
138
+ });
139
+ } catch (error) {
140
+ if (!(error instanceof TypeError)) throw error;
141
+ logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
142
+ documentUrl: documentUrl.href,
143
+ error
144
+ });
145
+ return null;
146
+ }
147
+ if (!isActor(object)) return null;
148
+ if (object.id == null || object.id.origin !== documentUrl.origin) {
149
+ logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
150
+ documentUrl: documentUrl.href,
151
+ actorId: object.id?.href
152
+ });
153
+ return null;
154
+ }
155
+ return object;
156
+ }
157
+ /**
158
+ * Resolves the owner that the given key claims, and returns it only if the
159
+ * claim holds.
160
+ *
161
+ * The `owner` of a {@link CryptographicKey}, like the `controller` of
162
+ * a {@link Multikey}, proves nothing on its own: the key document and the
163
+ * claim inside it are served by the same host, so anyone able to serve a key
164
+ * document can name any actor in the world as its owner. The claim becomes
165
+ * meaningful only once the named actor's own document is fetched and turns
166
+ * out to link back to the key. This function performs that mutual-link
167
+ * check, which is the only sound way to learn a fetched key's owner.
168
+ *
169
+ * The claimed owner is always dereferenced, never read out of the key
170
+ * document: an owner embedded there is written by whoever wrote the key.
171
+ *
172
+ * @param key The key whose ownership claim is to be verified. It must carry
173
+ * an `id`, as that is what the owner has to link back to.
174
+ * @param options Options for fetching the claimed owner.
175
+ * @returns The verified owner, or `null` if the key claims no owner, the
176
+ * owner cannot be fetched, or the owner does not link back to
177
+ * the key.
178
+ * @internal
179
+ */
180
+ async function verifyKeyOwnership(key, options = {}) {
181
+ const logger = getLogger([
182
+ "fedify",
183
+ "sig",
184
+ "key"
185
+ ]);
186
+ const keyId = key.id;
187
+ if (keyId == null) return null;
188
+ const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
189
+ if (claimedOwnerId == null) return null;
190
+ const owner = await fetchActorDocument(claimedOwnerId, options);
191
+ if (owner == null) {
192
+ logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
193
+ keyId: keyId.href,
194
+ claimedOwnerId: claimedOwnerId.href
195
+ });
196
+ return null;
197
+ }
198
+ const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
199
+ for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
200
+ logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
201
+ keyId: keyId.href,
202
+ claimedOwnerId: claimedOwnerId.href
203
+ });
204
+ return null;
205
+ }
206
+ /**
95
207
  * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
96
208
  * If the given URL contains an {@link Actor} object, it tries to find
97
209
  * the corresponding key in the `publicKey` or `assertionMethod` property.
@@ -160,8 +272,11 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
160
272
  }
161
273
  logger.debug("Fetching key {keyId} to verify signature...", { keyId });
162
274
  let document;
275
+ let documentUrl = cacheKey;
163
276
  try {
164
- document = (await (documentLoader ?? getDocumentLoader())(keyId)).document;
277
+ const remoteDocument = await (documentLoader ?? getDocumentLoader())(keyId);
278
+ document = remoteDocument.document;
279
+ documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
165
280
  } catch (_) {
166
281
  logger.debug("Failed to fetch key {keyId}.", { keyId });
167
282
  await keyCache?.set(cacheKey, null);
@@ -198,8 +313,22 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
198
313
  }
199
314
  }
200
315
  let key = null;
201
- if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object;
316
+ let ownerDocument = null;
317
+ if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
202
318
  else if (isActor(object)) {
319
+ if (object.id == null || object.id.origin !== documentUrl.origin) {
320
+ logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
321
+ keyId,
322
+ documentUrl: documentUrl.href,
323
+ actorId: object.id?.href
324
+ });
325
+ await keyCache?.set(cacheKey, null);
326
+ return {
327
+ key: null,
328
+ cached: false
329
+ };
330
+ }
331
+ ownerDocument = object;
203
332
  const keys = cls === CryptographicKey ? object.getPublicKeys({
204
333
  documentLoader,
205
334
  contextLoader,
@@ -258,6 +387,25 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
258
387
  cached: false
259
388
  };
260
389
  }
390
+ const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
391
+ if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
392
+ else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
393
+ if (await verifyKeyOwnership(key, {
394
+ documentLoader,
395
+ contextLoader,
396
+ tracerProvider
397
+ }) == null) {
398
+ logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
399
+ keyId,
400
+ claimedOwnerId: claimedOwnerId.href
401
+ });
402
+ await keyCache?.set(cacheKey, null);
403
+ return {
404
+ key: null,
405
+ cached: false
406
+ };
407
+ }
408
+ }
261
409
  if (keyCache != null) {
262
410
  await keyCache.set(cacheKey, key);
263
411
  logger.debug("Key {keyId} cached.", { keyId });
@@ -1109,4 +1257,4 @@ function timingSafeEqual(a, b) {
1109
1257
  return result === 0;
1110
1258
  }
1111
1259
  //#endregion
1112
- export { fetchKey as a, validateCryptoKey as c, exportJwk as i, name as l, signRequest as n, generateCryptoKeyPair as o, verifyRequest as r, importJwk as s, doubleKnock as t, version as u };
1260
+ export { fetchActorDocument as a, importJwk as c, name as d, version as f, exportJwk as i, validateCryptoKey as l, signRequest as n, fetchKey as o, verifyRequest as r, generateCryptoKeyPair as s, doubleKnock as t, verifyKeyOwnership as u };
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CGtm12Ft.mjs";
4
+ import { n as version, t as name } from "./deno-eqaJj6JL.mjs";
5
5
  import { Activity, getTypeId } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
7
  import { getLogger } from "@logtape/logtape";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CGtm12Ft.mjs";
4
+ import { n as version, t as name } from "./deno-eqaJj6JL.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -86,6 +86,118 @@ async function importJwk(jwk, type) {
86
86
  return key;
87
87
  }
88
88
  /**
89
+ * Fetches the document that the given actor URI dereferences to, and returns
90
+ * the actor only if the host that served the document is authoritative for
91
+ * the id the document claims.
92
+ *
93
+ * Only the origin that serves an actor id can speak for it. Without that
94
+ * rule any host could serve a document describing somebody else's actor—and
95
+ * listing its own keys as that actor's.
96
+ *
97
+ * @param actorId The URI of the actor to fetch.
98
+ * @param options Options for fetching the document.
99
+ * @returns The actor, or `null` if the document cannot be fetched, is not an
100
+ * actor, or belongs to another origin.
101
+ * @internal
102
+ */
103
+ async function fetchActorDocument(actorId, options = {}) {
104
+ const logger = getLogger([
105
+ "fedify",
106
+ "sig",
107
+ "key"
108
+ ]);
109
+ const documentLoader = options.documentLoader ?? getDocumentLoader();
110
+ const contextLoader = options.contextLoader ?? getDocumentLoader();
111
+ const { tracerProvider } = options;
112
+ let document;
113
+ let documentUrl = actorId;
114
+ try {
115
+ const remoteDocument = await documentLoader(actorId.href);
116
+ document = remoteDocument.document;
117
+ documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
118
+ } catch (error) {
119
+ logger.debug("Failed to fetch the actor {actorId}: {error}", {
120
+ actorId: actorId.href,
121
+ error
122
+ });
123
+ return null;
124
+ }
125
+ let object;
126
+ try {
127
+ object = await Object$1.fromJsonLd(document, {
128
+ documentLoader,
129
+ contextLoader,
130
+ tracerProvider,
131
+ baseUrl: documentUrl
132
+ });
133
+ } catch (error) {
134
+ if (!(error instanceof TypeError)) throw error;
135
+ logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
136
+ documentUrl: documentUrl.href,
137
+ error
138
+ });
139
+ return null;
140
+ }
141
+ if (!isActor(object)) return null;
142
+ if (object.id == null || object.id.origin !== documentUrl.origin) {
143
+ logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
144
+ documentUrl: documentUrl.href,
145
+ actorId: object.id?.href
146
+ });
147
+ return null;
148
+ }
149
+ return object;
150
+ }
151
+ /**
152
+ * Resolves the owner that the given key claims, and returns it only if the
153
+ * claim holds.
154
+ *
155
+ * The `owner` of a {@link CryptographicKey}, like the `controller` of
156
+ * a {@link Multikey}, proves nothing on its own: the key document and the
157
+ * claim inside it are served by the same host, so anyone able to serve a key
158
+ * document can name any actor in the world as its owner. The claim becomes
159
+ * meaningful only once the named actor's own document is fetched and turns
160
+ * out to link back to the key. This function performs that mutual-link
161
+ * check, which is the only sound way to learn a fetched key's owner.
162
+ *
163
+ * The claimed owner is always dereferenced, never read out of the key
164
+ * document: an owner embedded there is written by whoever wrote the key.
165
+ *
166
+ * @param key The key whose ownership claim is to be verified. It must carry
167
+ * an `id`, as that is what the owner has to link back to.
168
+ * @param options Options for fetching the claimed owner.
169
+ * @returns The verified owner, or `null` if the key claims no owner, the
170
+ * owner cannot be fetched, or the owner does not link back to
171
+ * the key.
172
+ * @internal
173
+ */
174
+ async function verifyKeyOwnership(key, options = {}) {
175
+ const logger = getLogger([
176
+ "fedify",
177
+ "sig",
178
+ "key"
179
+ ]);
180
+ const keyId = key.id;
181
+ if (keyId == null) return null;
182
+ const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
183
+ if (claimedOwnerId == null) return null;
184
+ const owner = await fetchActorDocument(claimedOwnerId, options);
185
+ if (owner == null) {
186
+ logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
187
+ keyId: keyId.href,
188
+ claimedOwnerId: claimedOwnerId.href
189
+ });
190
+ return null;
191
+ }
192
+ const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
193
+ for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
194
+ logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
195
+ keyId: keyId.href,
196
+ claimedOwnerId: claimedOwnerId.href
197
+ });
198
+ return null;
199
+ }
200
+ /**
89
201
  * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
90
202
  * If the given URL contains an {@link Actor} object, it tries to find
91
203
  * the corresponding key in the `publicKey` or `assertionMethod` property.
@@ -154,8 +266,11 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
154
266
  }
155
267
  logger.debug("Fetching key {keyId} to verify signature...", { keyId });
156
268
  let document;
269
+ let documentUrl = cacheKey;
157
270
  try {
158
- document = (await (documentLoader ?? getDocumentLoader())(keyId)).document;
271
+ const remoteDocument = await (documentLoader ?? getDocumentLoader())(keyId);
272
+ document = remoteDocument.document;
273
+ documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
159
274
  } catch (_) {
160
275
  logger.debug("Failed to fetch key {keyId}.", { keyId });
161
276
  await keyCache?.set(cacheKey, null);
@@ -192,8 +307,22 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
192
307
  }
193
308
  }
194
309
  let key = null;
195
- if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object;
310
+ let ownerDocument = null;
311
+ if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
196
312
  else if (isActor(object)) {
313
+ if (object.id == null || object.id.origin !== documentUrl.origin) {
314
+ logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
315
+ keyId,
316
+ documentUrl: documentUrl.href,
317
+ actorId: object.id?.href
318
+ });
319
+ await keyCache?.set(cacheKey, null);
320
+ return {
321
+ key: null,
322
+ cached: false
323
+ };
324
+ }
325
+ ownerDocument = object;
197
326
  const keys = cls === CryptographicKey ? object.getPublicKeys({
198
327
  documentLoader,
199
328
  contextLoader,
@@ -252,6 +381,25 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
252
381
  cached: false
253
382
  };
254
383
  }
384
+ const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
385
+ if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
386
+ else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
387
+ if (await verifyKeyOwnership(key, {
388
+ documentLoader,
389
+ contextLoader,
390
+ tracerProvider
391
+ }) == null) {
392
+ logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
393
+ keyId,
394
+ claimedOwnerId: claimedOwnerId.href
395
+ });
396
+ await keyCache?.set(cacheKey, null);
397
+ return {
398
+ key: null,
399
+ cached: false
400
+ };
401
+ }
402
+ }
255
403
  if (keyCache != null) {
256
404
  await keyCache.set(cacheKey, key);
257
405
  logger.debug("Key {keyId} cached.", { keyId });
@@ -262,4 +410,4 @@ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, key
262
410
  };
263
411
  }
264
412
  //#endregion
265
- export { validateCryptoKey as a, importJwk as i, fetchKey as n, generateCryptoKeyPair as r, exportJwk as t };
413
+ export { importJwk as a, generateCryptoKeyPair as i, fetchActorDocument as n, validateCryptoKey as o, fetchKey as r, verifyKeyOwnership as s, exportJwk as t };
@@ -5,6 +5,7 @@ import { CryptographicKey, Multikey } from "@fedify/vocab";
5
5
  //#region src/federation/keycache.ts
6
6
  const NULL_KEY_CACHE_VALUE = { _fedify: "key-unavailable" };
7
7
  const NULL_KEY_CACHE_TTL = Temporal.Duration.from({ minutes: 5 });
8
+ const KEY_CACHE_GENERATION = "2";
8
9
  function isNullKeyCacheValue(value) {
9
10
  return typeof value === "object" && value != null && "_fedify" in value && value._fedify === NULL_KEY_CACHE_VALUE._fedify;
10
11
  }
@@ -19,9 +20,16 @@ var KvKeyCache = class {
19
20
  this.nullKeys = /* @__PURE__ */ new Set();
20
21
  this.options = options;
21
22
  }
23
+ #entryKey(keyId) {
24
+ return [
25
+ ...this.prefix,
26
+ KEY_CACHE_GENERATION,
27
+ keyId.href
28
+ ];
29
+ }
22
30
  async get(keyId) {
23
31
  if (this.nullKeys.has(keyId.href)) return null;
24
- const serialized = await this.kv.get([...this.prefix, keyId.href]);
32
+ const serialized = await this.kv.get(this.#entryKey(keyId));
25
33
  if (serialized == null) return void 0;
26
34
  if (isNullKeyCacheValue(serialized)) {
27
35
  this.nullKeys.add(keyId.href);
@@ -33,7 +41,7 @@ var KvKeyCache = class {
33
41
  try {
34
42
  return await Multikey.fromJsonLd(serialized, this.options);
35
43
  } catch {
36
- await this.kv.delete([...this.prefix, keyId.href]);
44
+ await this.kv.delete(this.#entryKey(keyId));
37
45
  return;
38
46
  }
39
47
  }
@@ -41,12 +49,12 @@ var KvKeyCache = class {
41
49
  async set(keyId, key) {
42
50
  if (key == null) {
43
51
  this.nullKeys.add(keyId.href);
44
- await this.kv.set([...this.prefix, keyId.href], NULL_KEY_CACHE_VALUE, { ttl: NULL_KEY_CACHE_TTL });
52
+ await this.kv.set(this.#entryKey(keyId), NULL_KEY_CACHE_VALUE, { ttl: NULL_KEY_CACHE_TTL });
45
53
  return;
46
54
  }
47
55
  this.nullKeys.delete(keyId.href);
48
56
  const serialized = await key.toJsonLd(this.options);
49
- await this.kv.set([...this.prefix, keyId.href], serialized);
57
+ await this.kv.set(this.#entryKey(keyId), serialized);
50
58
  }
51
59
  };
52
60
  //#endregion
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-BuaGj55w.cjs");
4
+ const require_http = require("./http-BgOlx4n1.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { c as validateCryptoKey, t as doubleKnock } from "./http-BP1pVF9m.js";
3
+ import { l as validateCryptoKey, t as doubleKnock } from "./http-CwcG2wDs.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CGtm12Ft.mjs";
5
- import { a as validateCryptoKey, n as fetchKey } from "./key-rC9mW1U5.mjs";
4
+ import { n as version, t as name } from "./deno-eqaJj6JL.mjs";
5
+ import { o as validateCryptoKey, r as fetchKey } from "./key-bSphUqhB.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-BXwn4gnl.cjs");
3
+ const require_middleware = require("./middleware-D_7wZH27.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,24 +2,24 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-CGtm12Ft.mjs";
6
- import { a as validateCryptoKey, i as importJwk, t as exportJwk } from "./key-rC9mW1U5.mjs";
7
- import { l as verifyRequest } from "./http--P33qRwW.mjs";
8
- import { t as getAuthenticatedDocumentLoader } from "./docloader-_hXTUC-b.mjs";
9
- import { n as kvCache } from "./kv-cache-DqEKe5E0.mjs";
10
- import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-CN2nacLy.mjs";
11
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-CFgyZgkz.mjs";
12
- import { n as signObject, r as verifyObject } from "./proof-CpEH24hi.mjs";
13
- import { t as getNodeInfo } from "./client-HLLGHiTU.mjs";
14
- import { t as nodeInfoToJson } from "./types-C37hquWI.mjs";
15
- import { n as routeActivity } from "./inbox-BZNyUPzW.mjs";
16
- import { t as FederationBuilderImpl } from "./builder-DBKy92BX.mjs";
17
- import { t as buildCollectionSynchronizationHeader } from "./collection-ChgDTHLz.mjs";
18
- import { t as KvKeyCache } from "./keycache-DaQ3ndaJ.mjs";
19
- import { t as acceptsJsonLd } from "./negotiation-BehA2uul.mjs";
20
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-B4rBDksp.mjs";
21
- import { t as createExponentialBackoffPolicy } from "./retry-B_E3V_Dx.mjs";
22
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-pXXMu19o.mjs";
5
+ import { n as version, t as name } from "./deno-eqaJj6JL.mjs";
6
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-bSphUqhB.mjs";
7
+ import { l as verifyRequest } from "./http-Bu3NxfGb.mjs";
8
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BXUqhXNd.mjs";
9
+ import { n as kvCache } from "./kv-cache-BrjuJue8.mjs";
10
+ import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-D0uFMLmq.mjs";
11
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-Bzg6w7eA.mjs";
12
+ import { n as signObject, r as verifyObject } from "./proof-CgLA51pO.mjs";
13
+ import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-glFHmpn4.mjs";
14
+ import { t as nodeInfoToJson } from "./types-MNlFfxjv.mjs";
15
+ import { n as routeActivity } from "./inbox-Hou2ZAjv.mjs";
16
+ import { t as FederationBuilderImpl } from "./builder-DG5rS_Zv.mjs";
17
+ import { t as buildCollectionSynchronizationHeader } from "./collection-DI6YzYpd.mjs";
18
+ import { t as KvKeyCache } from "./keycache-D239uGT9.mjs";
19
+ import { t as acceptsJsonLd } from "./negotiation-TWEs6Cl8.mjs";
20
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-DPJlkHh3.mjs";
21
+ import { t as createExponentialBackoffPolicy } from "./retry-Ddbq3AcK.mjs";
22
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-D89-FDX-.mjs";
23
23
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
24
24
  import { lookupWebFinger } from "@fedify/webfinger";
25
25
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -474,8 +474,16 @@ async function handleInboxInternal(request, parameters, span) {
474
474
  }
475
475
  let json;
476
476
  try {
477
- json = await request.clone().json();
477
+ json = JSON.parse(await readBoundedText(request.clone(), MAX_BODY_SIZE, request.url));
478
478
  } catch (error) {
479
+ if (error instanceof BodyTooLargeError) {
480
+ request.body?.cancel(error).catch(() => {});
481
+ span.setStatus({
482
+ code: SpanStatusCode.ERROR,
483
+ message: String(error)
484
+ });
485
+ return new Response("Inbox body too large.", { status: 413 });
486
+ }
479
487
  logger.error("Failed to parse JSON:\n{error}", {
480
488
  recipient,
481
489
  error
@@ -1656,6 +1664,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1656
1664
  }
1657
1665
  try {
1658
1666
  await sendActivity({
1667
+ allowPrivateAddress: this.allowPrivateAddress,
1659
1668
  keys,
1660
1669
  activity: message.activity,
1661
1670
  activityId: message.activityId,
@@ -2047,6 +2056,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2047
2056
  });
2048
2057
  const promises = [];
2049
2058
  for (const inbox in inboxes) promises.push(sendActivity({
2059
+ allowPrivateAddress: this.allowPrivateAddress,
2050
2060
  keys,
2051
2061
  activity: jsonLd,
2052
2062
  activityId: activity.id?.href,
@@ -3187,6 +3197,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3187
3197
  else logger.debug("Forwarding activity immediately without queue since queue is not set.");
3188
3198
  const promises = [];
3189
3199
  for (const inbox in inboxes) promises.push(sendActivity({
3200
+ allowPrivateAddress: this.federation.allowPrivateAddress,
3190
3201
  keys,
3191
3202
  activity: this.activity,
3192
3203
  activityId: this.activityId,
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DpEJSmjy.mjs";
4
+ import { n as FederationImpl } from "./middleware-BuVM1K-J.mjs";
5
5
  export { FederationImpl };