117 lines
2.8 KiB
TypeScript
117 lines
2.8 KiB
TypeScript
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");
|
|
}
|