feat: accept more signature algorithms like ECDSA/Ed25519

cf. 3272b908c6

Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
MeiMei 2024-02-09 04:25:57 +09:00 committed by naskya
parent 99c592f4dd
commit cf5b42a160
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
2 changed files with 73 additions and 23 deletions

View File

@ -10,8 +10,6 @@ import type { IncomingMessage } from "http";
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
import { verify } from "node:crypto";
import { toSingle } from "@/prelude/array.js";
import { createHash } from "node:crypto";
export async function hasSignature(req: IncomingMessage): Promise<string> {
const meta = await fetchMeta();
@ -158,20 +156,3 @@ export function verifySignature(
return false;
}
}
export function verifyDigest(
body: string,
digest: string | string[] | undefined,
): boolean {
digest = toSingle(digest);
if (
body == null ||
digest == null ||
!digest.toLowerCase().startsWith("sha-256=")
)
return false;
return (
createHash("sha256").update(body).digest("base64") === digest.substring(8)
);
}

View File

@ -23,7 +23,6 @@ import { getUserKeypair } from "@/misc/keypair-store.js";
import {
checkFetch,
getSignatureUser,
verifyDigest,
} from "@/remote/activitypub/check-fetch.js";
import { getInstanceActor } from "@/services/instance-actor.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
@ -35,6 +34,9 @@ import Outbox, { packActivity } from "./activitypub/outbox.js";
import { serverLogger } from "./index.js";
import config from "@/config/index.js";
import Koa from "koa";
import * as crypto from "node:crypto";
import { inspect } from "node:util";
import type { IActivity } from "@/remote/activitypub/type.js";
// Init router
const router = new Router();
@ -43,27 +45,94 @@ const router = new Router();
function inbox(ctx: Router.RouterContext) {
if (ctx.req.headers.host !== config.host) {
serverLogger.warn("inbox: Invalid Host");
ctx.status = 400;
ctx.message = "Invalid Host";
return;
}
let signature;
let signature: httpSignature.IParsedSignature;
try {
signature = httpSignature.parseRequest(ctx.req, {
headers: ["(request-target)", "digest", "host", "date"],
});
} catch (e) {
serverLogger.warn(`inbox: signature parse error: ${inspect(e)}`);
ctx.status = 401;
if (e instanceof Error) {
if (e.name === "ExpiredRequestError")
ctx.message = "Expired Request Error";
if (e.name === "MissingHeaderError")
ctx.message = "Missing Required Header";
}
return;
}
if (!verifyDigest(ctx.request.rawBody, ctx.headers.digest)) {
// Validate signature algorithm
if (
!signature.algorithm
.toLowerCase()
.match(/^((dsa|rsa|ecdsa)-(sha256|sha384|sha512)|ed25519-sha512|hs2019)$/)
) {
serverLogger.warn(
`inbox: invalid signature algorithm ${signature.algorithm}`,
);
ctx.status = 401;
ctx.message = "Invalid Signature Algorithm";
return;
// hs2019
// keyType=ED25519 => ed25519-sha512
// keyType=other => (keyType)-sha256
}
// Validate digest header
const digest = ctx.req.headers.digest;
if (typeof digest !== "string") {
serverLogger.warn(
"inbox: zero or more than one digest header(s) are present",
);
ctx.status = 401;
ctx.message = "Invalid Digest Header";
return;
}
processInbox(ctx.request.body, signature);
const match = digest.match(/^([0-9A-Za-z-]+)=(.+)$/);
if (match == null) {
serverLogger.warn("inbox: unrecognized digest header");
ctx.status = 401;
ctx.message = "Invalid Digest Header";
return;
}
const digestAlgo = match[1];
const expectedDigest = match[2];
if (digestAlgo.toUpperCase() !== "SHA-256") {
serverLogger.warn("inbox: unsupported digest algorithm");
ctx.status = 401;
ctx.message = "Unsupported Digest Algorithm";
return;
}
const actualDigest = crypto
.createHash("sha256")
.update(ctx.request.rawBody)
.digest("base64");
if (expectedDigest !== actualDigest) {
serverLogger.warn("inbox: Digest Mismatch");
ctx.status = 401;
ctx.message = "Digest Missmatch";
return;
}
processInbox(ctx.request.body as IActivity, signature);
ctx.status = 202;
}