Initial working state: CL3.5 complete (Plesk sync + suspend/unsuspend)
This commit is contained in:
116
lib/crypto.ts
Normal file
116
lib/crypto.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
createCipheriv,
|
||||
createDecipheriv,
|
||||
createHash,
|
||||
randomBytes,
|
||||
} from "crypto";
|
||||
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const ALGO = "aes-256-gcm";
|
||||
|
||||
function deriveKey(secret: string) {
|
||||
return createHash("sha256").update(secret).digest();
|
||||
}
|
||||
|
||||
export function encryptSecret(value: string) {
|
||||
if (!env.encryptionKey) {
|
||||
throw new Error("ENCRYPTION_KEY is not configured");
|
||||
}
|
||||
|
||||
const iv = randomBytes(12);
|
||||
const key = deriveKey(env.encryptionKey);
|
||||
const cipher = createCipheriv(ALGO, key, iv);
|
||||
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(value, "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return `${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`;
|
||||
}
|
||||
|
||||
export function decryptSecret(payload: string) {
|
||||
if (!env.encryptionKey) {
|
||||
throw new Error("ENCRYPTION_KEY is not configured");
|
||||
}
|
||||
|
||||
const [ivEncoded, tagEncoded, valueEncoded] = payload.split(":");
|
||||
if (!ivEncoded || !tagEncoded || !valueEncoded) {
|
||||
throw new Error("Invalid encrypted payload");
|
||||
}
|
||||
|
||||
const key = deriveKey(env.encryptionKey);
|
||||
const decipher = createDecipheriv(
|
||||
ALGO,
|
||||
key,
|
||||
Buffer.from(ivEncoded, "base64"),
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tagEncoded, "base64"));
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(Buffer.from(valueEncoded, "base64")),
|
||||
decipher.final(),
|
||||
]);
|
||||
|
||||
return decrypted.toString("utf8");
|
||||
}
|
||||
|
||||
function derivePleskKey() {
|
||||
if (!env.pleskCredEncKey) {
|
||||
throw new Error("PLESK_CRED_ENC_KEY is not configured");
|
||||
}
|
||||
|
||||
const raw = env.pleskCredEncKey.trim();
|
||||
|
||||
if (/^[0-9a-fA-F]{64}$/.test(raw)) {
|
||||
return Buffer.from(raw, "hex");
|
||||
}
|
||||
|
||||
const b64 = Buffer.from(raw, "base64");
|
||||
if (b64.length === 32) {
|
||||
return b64;
|
||||
}
|
||||
|
||||
throw new Error("PLESK_CRED_ENC_KEY must be 32-byte base64 or 64-char hex");
|
||||
}
|
||||
|
||||
export function encryptPleskSecret(value: string, keyId = "v1") {
|
||||
const iv = randomBytes(12);
|
||||
const key = derivePleskKey();
|
||||
const cipher = createCipheriv(ALGO, key, iv);
|
||||
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(value, "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return {
|
||||
encryptedSecret: `${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`,
|
||||
secretKid: keyId,
|
||||
};
|
||||
}
|
||||
|
||||
export function decryptPleskSecret(payload: string) {
|
||||
const [ivEncoded, tagEncoded, valueEncoded] = payload.split(":");
|
||||
if (!ivEncoded || !tagEncoded || !valueEncoded) {
|
||||
throw new Error("Invalid encrypted payload");
|
||||
}
|
||||
|
||||
const key = derivePleskKey();
|
||||
const decipher = createDecipheriv(
|
||||
ALGO,
|
||||
key,
|
||||
Buffer.from(ivEncoded, "base64"),
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tagEncoded, "base64"));
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(Buffer.from(valueEncoded, "base64")),
|
||||
decipher.final(),
|
||||
]);
|
||||
|
||||
return decrypted.toString("utf8");
|
||||
}
|
||||
Reference in New Issue
Block a user