rudeshark.net/packages/backend/src/remote/activitypub/ap-request.ts
2022-04-03 15:33:22 +09:00

105 lines
3.0 KiB
TypeScript

import * as crypto from 'node:crypto';
import { URL } from 'node:url';
type Request = {
url: string;
method: string;
headers: Record<string, string>;
};
type PrivateKey = {
privateKeyPem: string;
keyId: string;
};
export function createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }) {
const u = new URL(args.url);
const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
const request: Request = {
url: u.href,
method: 'POST',
headers: objectAssignWithLcKey({
'Date': new Date().toUTCString(),
'Host': u.hostname,
'Content-Type': 'application/activity+json',
'Digest': digestHeader,
}, args.additionalHeaders),
};
const result = signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);
return {
request,
signingString: result.signingString,
signature: result.signature,
signatureHeader: result.signatureHeader,
};
}
export function createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }) {
const u = new URL(args.url);
const request: Request = {
url: u.href,
method: 'GET',
headers: objectAssignWithLcKey({
'Accept': 'application/activity+json, application/ld+json',
'Date': new Date().toUTCString(),
'Host': new URL(args.url).hostname,
}, args.additionalHeaders),
};
const result = signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
return {
request,
signingString: result.signingString,
signature: result.signature,
signatureHeader: result.signatureHeader,
};
}
function signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]) {
const signingString = genSigningString(request, includeHeaders);
const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64');
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
request.headers = objectAssignWithLcKey(request.headers, {
Signature: signatureHeader,
});
return {
request,
signingString,
signature,
signatureHeader,
};
}
function genSigningString(request: Request, includeHeaders: string[]) {
request.headers = lcObjectKey(request.headers);
const results: string[] = [];
for (const key of includeHeaders.map(x => x.toLowerCase())) {
if (key === '(request-target)') {
results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`);
} else {
results.push(`${key}: ${request.headers[key]}`);
}
}
return results.join('\n');
}
function lcObjectKey(src: Record<string, string>) {
const dst: Record<string, string> = {};
for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
return dst;
}
function objectAssignWithLcKey(a: Record<string, string>, b: Record<string, string>) {
return Object.assign(lcObjectKey(a), lcObjectKey(b));
}