iceshrimp-legacy/packages/backend/src/remote/activitypub/misc/ld-signature.ts

142 lines
3.5 KiB
TypeScript
Raw Normal View History

2023-01-13 05:40:33 +01:00
import * as crypto from "node:crypto";
import jsonld from "jsonld";
import { CONTEXTS } from "./contexts.js";
import fetch from "node-fetch";
import { httpAgent, httpsAgent } from "@/misc/fetch.js";
// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
export class LdSignature {
public debug = false;
public preLoad = true;
public loderTimeout = 10 * 1000;
2023-01-13 05:40:33 +01:00
public async signRsaSignature2017(
data: any,
privateKey: string,
creator: string,
domain?: string,
created?: Date,
): Promise<any> {
const options = {
2023-01-13 05:40:33 +01:00
type: "RsaSignature2017",
creator,
domain,
2023-01-13 05:40:33 +01:00
nonce: crypto.randomBytes(16).toString("hex"),
2021-12-09 15:58:30 +01:00
created: (created || new Date()).toISOString(),
} as {
type: string;
creator: string;
2022-02-03 13:43:53 +01:00
domain?: string;
nonce: string;
created: string;
};
if (!domain) {
2023-01-13 05:40:33 +01:00
options.domain = undefined;
}
const toBeSigned = await this.createVerifyData(data, options);
2023-01-13 05:40:33 +01:00
const signer = crypto.createSign("sha256");
signer.update(toBeSigned);
signer.end();
const signature = signer.sign(privateKey);
return {
...data,
signature: {
...options,
2023-01-13 05:40:33 +01:00
signatureValue: signature.toString("base64"),
2021-12-09 15:58:30 +01:00
},
};
}
2023-01-13 05:40:33 +01:00
public async verifyRsaSignature2017(
data: any,
publicKey: string,
): Promise<boolean> {
const toBeSigned = await this.createVerifyData(data, data.signature);
2023-01-13 05:40:33 +01:00
const verifier = crypto.createVerify("sha256");
verifier.update(toBeSigned);
2023-01-13 05:40:33 +01:00
return verifier.verify(publicKey, data.signature.signatureValue, "base64");
}
public async createVerifyData(data: any, options: any) {
const transformedOptions = {
...options,
2023-01-13 05:40:33 +01:00
"@context": "https://w3id.org/identity/v1",
};
2023-06-06 01:40:48 +02:00
transformedOptions["type"] = undefined;
transformedOptions["id"] = undefined;
transformedOptions["signatureValue"] = undefined;
const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data };
2023-06-06 01:40:48 +02:00
transformedData["signature"] = undefined;
const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(cannonidedData);
const verifyData = `${optionsHash}${documentHash}`;
return verifyData;
}
public async normalize(data: any) {
const customLoader = this.getLoader();
return await jsonld.normalize(data, {
2021-12-09 15:58:30 +01:00
documentLoader: customLoader,
});
}
private getLoader() {
return async (url: string): Promise<any> => {
2023-01-13 05:40:33 +01:00
if (!url.match("^https?://")) throw new Error(`Invalid URL ${url}`);
if (this.preLoad) {
if (url in CONTEXTS) {
if (this.debug) console.debug(`HIT: ${url}`);
return {
contextUrl: null,
document: CONTEXTS[url],
2021-12-09 15:58:30 +01:00
documentUrl: url,
};
}
}
if (this.debug) console.debug(`MISS: ${url}`);
const document = await this.fetchDocument(url);
return {
contextUrl: null,
document: document,
2021-12-09 15:58:30 +01:00
documentUrl: url,
};
};
}
private async fetchDocument(url: string) {
const json = await fetch(url, {
headers: {
2023-01-13 05:40:33 +01:00
Accept: "application/ld+json, application/json",
},
2022-04-03 09:30:22 +02:00
// TODO
//timeout: this.loderTimeout,
2023-01-13 05:40:33 +01:00
agent: (u) => (u.protocol === "http:" ? httpAgent : httpsAgent),
}).then((res) => {
if (!res.ok) {
2022-08-04 11:00:02 +02:00
throw new Error(`${res.status} ${res.statusText}`);
} else {
return res.json();
}
});
return json;
}
public sha256(data: string): string {
2023-01-13 05:40:33 +01:00
const hash = crypto.createHash("sha256");
hash.update(data);
2023-01-13 05:40:33 +01:00
return hash.digest("hex");
}
}