From 262a1608c73a718c9a639f971b6032784148eee9 Mon Sep 17 00:00:00 2001 From: junseok Date: Sat, 1 Aug 2026 16:33:36 +0900 Subject: [PATCH] Reject mismatched standalone key IDs Keep fetched key identities aligned with their requested URLs and cache entries. Add regression coverage for both CryptographicKey and Multikey rejection and negative caching while preserving actor lookup behavior. Fixes https://github.com/fedify-dev/fedify/issues/963 Assisted-by: Codex:gpt-5 --- CHANGES.md | 8 ++++ changes.d/fedify/mismatched-key-id.md | 5 +++ packages/fedify/src/sig/key.test.ts | 59 +++++++++++++++++++++++++++ packages/fedify/src/sig/key.ts | 5 ++- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 changes.d/fedify/mismatched-key-id.md 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