import { createCipheriv, createDecipheriv, randomBytes, createHash, timingSafeEqual, } from "node:crypto";
const ALGORITHM = "aes-256-gcm";
const KEY_BYTES = 32;
const IV_BYTES = 12;
const TAG_BYTES = 16;
const TAG_LENGTH = TAG_BYTES * 8;
export class CryptoError extends Error {
    constructor(message) {
        super(message);
        this.name = "CryptoError";
    }
}
/**
 * Chiffrement par enveloppe.
 *
 *  - KEK (Key Encryption Key) : issue de MASTER_KEK (32 octets base64), versionnée.
 *  - DEK (Data Encryption Key) : aléatoire par connexion, wrappée (AES-256-GCM) sous la KEK.
 *  - App password : chiffré (AES-256-GCM) avec la DEK, AAD = family_id
 *    (un blob ne peut pas être rejoué d'une famille sur une autre).
 *
 * Format binaire des blobs : nonce(12) || ciphertext || tag(16).
 *
 * Réutilisé tel quel depuis l'ancien backend — servira à chiffrer les
 * futurs secrets locaux (clé de maintenance persistée, etc.).
 */
export function generateDek() {
    return randomBytes(KEY_BYTES);
}
export function generateKek() {
    return randomBytes(KEY_BYTES);
}
/** Parse MASTER_KEK depuis l'environnement (base64). */
export function kekFromBase64(base64) {
    const buf = Buffer.from(base64, "base64");
    if (buf.length !== KEY_BYTES) {
        throw new CryptoError(`MASTER_KEK invalide : ${buf.length} octets, ${KEY_BYTES} attendus`);
    }
    return buf;
}
/** Wrappe une DEK sous une KEK. */
export function wrapDek(kek, dek) {
    if (kek.length !== KEY_BYTES || dek.length !== KEY_BYTES) {
        throw new CryptoError("wrapDek : KEK et DEK doivent faire 32 octets");
    }
    const nonce = randomBytes(IV_BYTES);
    const cipher = createCipheriv(ALGORITHM, kek, nonce);
    const ciphertext = Buffer.concat([cipher.update(dek), cipher.final()]);
    const tag = cipher.getAuthTag();
    return Buffer.concat([nonce, ciphertext, tag]).toString("base64");
}
/** Dé-wrappe une DEK. */
export function unwrapDek(kek, wrapped) {
    const blob = Buffer.from(wrapped, "base64");
    if (blob.length !== IV_BYTES + KEY_BYTES + TAG_BYTES) {
        throw new CryptoError("unwrapDek : blob de taille invalide");
    }
    const nonce = blob.subarray(0, IV_BYTES);
    const ciphertext = blob.subarray(IV_BYTES, IV_BYTES + KEY_BYTES);
    const tag = blob.subarray(IV_BYTES + KEY_BYTES);
    const decipher = createDecipheriv(ALGORITHM, kek, nonce);
    decipher.setAuthTag(tag);
    try {
        const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
        return plaintext;
    }
    catch {
        throw new CryptoError("unwrapDek : échec d'authentification (KEK erronée ?)");
    }
}
/**
 * Chiffre un secret d'application (app password) avec la DEK.
 * AAD = familyId : le blob chiffré n'est valide que pour cette famille.
 */
export function encryptSecret(dek, familyId, secret) {
    const nonce = randomBytes(IV_BYTES);
    const cipher = createCipheriv(ALGORITHM, dek, nonce);
    cipher.setAAD(Buffer.from(familyId, "utf8"));
    const payload = Buffer.from(secret, "utf8");
    const ciphertext = Buffer.concat([cipher.update(payload), cipher.final()]);
    const tag = cipher.getAuthTag();
    return {
        ciphertext: Buffer.concat([ciphertext, tag]).toString("base64"),
        nonce: nonce.toString("base64"),
    };
}
/** Déchiffre un secret ; échoue si l'AAD (familyId) ou la DEK ne correspondent pas. */
export function decryptSecret(dek, familyId, ciphertext, nonce) {
    const ct = Buffer.from(ciphertext, "base64");
    if (ct.length < TAG_BYTES) {
        throw new CryptoError("decryptSecret : ciphertext trop court");
    }
    const payload = ct.subarray(0, ct.length - TAG_BYTES);
    const tag = ct.subarray(ct.length - TAG_BYTES);
    const decipher = createDecipheriv(ALGORITHM, dek, Buffer.from(nonce, "base64"));
    decipher.setAAD(Buffer.from(familyId, "utf8"));
    decipher.setAuthTag(tag);
    try {
        return Buffer.concat([decipher.update(payload), decipher.final()]).toString("utf8");
    }
    catch {
        throw new CryptoError("decryptSecret : échec d'authentification (AAD ou DEK erronés)");
    }
}
/**
 * Rotation de KEK : re-wrappe une DEK sous une nouvelle KEK.
 * Ne déchiffre jamais l'app password lui-même.
 */
export function rewrapDek(oldKek, newKek, wrappedDek) {
    const dek = unwrapDek(oldKek, wrappedDek);
    return wrapDek(newKek, dek);
}
/* ------------------------------------------------------------------ */
/* Utilitaires                                                         */
/* ------------------------------------------------------------------ */
/** Empreinte stable (non réversible) d'un token de refresh, pour le stockage. */
export function sha256(input) {
    return createHash("sha256").update(input).digest("hex");
}
export function constantTimeEqual(a, b) {
    const ba = Buffer.from(a);
    const bb = Buffer.from(b);
    if (ba.length !== bb.length)
        return false;
    return timingSafeEqual(ba, bb);
}
//# sourceMappingURL=envelope.js.map