feat: accept more signature algorithms like ECDSA/Ed25519
cf. 3272b908c6
Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
parent
99c592f4dd
commit
cf5b42a160
|
@ -10,8 +10,6 @@ import type { IncomingMessage } from "http";
|
||||||
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
||||||
import type { UserPublickey } from "@/models/entities/user-publickey.js";
|
import type { UserPublickey } from "@/models/entities/user-publickey.js";
|
||||||
import { verify } from "node:crypto";
|
import { verify } from "node:crypto";
|
||||||
import { toSingle } from "@/prelude/array.js";
|
|
||||||
import { createHash } from "node:crypto";
|
|
||||||
|
|
||||||
export async function hasSignature(req: IncomingMessage): Promise<string> {
|
export async function hasSignature(req: IncomingMessage): Promise<string> {
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
|
@ -158,20 +156,3 @@ export function verifySignature(
|
||||||
return false;
|
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { getUserKeypair } from "@/misc/keypair-store.js";
|
||||||
import {
|
import {
|
||||||
checkFetch,
|
checkFetch,
|
||||||
getSignatureUser,
|
getSignatureUser,
|
||||||
verifyDigest,
|
|
||||||
} from "@/remote/activitypub/check-fetch.js";
|
} from "@/remote/activitypub/check-fetch.js";
|
||||||
import { getInstanceActor } from "@/services/instance-actor.js";
|
import { getInstanceActor } from "@/services/instance-actor.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.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 { serverLogger } from "./index.js";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import Koa from "koa";
|
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
|
// Init router
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
@ -43,27 +45,94 @@ const router = new Router();
|
||||||
|
|
||||||
function inbox(ctx: Router.RouterContext) {
|
function inbox(ctx: Router.RouterContext) {
|
||||||
if (ctx.req.headers.host !== config.host) {
|
if (ctx.req.headers.host !== config.host) {
|
||||||
|
serverLogger.warn("inbox: Invalid Host");
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
|
ctx.message = "Invalid Host";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature;
|
let signature: httpSignature.IParsedSignature;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signature = httpSignature.parseRequest(ctx.req, {
|
signature = httpSignature.parseRequest(ctx.req, {
|
||||||
headers: ["(request-target)", "digest", "host", "date"],
|
headers: ["(request-target)", "digest", "host", "date"],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
serverLogger.warn(`inbox: signature parse error: ${inspect(e)}`);
|
||||||
ctx.status = 401;
|
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;
|
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.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;
|
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;
|
ctx.status = 202;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue