import crypto, { createVerify } from "crypto"; import { Request, Response, NextFunction } from "express"; import { getActor } from "../federate"; import { IncomingHttpHeaders } from "http"; import { Database } from "sqlite3"; export = async (req: Request, res: Response, next: NextFunction) => { if (req.method !== "POST") { next(); return; } const db = req.app.get("db") as Database; const actor = await getActor(req.body.actor as string, db); if (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, db, true); 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; }