diff --git a/config/config.example.toml b/config/config.example.toml index 15c63a1e..60c23b07 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -101,6 +101,10 @@ force_sensitive = [] # NOT IMPLEMENTED # Remove theses instances' media remove_media = [] # NOT IMPLEMENTED +# Whether to verify HTTP signatures for every request (warning: can slow down your server +# significantly depending on processing power) +authorized_fetch = false + [filters] # Drop notes with these regex filters (only applies to new activities) diff --git a/server/api/users/[username]/inbox/index.ts b/server/api/users/[username]/inbox/index.ts index 4678356c..8aa3d3fa 100644 --- a/server/api/users/[username]/inbox/index.ts +++ b/server/api/users/[username]/inbox/index.ts @@ -57,34 +57,49 @@ export default async ( const body: APActivity = await req.json(); // Verify HTTP signature - const signature = req.headers.get("Signature") ?? ""; - const signatureParams = signature - .split(",") - .reduce>((params, param) => { - const [key, value] = param.split("="); - params[key] = value.replace(/"/g, ""); - return params; - }, {}); + if (config.activitypub.authorized_fetch) { + // Check if date is older than 30 seconds + const date = new Date(req.headers.get("Date") ?? ""); - const signedString = `(request-target): post /users/${username}/inbox\nhost: ${ - config.http.base_url - }\ndate: ${req.headers.get("Date")}`; - const signatureBuffer = new TextEncoder().encode(signatureParams.signature); - const signatureBytes = new Uint8Array(signatureBuffer).buffer; - const publicKeyBuffer = (body.actor as any).publicKey.publicKeyPem; - const publicKey = await crypto.subtle.importKey( - "spki", - publicKeyBuffer, - { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, - false, - ["verify"] - ); - const verified = await crypto.subtle.verify( - { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, - publicKey, - signatureBytes, - new TextEncoder().encode(signedString) - ); + if (date.getTime() < Date.now() - 30000) { + return errorResponse("Date is too old (max 30 seconds)", 401); + } + + const signature = req.headers.get("Signature") ?? ""; + const signatureParams = signature + .split(",") + .reduce>((params, param) => { + const [key, value] = param.split("="); + params[key] = value.replace(/"/g, ""); + return params; + }, {}); + + const signedString = `(request-target): post /users/${username}/inbox\nhost: ${ + config.http.base_url + }\ndate: ${req.headers.get("Date")}`; + const signatureBuffer = new TextEncoder().encode( + signatureParams.signature + ); + const signatureBytes = new Uint8Array(signatureBuffer).buffer; + const publicKeyBuffer = (body.actor as any).publicKey.publicKeyPem; + const publicKey = await crypto.subtle.importKey( + "spki", + publicKeyBuffer, + { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + false, + ["verify"] + ); + const verified = await crypto.subtle.verify( + { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, + publicKey, + signatureBytes, + new TextEncoder().encode(signedString) + ); + + if (!verified) { + return errorResponse("Invalid signature", 401); + } + } // Get the object's ActivityPub type const type = body.type; diff --git a/utils/config.ts b/utils/config.ts index 6949bc99..8ab1d030 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -55,6 +55,7 @@ export interface ConfigType { force_sensitive: string[]; remove_media: string[]; fetch_all_colletion_members: boolean; + authorized_fetch: boolean; }; filters: { @@ -184,6 +185,7 @@ export const configDefaults: ConfigType = { discard_follows: [], remove_media: [], fetch_all_colletion_members: false, + authorized_fetch: false, }, filters: { note_filters: [],