diff --git a/CHANGES.md b/CHANGES.md index a22ea5651..132084f83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,14 @@ Version 2.0.25 To be released. +### @fedify/fedify + + - Standalone key documents whose `id` differs from the requested key URL are + now rejected instead of being cached under the wrong URL. [[#963]] by + ojspp41 + +[#963]: https://github.com/fedify-dev/fedify/issues/963 + Version 2.0.24 -------------- diff --git a/changes.d/fedify/mismatched-key-id.md b/changes.d/fedify/mismatched-key-id.md new file mode 100644 index 000000000..63d532e9d --- /dev/null +++ b/changes.d/fedify/mismatched-key-id.md @@ -0,0 +1,5 @@ + - Standalone key documents whose `id` differs from the requested key URL are + now rejected instead of being cached under the wrong URL. [[#963]] by + ojspp41 + +[#963]: https://github.com/fedify-dev/fedify/issues/963 diff --git a/packages/fedify/src/sig/key.test.ts b/packages/fedify/src/sig/key.test.ts index 09c002bb8..de0f6d934 100644 --- a/packages/fedify/src/sig/key.test.ts +++ b/packages/fedify/src/sig/key.test.ts @@ -352,6 +352,65 @@ test("fetchKey()", async () => { ); }); +test("fetchKey() rejects standalone keys with a mismatched id", async () => { + for ( + const { keyId, standaloneKey, fetch } of [ + { + keyId: "https://example.com/key", + standaloneKey: rsaPublicKey1, + fetch: (keyId: string, options: FetchKeyOptions) => + fetchKey(keyId, CryptographicKey, options), + }, + { + keyId: "https://example.com/multikey", + standaloneKey: ed25519Multikey, + fetch: (keyId: string, options: FetchKeyOptions) => + fetchKey(keyId, Multikey, options), + }, + ] + ) { + const cache: Record = {}; + const options: FetchKeyOptions = { + async documentLoader(resource) { + if (resource === keyId) { + const document = await standaloneKey.toJsonLd({ + contextLoader: mockDocumentLoader, + }); + return { + contextUrl: null, + documentUrl: resource, + document: { + ...document as Record, + id: "https://example.com/different-key", + }, + }; + } + return await mockDocumentLoader(resource); + }, + contextLoader: mockDocumentLoader, + keyCache: { + get(keyId) { + return Promise.resolve(cache[keyId.href]); + }, + set(keyId, key) { + cache[keyId.href] = key; + return Promise.resolve(); + }, + } satisfies KeyCache, + }; + + assertEquals(await fetch(keyId, options), { + key: null, + cached: false, + }); + assertEquals(cache, { [keyId]: null }); + assertEquals(await fetch(keyId, options), { + key: null, + cached: true, + }); + } +}); + test("fetchKey() returns null for a malformed actor publicKey", async () => { const actorId = "https://example.com/malformed-public-key"; const keyId = "https://example.com/malformed-public-key#main-key"; diff --git a/packages/fedify/src/sig/key.ts b/packages/fedify/src/sig/key.ts index 757201f97..1928e145c 100644 --- a/packages/fedify/src/sig/key.ts +++ b/packages/fedify/src/sig/key.ts @@ -323,7 +323,10 @@ async function fetchKeyInternal( } } let key: T | null = null; - if (object instanceof cls) key = object; + if ( + object instanceof cls && + (object.id == null || object.id.href === keyId) + ) key = object; else if (isActor(object)) { // Treat malformed remote actor keys as missing keys. // @ts-ignore: cls is either CryptographicKey or Multikey