@fedify/fedify 2.4.0-dev.2127 → 2.4.0-dev.2141

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 (92) hide show
  1. package/dist/{builder-B2bgGYbP.mjs → builder-BI7H61zO.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-SVlHSzV7.mjs → compound-proof-D0H4LLvQ.mjs} +15 -20
  6. package/dist/{context-Ci5vuUr_.d.ts → context-B2C8nuN3.d.ts} +4 -3
  7. package/dist/{context-Bm6ldGvx.d.cts → context-CrDDVrbF.d.cts} +4 -3
  8. package/dist/{deno-Be9xoxps.mjs → deno-BXI_BLNy.mjs} +1 -1
  9. package/dist/{docloader-Dtuyb0ps.mjs → docloader-B5BIo7e9.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +64 -8
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +7 -7
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +6 -6
  23. package/dist/federation/portable-actor.test.mjs +3 -3
  24. package/dist/federation/portable-chain.test.mjs +17 -5
  25. package/dist/federation/portable-gateway-keys.test.mjs +76 -5
  26. package/dist/federation/portable-inbox.test.mjs +9 -4
  27. package/dist/federation/portable.test.mjs +3 -3
  28. package/dist/federation/send.test.mjs +3 -3
  29. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  30. package/dist/federation/tasks/tasks.test.mjs +2 -2
  31. package/dist/federation/temporal.test.mjs +1 -1
  32. package/dist/federation/webfinger.test.mjs +1 -1
  33. package/dist/{federation-B6IHuS6n.cjs → federation-DqDOPjWo.cjs} +23 -28
  34. package/dist/{http-D76Py0M4.d.cts → http-C-x_OLSp.d.ts} +57 -14
  35. package/dist/{http-BFeK9sBg.mjs → http-CEx2SZjf.mjs} +18 -12
  36. package/dist/{http-BrQgkJay.d.ts → http-CjezPjFX.d.cts} +57 -14
  37. package/dist/{http-Rmbiu-5S.js → http-D4KW2GTi.js} +376 -36
  38. package/dist/{http-o8l4QeUY.cjs → http-DSYQQn23.cjs} +392 -34
  39. package/dist/{key-Cc1b6pW9.mjs → key-C0giKuki.mjs} +103 -16
  40. package/dist/{kv-cache-tLKBfxHc.mjs → kv-cache-AWTrxTq3.mjs} +1 -1
  41. package/dist/{kv-cache-BUkgZAcX.cjs → kv-cache-BcsvaCCL.cjs} +1 -1
  42. package/dist/{kv-cache-BweDce2I.js → kv-cache-D_E-2K3i.js} +1 -1
  43. package/dist/{ld-DQNTIrAe.mjs → ld-CWDReGMZ.mjs} +3 -3
  44. package/dist/{metrics-DDE9S63Q.mjs → metrics-DxlVXwn5.mjs} +1 -1
  45. package/dist/{middleware-K9aLnf57.js → middleware-1mv9QR16.js} +23 -28
  46. package/dist/{middleware-CsQorLC_.mjs → middleware-B7stx5gy.mjs} +1 -1
  47. package/dist/{middleware-BdhvduR9.mjs → middleware-CAWRGSwH.mjs} +24 -24
  48. package/dist/{mod-AVir5nVI.d.cts → mod-B7SfEqIP.d.cts} +1 -1
  49. package/dist/{mod-Co53r-VP.d.ts → mod-Bz9tFXI2.d.ts} +1 -1
  50. package/dist/{mod-eVOUPbQE.d.ts → mod-C06GoL5Z.d.ts} +1 -1
  51. package/dist/{mod-Cwdt_CWU.d.ts → mod-CZetqT5h.d.ts} +1 -1
  52. package/dist/{mod-Cr79cQlc.d.cts → mod-D4g9ffEn.d.cts} +1 -1
  53. package/dist/{mod-Byt5tvAS.d.cts → mod-S--JxYjo.d.cts} +1 -1
  54. package/dist/mod.cjs +4 -4
  55. package/dist/mod.d.cts +5 -5
  56. package/dist/mod.d.ts +5 -5
  57. package/dist/mod.js +4 -4
  58. package/dist/nodeinfo/handler.test.mjs +1 -1
  59. package/dist/{owner-CCfdL83M.mjs → owner-CgoCOTUQ.mjs} +37 -4
  60. package/dist/{portable-collection-WXaHkNqC.mjs → portable-collection-0_5DyXMt.mjs} +2 -2
  61. package/dist/{portable-collection-DXogVs__.cjs → portable-collection-BD8fE3j3.cjs} +34 -1
  62. package/dist/{portable-collection-DdXsKBg4.js → portable-collection-CG9P_i0E.js} +34 -1
  63. package/dist/portable-key-BXkgMpw1.mjs +395 -0
  64. package/dist/{portable-key-id-T7lecIbt.mjs → portable-key-id-Bg259c4Z.mjs} +69 -2
  65. package/dist/{proof-CW8k0R1G.mjs → proof-B5s-W6Sm.mjs} +7 -6
  66. package/dist/{send--FORaIc4.mjs → send-BAJvkdNL.mjs} +3 -3
  67. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  68. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  69. package/dist/sig/compound-proof-verification.test.mjs +196 -2
  70. package/dist/sig/compound-proof.test.mjs +2 -2
  71. package/dist/sig/http.test.mjs +2 -2
  72. package/dist/sig/key.test.mjs +1 -1
  73. package/dist/sig/ld.test.mjs +2 -2
  74. package/dist/sig/mod.cjs +2 -2
  75. package/dist/sig/mod.d.cts +2 -2
  76. package/dist/sig/mod.d.ts +2 -2
  77. package/dist/sig/mod.js +2 -2
  78. package/dist/sig/owner.test.mjs +1 -1
  79. package/dist/sig/portable-collection.test.mjs +1 -1
  80. package/dist/sig/portable-key.test.mjs +479 -5
  81. package/dist/sig/proof.test.mjs +45 -3
  82. package/dist/{temporal-CffY_XOJ.mjs → temporal-BkCPJ_uC.mjs} +1 -1
  83. package/dist/testing/kv.test.mjs +1 -1
  84. package/dist/testing/mod.d.mts +3 -2
  85. package/dist/utils/docloader.test.mjs +2 -2
  86. package/dist/utils/kv-cache.test.mjs +1 -1
  87. package/dist/utils/mod.cjs +1 -1
  88. package/dist/utils/mod.d.cts +1 -1
  89. package/dist/utils/mod.d.ts +1 -1
  90. package/dist/utils/mod.js +1 -1
  91. package/package.json +6 -6
  92. package/dist/portable-key-C-fPq_Yf.mjs +0 -214
@@ -7,10 +7,10 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
7
7
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
- import { p as signJsonLd } from "../ld-DQNTIrAe.mjs";
11
- import { l as signRequest } from "../http-BFeK9sBg.mjs";
10
+ import { p as signJsonLd } from "../ld-CWDReGMZ.mjs";
11
+ import { l as signRequest } from "../http-CEx2SZjf.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-BdhvduR9.mjs";
13
+ import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-CAWRGSwH.mjs";
14
14
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
15
15
  import { t as KvKeyCache } from "../keycache-BInaECGj.mjs";
16
16
  import { Create, CryptographicKey, Person } from "@fedify/vocab";
@@ -214,6 +214,24 @@ test("Context.sendActivity() never signs documents with gateway keys", async ()
214
214
  assertEquals(bodies[0].proof, void 0);
215
215
  assertEquals(bodies[0].signature, void 0);
216
216
  });
