shadowfacts.net/lib/activitypub/middleware/http-signature.ts

52 lines
1.8 KiB
TypeScript
Raw Normal View History

2019-02-20 23:07:29 +00:00
import crypto, { createVerify } from "crypto";
import { Request, Response, NextFunction } from "express";
2019-02-24 15:21:14 +00:00
import { getActor } from "../federate";
2019-02-20 23:07:29 +00:00
import { IncomingHttpHeaders } from "http";
2019-02-24 15:21:14 +00:00
import { Database } from "sqlite3";
2019-02-20 23:07:29 +00:00
export = async (req: Request, res: Response, next: NextFunction) => {
if (req.method !== "POST") {
next();
return;
}
2019-02-24 15:21:14 +00:00
const db = req.app.get("db") as Database;
const actor = await getActor(req.body.actor as string, db);
2019-02-20 23:07:29 +00:00
if (validate(req, actor.publicKey.publicKeyPem)) {
next();
} else {
2019-02-24 15:21:14 +00:00
// 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");
}
2019-02-20 23:07:29 +00:00
}
};
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<string, string> {
const map = new Map<string, string>();
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;
}