[backend] Refetch user keys when HTTP Signature validation fails

If a user has had a key rotation, and nobody on this server follows
that user, we will not receive the Update activity with the new key

Therefore, when we encounter key validation errors we should check
for an up-to-date key.

References (other implementations):

 * [Mastodon](fc9ab61448/app/controllers/concerns/signature_verification.rb (L96))
 * [Akkoma](https://akkoma.dev/AkkomaGang/http_signatures/src/branch/main/lib/http_signatures/http_signatures.ex#L46)
This commit is contained in:
Erin Shepherd 2023-10-16 23:37:09 +02:00 committed by Laura Hausmann
parent 092462d3a9
commit 0c9c04f89d
Signed by: zotan
GPG key ID: D044E84C5BE01605
3 changed files with 41 additions and 4 deletions

View file

@ -95,11 +95,25 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
}
// HTTP-Signatureの検証
const httpSignatureValidated = httpSignature.verifySignature(
let httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
// If signature validation failed, try refetching the actor
if (!httpSignatureValidated) {
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
if (authUser.key == null) {
return "skip: failed to re-resolve user publicKey";
}
httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
}
// また、signatureのsignerは、activity.actorと一致する必要がある
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る

View file

@ -87,11 +87,25 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
}
// HTTP-Signatureの検証
const httpSignatureValidated = httpSignature.verifySignature(
let httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
// If signature validation failed, try refetching the actor
if (!httpSignatureValidated) {
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
if (authUser.key == null) {
return 403;
}
httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
}
if (!httpSignatureValidated) {
return 403;
}

View file

@ -17,7 +17,7 @@ import { Cache } from "@/misc/cache.js";
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
import type { IObject } from "./type.js";
import { getApId } from "./type.js";
import { resolvePerson } from "./models/person.js";
import { resolvePerson, updatePerson } from "./models/person.js";
import {redisClient, subscriber} from "@/db/redis.js";
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
@ -152,7 +152,7 @@ export default class DbResolver {
*/
public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey;
key: UserPublickey | null;
} | null> {
const key = await publicKeyCache.fetch(
keyId,
@ -204,6 +204,15 @@ export default class DbResolver {
key,
};
}
public async refetchPublicKeyForApId(user: CacheableRemoteUser): Promise<UserPublickey | null> {
await updatePerson(user.uri!, undefined, undefined, user);
let key = await UserPublickeys.findOneBy({ userId: user.id });
if (key != null) {
await publicKeyByUserIdCache.set(user.id, key);
}
return key;
}
}
subscriber.on("message", async (_, data) => {