import crypto from "crypto"; import { Request, Response, NextFunction } from "express"; import { getActor } from "../federate"; export = async (req: Request, res: Response, next: NextFunction) => { if (req.method !== "POST") { next(); return; } const actor = await getActor(req.body.actor as string); if (actor && actor.publicKey && validate(req, actor.publicKey.publicKeyPem)) { next(); } else { // if the first check fails, force re-fetch the actor and try again const actor = await getActor(req.body.actor as string, true); if (!actor) { // probably caused by Delete activity for an actor if (req.body.type === "Delete") { // if we don't have a cached copy of the key, we have had no interaction with actor // so the Delete can be safely ignored // we still send a 200 OK status, so that the originating instances knows it has successfully // delivered the Delete (we just can't act on it) res.status(200).end(); } else { console.log(`Could not retrieve actor ${req.body.actor} to validate HTTP signature for`, req.body); res.status(401).end("Could not retrieve actor to validate HTTP signature"); } } else if (!validate(req, actor.publicKey.publicKeyPem)) { console.log(`Could not validate HTTP signature for ${req.body.actor}`); res.status(401).end("Could not validate HTTP signature"); } else { next(); } } }; function validate(req: Request, publicKeyPem: string): boolean { const signature = parseSignature(req.header("signature")!); const usedHeaders = signature.get("headers")!.split(/\s/); const signingString = usedHeaders.map(header => { const value = header === "(request-target)" ? `${req.method} ${req.path}`.toLowerCase() : req.header(header); return `${header}: ${value}`; }).join("\n"); const verifier = crypto.createVerify("sha256"); verifier.update(signingString); verifier.end(); return verifier.verify(publicKeyPem, signature.get("signature")!, "base64"); } function parseSignature(signature: string): Map { const map = new Map(); map.set("headers", "date"); for (const part of signature.split(",")) { const index = part.indexOf("="); const key = part.substring(0, index); const value = part.substring(index + 1); const unquoted = value.replace(/^"+|"+$/g, ""); // strip leading and trailing quotes map.set(key, unquoted); } return map; }