chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const keyPath =
|
||||
process.env.NODE_ENV === "development"
|
||||
? path.resolve(__dirname, `../../storage/comkey`)
|
||||
: path.resolve(
|
||||
process.env.STORAGE_DIR ?? path.resolve(__dirname, `../../storage`),
|
||||
`comkey`
|
||||
);
|
||||
|
||||
// What does this class do?
|
||||
// This class generates a hashed version of some text (typically a JSON payload) using a rolling RSA key
|
||||
// that can then be appended as a header value to do integrity checking on a payload. Given the
|
||||
// nature of this class and that keys are rolled constantly, this protects the request
|
||||
// integrity of requests sent to the collector as only the server can sign these requests.
|
||||
// This keeps accidental misconfigurations of AnythingLLM that leave the collector port open from
|
||||
// being abused or SSRF'd by users scraping malicious sites who have a loopback embedded in a <script>, for example.
|
||||
// Since each request to the collector must be signed to be valid, unsigned requests directly to the collector
|
||||
// will be dropped and must go through the /server endpoint directly.
|
||||
class CommunicationKey {
|
||||
#privKeyName = "ipc-priv.pem";
|
||||
#pubKeyName = "ipc-pub.pem";
|
||||
#storageLoc = keyPath;
|
||||
|
||||
// Init the class and determine if keys should be rolled.
|
||||
// This typically occurs on boot up so key is fresh each boot.
|
||||
constructor(generate = false) {
|
||||
if (generate) this.#generate();
|
||||
}
|
||||
|
||||
log(text, ...args) {
|
||||
console.log(`\x1b[36m[CommunicationKey]\x1b[0m ${text}`, ...args);
|
||||
}
|
||||
|
||||
#readPrivateKey() {
|
||||
return fs.readFileSync(path.resolve(this.#storageLoc, this.#privKeyName));
|
||||
}
|
||||
|
||||
#generate() {
|
||||
const keyPair = crypto.generateKeyPairSync("rsa", {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: "pkcs1",
|
||||
format: "pem",
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: "pkcs1",
|
||||
format: "pem",
|
||||
},
|
||||
});
|
||||
|
||||
if (!fs.existsSync(this.#storageLoc))
|
||||
fs.mkdirSync(this.#storageLoc, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
`${path.resolve(this.#storageLoc, this.#privKeyName)}`,
|
||||
keyPair.privateKey
|
||||
);
|
||||
fs.writeFileSync(
|
||||
`${path.resolve(this.#storageLoc, this.#pubKeyName)}`,
|
||||
keyPair.publicKey
|
||||
);
|
||||
this.log(
|
||||
"RSA key pair generated for signed payloads within AnythingLLM services."
|
||||
);
|
||||
}
|
||||
|
||||
// This instance of ComKey on server is intended for generation of Priv/Pub key for signing and decoding.
|
||||
// this resource is shared with /collector/ via a class of the same name in /utils which does decoding/verification only
|
||||
// while this server class only does signing with the private key.
|
||||
sign(textData = "") {
|
||||
return crypto
|
||||
.sign("RSA-SHA256", Buffer.from(textData), this.#readPrivateKey())
|
||||
.toString("hex");
|
||||
}
|
||||
|
||||
// Use the rolling priv-key to encrypt arbitrary data that is text
|
||||
// returns the encrypted content as a base64 string.
|
||||
encrypt(textData = "") {
|
||||
return crypto
|
||||
.privateEncrypt(this.#readPrivateKey(), Buffer.from(textData, "utf-8"))
|
||||
.toString("base64");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { CommunicationKey };
|
||||
Reference in New Issue
Block a user