import crypto, { createVerify } from "crypto"; import { Request, Response, NextFunction } from "express"; import { fetchActor } from "../federate"; import { IncomingHttpHeaders } from "http"; export = async (req: Request, res: Response, next: NextFunction) => { if (req.method !== "POST") { next(); return; } const actor = await fetchActor(req.body.actor as string); if (validate(req, actor.publicKey.publicKeyPem)) { next(); } else { console.log(`Could not validate HTTP signature for ${req.body.actor}`); res.status(401).end("Could not validate HTTP signature"); } }; 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; }