217
+ test("Context.sendActivity() never signs documents with keys at ap: URIs", async () => {
218
+ const ctx = createTestFederation().createContext(new URL(gateway));
219
+ const keys = [{
220
+ keyId: parseIri(`${actorId.href}#main-key`),
221
+ privateKey: rsaPrivateKey2
222
+ }, {
223
+ keyId: parseIri(`${actorId.href}#key-2`),
224
+ privateKey: ed25519GatewayKeyPair.privateKey
225
+ }];
226
+ const activity = new Create({
227
+ id: new URL("https://example.com/activities/1"),
228
+ actor: new URL("https://example.com/users/bob"),
229
+ object: new URL("https://example.com/notes/1")
230
+ });
231
+ const { bodies } = await capture(() => ctx.sendActivity(keys, recipient, activity));
232
+ assertEquals(bodies[0].proof, void 0);
233
+ assertEquals(bodies[0].signature, void 0);
234
+ });
217
235
  test("Context.getDocumentLoader() signs requests with gateway keys for portable actors", async () => {
218
236
  const documentLoader = await createTestFederation(() => actorId).createContext(new URL(gateway)).getDocumentLoader({ identifier: "alice" });
219
237
  let keyId;
@@ -255,14 +273,14 @@ function createLoader(responses) {
255
273
  });
256
274
  };
