[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:
parent
092462d3a9
commit
0c9c04f89d
3 changed files with 41 additions and 4 deletions
|
@ -95,11 +95,25 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
const httpSignatureValidated = httpSignature.verifySignature(
|
let httpSignatureValidated = httpSignature.verifySignature(
|
||||||
signature,
|
signature,
|
||||||
authUser.key.keyPem,
|
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と一致する必要がある
|
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||||
|
|
|
@ -87,11 +87,25 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
const httpSignatureValidated = httpSignature.verifySignature(
|
let httpSignatureValidated = httpSignature.verifySignature(
|
||||||
signature,
|
signature,
|
||||||
authUser.key.keyPem,
|
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) {
|
if (!httpSignatureValidated) {
|
||||||
return 403;
|
return 403;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { Cache } from "@/misc/cache.js";
|
||||||
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
|
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
|
||||||
import type { IObject } from "./type.js";
|
import type { IObject } from "./type.js";
|
||||||
import { getApId } 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";
|
import {redisClient, subscriber} from "@/db/redis.js";
|
||||||
|
|
||||||
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
||||||
|
@ -152,7 +152,7 @@ export default class DbResolver {
|
||||||
*/
|
*/
|
||||||
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
||||||
user: CacheableRemoteUser;
|
user: CacheableRemoteUser;
|
||||||
key: UserPublickey;
|
key: UserPublickey | null;
|
||||||
} | null> {
|
} | null> {
|
||||||
const key = await publicKeyCache.fetch(
|
const key = await publicKeyCache.fetch(
|
||||||
keyId,
|
keyId,
|
||||||
|
@ -204,6 +204,15 @@ export default class DbResolver {
|
||||||
key,
|
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) => {
|
subscriber.on("message", async (_, data) => {
|
||||||
|
|
Loading…
Reference in a new issue