const crypto = require("node:crypto"); function toBase64Url(buffer) { return Buffer.from(buffer).toString("base64url"); } function fromBase64Url(value) { return Buffer.from(value, "base64url"); } function deriveMasterKey(encryptionKey) { return crypto.scryptSync(encryptionKey, "hf-home-master-key", 32); } function deriveTokenKey(masterKey, sessionSecret, sessionId) { return crypto.hkdfSync( "sha256", Buffer.from(sessionSecret, "utf8"), masterKey, Buffer.from(`hf-home:${sessionId}`, "utf8"), 32, ); } function encryptText(plainText, key, aad = "") { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv("aes-256-gcm", key, iv); if (aad) { cipher.setAAD(Buffer.from(aad, "utf8")); } const ciphertext = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]); const tag = cipher.getAuthTag(); return `${toBase64Url(iv)}.${toBase64Url(ciphertext)}.${toBase64Url(tag)}`; } function decryptText(payload, key, aad = "") { const [ivPart, ciphertextPart, tagPart] = String(payload).split("."); if (!ivPart || !ciphertextPart || !tagPart) { throw new Error("Invalid encrypted payload."); } const decipher = crypto.createDecipheriv("aes-256-gcm", key, fromBase64Url(ivPart)); if (aad) { decipher.setAAD(Buffer.from(aad, "utf8")); } decipher.setAuthTag(fromBase64Url(tagPart)); const plaintext = Buffer.concat([ decipher.update(fromBase64Url(ciphertextPart)), decipher.final(), ]); return plaintext.toString("utf8"); } function encryptJson(value, key, aad = "") { return encryptText(JSON.stringify(value), key, aad); } function decryptJson(payload, key, aad = "") { return JSON.parse(decryptText(payload, key, aad)); } function hashSessionSecret(secret, salt = crypto.randomBytes(16)) { const hash = crypto.scryptSync(secret, salt, 64); return { salt: toBase64Url(salt), hash: toBase64Url(hash), }; } function verifySessionSecret(secret, expectedHash, salt) { const actual = crypto.scryptSync(secret, fromBase64Url(salt), 64); const expected = fromBase64Url(expectedHash); if (actual.length !== expected.length) { return false; } return crypto.timingSafeEqual(actual, expected); } function generateToken(size = 32) { return toBase64Url(crypto.randomBytes(size)); } function createCsrfToken(masterKey, sessionId, sessionSecret) { return crypto .createHmac("sha256", masterKey) .update(`csrf:${sessionId}:${sessionSecret}`, "utf8") .digest("base64url"); } module.exports = { createCsrfToken, decryptJson, decryptText, deriveMasterKey, deriveTokenKey, encryptJson, encryptText, generateToken, hashSessionSecret, verifySessionSecret, };