257
275
  }
258
- async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor, documentLoader } = {}) {
276
+ async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor, documentLoader, keyId = gatewayKeyId } = {}) {
259
277
  documentLoader ??= createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
260
278
  const unsigned = new Request("https://local.example/inbox", {
261
279
  method: "POST",
262
280
  body: JSON.stringify(body),
263
281
  headers: { "Content-Type": "application/activity+json" }
264
282
  });
265
- const request = httpSignature ? await signRequest(unsigned, rsaPrivateKey2, gatewayKeyId) : unsigned;
283
+ const request = httpSignature ? await signRequest(unsigned, rsaPrivateKey2, keyId) : unsigned;
266
284
  const context = createRequestContext({
267
285
  federation: createFederation({ kv: new MemoryKvStore() }),
268
286
  request,
@@ -332,6 +350,59 @@ test("handleInbox() requires proofs of portable actors despite gateway signature
332
350
  dispatched: 1
333
351
  });
334
352
  });
353
+ const apKeyId = parseIri(`${actorId.href}?@gateway=${encodeURIComponent(gateway)}#main-key`);
354
+ async function apActorDocument() {
355
+ const json = await new Person({
356
+ id: actorId,
357
+ inbox: parseIri(`${actorId.href}/inbox`),
358
+ gateways: [new URL(gateway)],
359
+ publicKey: new CryptographicKey({
360
+ id: parseIri(`${actorId.href}#main-key`),
361
+ owner: actorId,
362
+ publicKey: rsaPublicKey2.publicKey
363
+ })
364
+ }).toJsonLd({
365
+ format: "compact",
366
+ contextLoader: mockDocumentLoader
367
+ });
368
+ json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
369
+ return await sign(json);
370
+ }
371
+ test("handleInbox() requires proofs of portable actors despite signatures with keys at ap: key IDs", async () => {
372
+ const fetched = [];
373
+ const inner = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
374
+ const documentLoader = (url, options) => {
375
+ fetched.push(url);
376
+ return inner(url, options);
377
+ };
378
+ assertEquals(await deliver(await createJson("unsigned"), [gateway], {
379
+ documentLoader,
380
+ keyId: apKeyId
381
+ }), {
382
+ status: 401,
383
+ dispatched: 0
384
+ });
385
+ assertEquals(fetched.some((url) => url.startsWith(compatibleActorId(gateway))), true);
386
+ assertEquals(await deliver(await sign(await createJson("signed")), [gateway], {
387
+ documentLoader,
388
+ keyId: apKeyId
389
+ }), {
390
+ status: 202,
391
+ dispatched: 1
392
+ });
393
+ });
394
+ test("RequestContext.getSignedKeyOwner() resolves keys at ap: key IDs", async () => {
395
+ const documentLoader = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
396
+ const federation = new FederationImpl({
397
+ kv: new MemoryKvStore(),
398
+ contextLoaderFactory: () => mockDocumentLoader,
399
+ documentLoaderFactory: () => documentLoader
400
+ });
401
+ const request = await signRequest(new Request("https://local.example/notes/1", { headers: { Accept: "application/activity+json" } }), rsaPrivateKey2, apKeyId);
402
+ const ctx = federation.createContext(request, void 0);
403
+ assertEquals((await ctx.getSignedKey())?.id?.href, apKeyId.href);
404
+ assertEquals((await ctx.getSignedKeyOwner())?.id?.href, actorId.href);
405
+ });
335
406
  test("handleInbox() fetches an unreachable gateway key once", async () => {
336
407
  const fetched = [];
337
408
  const documentLoader = (url) => {
@@ -8,11 +8,11 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { p as signJsonLd } from "../ld-DQNTIrAe.mjs";
12
- import { i as signObject } from "../proof-CW8k0R1G.mjs";
13
- import { f as verifyRequestDetailed, l as signRequest } from "../http-BFeK9sBg.mjs";
11
+ import { p as signJsonLd } from "../ld-CWDReGMZ.mjs";
12
+ import { i as signObject } from "../proof-B5s-W6Sm.mjs";
13
+ import { f as verifyRequestDetailed, l as signRequest } from "../http-CEx2SZjf.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
15
- import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-BdhvduR9.mjs";
15
+ import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-CAWRGSwH.mjs";
16
16
  import { Create, CryptographicKey, Follow, Note, Person, Tombstone } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
18
18
  import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
@@ -1089,6 +1089,11 @@ test("Federation.fetch() forwards portable inbox deliveries whose sending gatewa
1089
1089
  sign: { keyId: bobKeyId },
1090
1090
  fetches: false
1091
1091
  },
1092
+ {
1093
+ name: "key at an ap: key ID",
1094
+ sign: { keyId: parseIri(`ap+ef61://${did}/users/alice?@gateway=${encodeURIComponent(GATEWAY2)}#main-key`) },
1095
+ fetches: false
1096
+ },
1092
1097
  {
1093
1098
  name: "content-digest not covered",
1094
1099
  sign: {
@@ -8,10 +8,10 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { i as signObject, s as verifyPortableObjectProof } from "../proof-CW8k0R1G.mjs";
12
- import { l as signRequest } from "../http-BFeK9sBg.mjs";
11
+ import { i as signObject, s as verifyPortableObjectProof } from "../proof-B5s-W6Sm.mjs";
12
+ import { l as signRequest } from "../http-CEx2SZjf.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BdhvduR9.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-CAWRGSwH.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { d as verifyRequest } from "../http-BFeK9sBg.mjs";
13
+ import { d as verifyRequest } from "../http-CEx2SZjf.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-CCfdL83M.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send--FORaIc4.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-CgoCOTUQ.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-BAJvkdNL.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
6
6
  import "../../testing-otQhu4aD.mjs";
7
- import { o as createFederation } from "../../middleware-BdhvduR9.mjs";
7
+ import { o as createFederation } from "../../middleware-CAWRGSwH.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
6
6
  import "../../testing-otQhu4aD.mjs";
7
- import { o as createFederation } from "../../middleware-BdhvduR9.mjs";
8
- import { r as createFederationBuilder } from "../../builder-B2bgGYbP.mjs";
7
+ import { o as createFederation } from "../../middleware-CAWRGSwH.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-BI7H61zO.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CffY_XOJ.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-BkCPJ_uC.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-BdhvduR9.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-CAWRGSwH.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-o8l4QeUY.cjs");
4
+ const require_http = require("./http-DSYQQn23.cjs");
5
5
  const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_portable_collection = require("./portable-collection-DXogVs__.cjs");
7
- const require_kv_cache = require("./kv-cache-BUkgZAcX.cjs");
6
+ const require_portable_collection = require("./portable-collection-BD8fE3j3.cjs");
7
+ const require_kv_cache = require("./kv-cache-BcsvaCCL.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -1964,9 +1964,9 @@ const KEY_PROPERTIES = /* @__PURE__ */ new Set([
1964
1964
  *
1965
1965
  * The projection must expand to the parent's verified ID with the key as the
1966
1966
  * single value of `publicKey` or `assertionMethod`, and the key must be
1967
- * nothing but a `CryptographicKey` or `Multikey` whose ID is a compatible
1968
- * identifier of the parent plus a fragment, and whose owner or controller,
1969
- * if any, is the parent.
1967
+ * nothing but a `CryptographicKey` or `Multikey` whose ID is the parent's
1968
+ * `ap:` or `ap+ef61:` URI, or its compatible identifier, plus a fragment,
1969
+ * and whose owner or controller, if any, is the parent.
1970
1970
  */
1971
1971
  async function isEmbeddedPortableKey(parent, member, key, parentId) {
1972
1972
  const parentPortableId = getCanonicalPortableIdOf(parentId);
@@ -2024,21 +2024,16 @@ function getCanonicalPortableIdOf(id) {
2024
2024
  }
2025
2025
  }
2026
2026
  /**
2027
- * Checks whether an ID is a compatible identifier of the given portable
2028
- * object plus a fragment.
2027
+ * Checks whether an ID is the given portable object's `ap:` or `ap+ef61:`
2028
+ * URI, or its compatible identifier, plus a non-empty fragment. The ID is
2029
+ * compared as it is written, so an ID that URL parsing would turn into
2030
+ * the portable object's, e.g., through dot segments, is not one of its keys.
2029
2031
  */
2030
2032
  function isKeyIdOf(id, portableId) {
2031
- let url;
2032
- try {
2033
- if ((0, _fedify_vocab_runtime.fromCompatibleEf61Id)(id) == null) return false;
2034
- url = new URL(id);
2035
- } catch (error) {
2036
- if (error instanceof TypeError) return false;
2037
- throw error;
2038
- }
2039
- if (url.hash.length < 2) return false;
2040
- url.hash = "";
2041
- return require_http.getCanonicalPortableId(url) === portableId;
2033
+ const canonical = require_http.getRawCanonicalPortableId(id);
2034
+ if (canonical == null) return false;
2035
+ const hash = canonical.indexOf("#");
2036
+ return hash >= 0 && hash < canonical.length - 1 && canonical.slice(0, hash) === portableId;
2042
2037
  }
2043
2038
  /**
2044
2039
  * Checks whether a `@context` value defines no keyword aliases and no scoped
@@ -4671,7 +4666,7 @@ async function handleInboxInternal(request, parameters, span) {
4671
4666
  "http_signatures.verified": httpSigKey != null,
4672
4667
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
4673
4668
  });
4674
- if (!skipSignatureVerification && !proofVerified && (isPortableActivity(activity) || isPortableGatewayKey(httpSigKey))) {
4669
+ if (!skipSignatureVerification && !proofVerified && (isPortableActivity(activity) || isPortableActorKey(httpSigKey))) {
4675
4670
  if (deferredLdSignatureError != null) throw deferredLdSignatureError;
4676
4671
  logger.error("The activity {activityId} of the portable actor {actorId} is not authenticated by a valid Object Integrity Proof.", {
4677
4672
  activity: json,
@@ -5452,13 +5447,13 @@ const MIN_COMPONENTS = [
5452
5447
  "@authority"
5453
5448
  ];
5454
5449
  /**
5455
- * Checks whether an HTTP Signature key is a gateway key of an FEP-ef61
5456
- * portable actor, i.e., its ID is a compatible identifier and it is owned by
5457
- * a portable actor, which only the portable actor's signed document can
5458
- * establish.
5450
+ * Checks whether an HTTP Signature key is a key of an FEP-ef61 portable
5451
+ * actor, i.e., its ID is a compatible identifier or an `ap:` or `ap+ef61:`
5452
+ * URI, and it is owned by a portable actor, which only the portable actor's
5453
+ * signed document can establish.
5459
5454
  */
5460
- function isPortableGatewayKey(key) {
5461
- return key?.id != null && require_http.isCompatibleKeyId(key.id) && key.ownerId != null && require_http.isPortableId(key.ownerId);
5455
+ function isPortableActorKey(key) {
5456
+ return key?.id != null && require_http.isPortableKeyId(key.id) && key.ownerId != null && require_http.isPortableId(key.ownerId);
5462
5457
  }
5463
5458
  /**
5464
5459
  * Checks whether an activity needs an Object Integrity Proof by its DID:
@@ -5895,7 +5890,7 @@ async function signOutgoingActivity(activity, candidates, options) {
5895
5890
  hasProof = true;
5896
5891
  break;
5897
5892
  }
5898
- const keys = candidates.filter((c) => c.privateKey.algorithm.name === "Ed25519" && !require_http.isCompatibleKeyId(c.verificationMethod));
5893
+ const keys = candidates.filter((c) => c.privateKey.algorithm.name === "Ed25519" && !require_http.isPortableKeyId(c.verificationMethod));
5899
5894
  if (!hasProof && activity.id != null && require_http.isPortableId(activity.id)) return await sign(selectPortableActivityKey(activity, activity.id, keys));
5900
5895
  if (keys.length < 1 || hasProof && !options.appendToExistingProofs) return {
5901
5896
  activity,
@@ -7863,7 +7858,7 @@ var FederationImpl = class extends FederationBuilderImpl {
7863
7858
  const ldSignable = !require_http.hasPortableActor(activity);
7864
7859
  for (const { keyId, privateKey } of keys) {
7865
7860
  require_http.validateCryptoKey(privateKey, "private");
7866
- if (ldSignable && rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && !require_http.isCompatibleKeyId(keyId)) rsaKey = {
7861
+ if (ldSignable && rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && !require_http.isPortableKeyId(keyId)) rsaKey = {
7867
7862
  keyId,
7868
7863
  privateKey
7869
7864
  };
@@ -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
  /**
@@ -29,6 +29,28 @@ type PortableGatewayKeyResolution = {
29
29
  */
30
30
  readonly expires?: Temporal.Instant;
31
31
  };
32
+ /**
33
+ * The result of resolving a key of a portable actor at an `ap:` or
34
+ * `ap+ef61:` key ID.
35
+ * @internal
36
+ */
37
+ type PortableActorKeyResolution = Exclude<PortableGatewayKeyResolution, {
38
+ readonly type: "legacy";
39
+ }> | {
40
+ /**
41
+ * Whether the actor's document vouches for the key could not be
42
+ * determined, e.g., because no gateway could be reached.
43
+ */
44
+ readonly type: "unavailable";
45
+ readonly error: unknown;
46
+ /**
47
+ * Whether the failure can be cached as a failure to fetch the key, i.e.,
48
+ * every gateway failed to serve the actor's document at all. A failure
49
+ * that is not cacheable comes with an error that has no HTTP status,
50
+ * since none of the statuses describes the lookup as a whole.
51
+ */
52
+ readonly cacheable: boolean;
53
+ };
32
54
  //#endregion
33
55
  //#region src/sig/key.d.ts
34
56
  /**
@@ -119,17 +141,34 @@ interface FetchKeyOptions {
119
141
  */
120
142
  keyIdBoundByCaller?: boolean;
121
143
  /**
122
- * Resolves a gateway key of an [FEP-ef61] portable actor, i.e., a key whose
123
- * ID is a compatible identifier and dereferences to a portable actor's
124
- * document. Such a key is resolved only through this function, and is
125
- * rejected if it is omitted, so that only HTTP Signature verification,
126
- * which passes it, accepts gateway keys. Object Integrity Proofs and
127
- * Linked Data Signatures never do.
144
+ * Resolves keys of [FEP-ef61] portable actors, which only HTTP Signature
145
+ * verification passes, so that only it accepts them. Object Integrity
146
+ * Proofs and Linked Data Signatures never do: such a key is rejected if
147
+ * this is omitted.
128
148
  *
129
149
  * [FEP-ef61]: https://w3id.org/fep/ef61
130
150
  * @internal
131
151
  */
132
- portableGatewayKeyResolver?: (document: unknown, actor: Actor, keyId: URL) => Promise<PortableGatewayKeyResolution>;
152
+ portableKeyResolvers?: PortableKeyResolvers;
153
+ }
154
+ /**
155
+ * Resolves keys of [FEP-ef61] portable actors for HTTP Signature
156
+ * verification. See {@link FetchKeyOptions.portableKeyResolvers}.
157
+ *
158
+ * [FEP-ef61]: https://w3id.org/fep/ef61
159
+ * @internal
160
+ */
161
+ interface PortableKeyResolvers {
162
+ /**
163
+ * Resolves a gateway key, i.e., a key whose ID is a compatible identifier
164
+ * and dereferences to a portable actor's document.
165
+ */
166
+ gatewayKey(document: unknown, actor: Actor, keyId: URL): Promise<PortableGatewayKeyResolution>;
167
+ /**
168
+ * Resolves a key of a portable actor itself, i.e., a key whose ID is an
169
+ * `ap:` or `ap+ef61:` URI.
170
+ */
171
+ actorKey(keyId: URL): Promise<PortableActorKeyResolution>;
133
172
  }
134
173
  /**
135
174
  * The result of {@link fetchKey}.
@@ -182,9 +221,11 @@ type FetchableKeyClass<T extends CryptographicKey | Multikey> = (new (...args: a
182
221
  * Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
183
222
  * compatible identifiers that dereference to portable actor documents, are
184
223
  * not resolved by this function, as they only authenticate HTTP requests;
185
- * use `verifyRequest()` or `getKeyOwner()` for them instead. Keys whose IDs
186
- * are compatible identifiers are never read from or written to the key
187
- * cache under their IDs, except for failures to fetch them.
224
+ * use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
225
+ * of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
226
+ * reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
227
+ * URIs are never read from or written to the key cache under their IDs,
228
+ * except for failures to fetch them.
188
229
  *
189
230
  * [FEP-ef61]: https://w3id.org/fep/ef61
190
231
  * @template T The type of the key to fetch. Either {@link CryptographicKey}
@@ -204,9 +245,11 @@ declare function fetchKey<T extends CryptographicKey | Multikey>(keyId: URL | st
204
245
  * Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
205
246
  * compatible identifiers that dereference to portable actor documents, are
206
247
  * not resolved by this function, as they only authenticate HTTP requests;
207
- * use `verifyRequest()` or `getKeyOwner()` for them instead. Keys whose IDs
208
- * are compatible identifiers are never read from or written to the key
209
- * cache under their IDs, except for failures to fetch them.
248
+ * use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
249
+ * of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
250
+ * reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
251
+ * URIs are never read from or written to the key cache under their IDs,
252
+ * except for failures to fetch them.
210
253
  *
211
254
  * [FEP-ef61]: https://w3id.org/fep/ef61
212
255
  *
@@ -1,11 +1,12 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Be9xoxps.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DDE9S63Q.mjs";
4
+ import { n as version, t as name } from "./deno-BXI_BLNy.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DxlVXwn5.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-Cc1b6pW9.mjs";
8
- import { n as verifyPortableGatewayKeyDocument } from "./portable-key-C-fPq_Yf.mjs";
7
+ import { f as parseKeyIdString } from "./portable-key-id-Bg259c4Z.mjs";
8
+ import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-C0giKuki.mjs";
9
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-BXkgMpw1.mjs";
9
10
  import { getLogger } from "@logtape/logtape";
10
11
  import { CryptographicKey } from "@fedify/vocab";
11
12
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -383,13 +384,18 @@ function keyFetchErrorResult(keyId, result) {
383
384
  }
384
385
  };
385
386
  }
387
+ /**
388
+ * Creates the resolvers of keys of FEP-ef61 portable actors, which HTTP
389
+ * Signature verification alone passes to key lookups.
390
+ */
391
+ function createPortableKeyResolvers(options) {
392
+ return {
393
+ gatewayKey: (document, actor, keyId) => verifyPortableGatewayKeyDocument(document, actor, keyId, options),
394
+ actorKey: (keyId) => resolvePortableActorKey(keyId, options)
395
+ };
396
+ }
386
397
  function parseKeyId(value) {
387
- if (value == null) return null;
388
- try {
389
- return new URL(value);
390
- } catch {
391
- return null;
392
- }
398
+ return value == null ? null : parseKeyIdString(value);
393
399
  }
394
400
  function getKeyFetchErrorName(error) {
395
401
  return error.name || error.constructor.name || "Error";
@@ -647,7 +653,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
647
653
  keyCache,
648
654
  tracerProvider,
649
655
  meterProvider,
650
- portableGatewayKeyResolver: (document, actor, keyId) => verifyPortableGatewayKeyDocument(document, actor, keyId, {
656
+ portableKeyResolvers: createPortableKeyResolvers({
651
657
  documentLoader,
652
658
  contextLoader,
653
659
  keyCache,
@@ -867,7 +873,7 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
867
873
  keyCache,
868
874
  tracerProvider,
869
875
  meterProvider,
870
- portableGatewayKeyResolver: (document, actor, gatewayKeyId) => verifyPortableGatewayKeyDocument(document, actor, gatewayKeyId, {
876
+ portableKeyResolvers: createPortableKeyResolvers({
871
877
  documentLoader,
872
878
  contextLoader,
873
879
  keyCache,
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
3
- import { MeterProvider, TracerProvider } from "@opentelemetry/api";
4
3
  import { DocumentLoader } from "@fedify/vocab-runtime";
4
+ import { MeterProvider, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/sig/portable-key.d.ts
7
7
  /**
@@ -29,6 +29,28 @@ type PortableGatewayKeyResolution = {
29
29
  */
30
30
  readonly expires?: Temporal.Instant;
31
31
  };
32
+ /**
33
+ * The result of resolving a key of a portable actor at an `ap:` or
34
+ * `ap+ef61:` key ID.
35
+ * @internal
36
+ */
37
+ type PortableActorKeyResolution = Exclude<PortableGatewayKeyResolution, {
38
+ readonly type: "legacy";
39
+ }> | {
40
+ /**
41
+ * Whether the actor's document vouches for the key could not be
42
+ * determined, e.g., because no gateway could be reached.
43
+ */
44
+ readonly type: "unavailable";
45
+ readonly error: unknown;
46
+ /**
47
+ * Whether the failure can be cached as a failure to fetch the key, i.e.,
48
+ * every gateway failed to serve the actor's document at all. A failure
49
+ * that is not cacheable comes with an error that has no HTTP status,
50
+ * since none of the statuses describes the lookup as a whole.
51
+ */
52
+ readonly cacheable: boolean;
53
+ };
32
54
  //#endregion
33
55
  //#region src/sig/key.d.ts
34
56
  /**
@@ -119,17 +141,34 @@ interface FetchKeyOptions {
119
141
  */
120
142
  keyIdBoundByCaller?: boolean;
121
143
  /**
122
- * Resolves a gateway key of an [FEP-ef61] portable actor, i.e., a key whose
123
- * ID is a compatible identifier and dereferences to a portable actor's
124
- * document. Such a key is resolved only through this function, and is
125
- * rejected if it is omitted, so that only HTTP Signature verification,
126
- * which passes it, accepts gateway keys. Object Integrity Proofs and
127
- * Linked Data Signatures never do.
144
+ * Resolves keys of [FEP-ef61] portable actors, which only HTTP Signature
145
+ * verification passes, so that only it accepts them. Object Integrity
146
+ * Proofs and Linked Data Signatures never do: such a key is rejected if
147
+ * this is omitted.
128
148
  *
129
149
  * [FEP-ef61]: https://w3id.org/fep/ef61
130
150
  * @internal
131
151
  */
132
- portableGatewayKeyResolver?: (document: unknown, actor: Actor, keyId: URL) => Promise<PortableGatewayKeyResolution>;
152
+ portableKeyResolvers?: PortableKeyResolvers;
153
+ }
154
+ /**
155
+ * Resolves keys of [FEP-ef61] portable actors for HTTP Signature
156
+ * verification. See {@link FetchKeyOptions.portableKeyResolvers}.
157
+ *
158
+ * [FEP-ef61]: https://w3id.org/fep/ef61
159
+ * @internal
160
+ */
161
+ interface PortableKeyResolvers {
162
+ /**
163
+ * Resolves a gateway key, i.e., a key whose ID is a compatible identifier
164
+ * and dereferences to a portable actor's document.
165
+ */
166
+ gatewayKey(document: unknown, actor: Actor, keyId: URL): Promise<PortableGatewayKeyResolution>;
167
+ /**
168
+ * Resolves a key of a portable actor itself, i.e., a key whose ID is an
169
+ * `ap:` or `ap+ef61:` URI.
170
+ */
171
+ actorKey(keyId: URL): Promise<PortableActorKeyResolution>;
133
172
  }
134
173
  /**
135
174
  * The result of {@link fetchKey}.
@@ -182,9 +221,11 @@ type FetchableKeyClass<T extends CryptographicKey | Multikey> = (new (...args: a
182
221
  * Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
183
222
  * compatible identifiers that dereference to portable actor documents, are
184
223
  * not resolved by this function, as they only authenticate HTTP requests;
185
- * use `verifyRequest()` or `getKeyOwner()` for them instead. Keys whose IDs
186
- * are compatible identifiers are never read from or written to the key
187
- * cache under their IDs, except for failures to fetch them.
224
+ * use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
225
+ * of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
226
+ * reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
227
+ * URIs are never read from or written to the key cache under their IDs,
228
+ * except for failures to fetch them.
188
229
  *
189
230
  * [FEP-ef61]: https://w3id.org/fep/ef61
190
231
  * @template T The type of the key to fetch. Either {@link CryptographicKey}
@@ -204,9 +245,11 @@ declare function fetchKey<T extends CryptographicKey | Multikey>(keyId: URL | st
204
245
  * Gateway keys of [FEP-ef61] portable actors, i.e., keys whose IDs are
205
246
  * compatible identifiers that dereference to portable actor documents, are
206
247
  * not resolved by this function, as they only authenticate HTTP requests;
207
- * use `verifyRequest()` or `getKeyOwner()` for them instead. Keys whose IDs
208
- * are compatible identifiers are never read from or written to the key
209
- * cache under their IDs, except for failures to fetch them.
248
+ * use `verifyRequest()` or `getKeyOwner()` for them instead. Nor are keys
249
+ * of portable actors whose IDs are `ap:` or `ap+ef61:` URIs, for the same
250
+ * reason. Keys whose IDs are compatible identifiers or `ap:` or `ap+ef61:`
251
+ * URIs are never read from or written to the key cache under their IDs,
252
+ * except for failures to fetch them.
210
253
  *
211
254
  * [FEP-ef61]: https://w3id.org/fep/ef61
212
255
  *