/**
 * Session Encryption for secure storage of chat sessions
 *
 * Uses AES-256-GCM for authenticated encryption:
 * - Encrypts session content before storage
 * - Protects sensitive conversation data
 * - Key derived from user password or machine-specific key
 */
import * as crypto from 'crypto';
import fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
// AES-256-GCM parameters
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16; // 128 bits
const AUTH_TAG_LENGTH = 16; // 128 bits
const SALT_LENGTH = 32;
const PBKDF2_ITERATIONS = 100000;
const DEFAULT_CONFIG = {
    keyPath: path.join(os.homedir(), '.codebuddy', '.encryption-key'),
    usePassword: false,
    enabled: true,
};
/**
 * Session encryption manager
 */
export class SessionEncryption {
    config;
    key = null;
    initialized = false;
    constructor(config = {}) {
        this.config = {
            keyPath: config.keyPath ?? DEFAULT_CONFIG.keyPath,
            usePassword: config.usePassword ?? DEFAULT_CONFIG.usePassword,
            enabled: config.enabled ?? DEFAULT_CONFIG.enabled,
        };
    }
    /**
     * Initialize encryption with machine-generated key
     */
    async initialize() {
        if (this.initialized || !this.config.enabled) {
            return;
        }
        try {
            // Try to load existing key
            if (await fs.pathExists(this.config.keyPath)) {
                const keyData = await fs.readFile(this.config.keyPath);
                this.key = keyData;
            }
            else {
                // Generate new key
                this.key = crypto.randomBytes(KEY_LENGTH);
                // Store key securely
                await fs.ensureDir(path.dirname(this.config.keyPath));
                await fs.writeFile(this.config.keyPath, this.key, { mode: 0o600 });
            }
            this.initialized = true;
        }
        catch {
            // Fallback to machine-based key
            this.key = this.deriveMachineKey();
            this.initialized = true;
        }
    }
    /**
     * Initialize with password-based key
     */
    async initializeWithPassword(password, salt) {
        if (!this.config.enabled) {
            return '';
        }
        const saltBuffer = salt
            ? Buffer.from(salt, 'base64')
            : crypto.randomBytes(SALT_LENGTH);
        this.key = await this.deriveKeyFromPassword(password, saltBuffer);
        this.initialized = true;
        return saltBuffer.toString('base64');
    }
    /**
     * Encrypt data
     */
    encrypt(data) {
        if (!this.config.enabled || !this.key) {
            // Return passthrough if encryption disabled
            return {
                ciphertext: Buffer.from(data).toString('base64'),
                iv: '',
                authTag: '',
                salt: '',
                version: 0, // version 0 = unencrypted
            };
        }
        const iv = crypto.randomBytes(IV_LENGTH);
        const salt = crypto.randomBytes(SALT_LENGTH);
        const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv, {
            authTagLength: AUTH_TAG_LENGTH,
        });
        const encrypted = Buffer.concat([
            cipher.update(data, 'utf8'),
            cipher.final(),
        ]);
        return {
            ciphertext: encrypted.toString('base64'),
            iv: iv.toString('base64'),
            authTag: cipher.getAuthTag().toString('base64'),
            salt: salt.toString('base64'),
            version: 1,
        };
    }
    /**
     * Decrypt data
     */
    decrypt(encrypted) {
        // Handle unencrypted data (version 0)
        if (encrypted.version === 0 || !this.config.enabled || !this.key) {
            return Buffer.from(encrypted.ciphertext, 'base64').toString('utf8');
        }
        const iv = Buffer.from(encrypted.iv, 'base64');
        const authTag = Buffer.from(encrypted.authTag, 'base64');
        const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');
        const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv, {
            authTagLength: AUTH_TAG_LENGTH,
        });
        decipher.setAuthTag(authTag);
        const decrypted = Buffer.concat([
            decipher.update(ciphertext),
            decipher.final(),
        ]);
        return decrypted.toString('utf8');
    }
    /**
     * Encrypt JSON object
     */
    encryptObject(obj) {
        return this.encrypt(JSON.stringify(obj));
    }
    /**
     * Decrypt to JSON object
     */
    decryptObject(encrypted) {
        const decrypted = this.decrypt(encrypted);
        return JSON.parse(decrypted);
    }
    /**
     * Check if data is encrypted
     */
    isEncrypted(data) {
        return data.version > 0 && !!data.iv && !!data.authTag;
    }
    /**
     * Derive key from password using PBKDF2
     */
    deriveKeyFromPassword(password, salt) {
        return new Promise((resolve, reject) => {
            crypto.pbkdf2(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512', (err, key) => {
                if (err)
                    reject(err);
                else
                    resolve(key);
            });
        });
    }
    /**
     * Derive a key from machine-specific data
     * (Used as fallback when key file can't be created)
     */
    deriveMachineKey() {
        const machineId = [
            os.hostname(),
            os.userInfo().username,
            os.homedir(),
            os.platform(),
        ].join('|');
        return crypto.createHash('sha256').update(machineId).digest();
    }
    /**
     * Rotate encryption key
     * Re-encrypts all data with a new key
     */
    async rotateKey() {
        if (!this.key) {
            throw new Error('Encryption not initialized');
        }
        const oldKey = this.key.toString('base64');
        // Generate new key
        const newKey = crypto.randomBytes(KEY_LENGTH);
        // Store new key
        await fs.writeFile(this.config.keyPath, newKey, { mode: 0o600 });
        this.key = newKey;
        return {
            oldKey,
            newKey: newKey.toString('base64'),
        };
    }
    /**
     * Check if encryption is enabled and initialized
     */
    isReady() {
        return this.initialized && this.key !== null;
    }
    /**
     * Get encryption status
     */
    getStatus() {
        return {
            enabled: this.config.enabled,
            initialized: this.initialized,
            algorithm: ALGORITHM,
            keyLength: KEY_LENGTH * 8, // bits
        };
    }
    /**
     * Clear encryption key from memory
     */
    dispose() {
        if (this.key) {
            // Overwrite key in memory
            crypto.randomFillSync(this.key);
            this.key = null;
        }
        this.initialized = false;
    }
}
// Singleton instance
let sessionEncryption = null;
/**
 * Get or create the session encryption instance
 */
export function getSessionEncryption() {
    if (!sessionEncryption) {
        sessionEncryption = new SessionEncryption();
    }
    return sessionEncryption;
}
/**
 * Initialize session encryption
 */
export async function initializeEncryption(config) {
    sessionEncryption = new SessionEncryption(config);
    await sessionEncryption.initialize();
    return sessionEncryption;
}
/**
 * Reset session encryption
 */
export function resetSessionEncryption() {
    if (sessionEncryption) {
        sessionEncryption.dispose();
        sessionEncryption = null;
    }
}
export default SessionEncryption;
//# sourceMappingURL=session-encryption.js.map