import { randomBytes } from 'node:crypto'; import { existsSync } from 'node:fs'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { dirname } from 'node:path'; import { getMasterKeyPath } from '../config/paths'; /** * Master key length in bytes (256 bits = 32 bytes) */ const MASTER_KEY_LENGTH = 32; /** * Generate a new master key * * Policy function (Rule 10.1) - pure computation, no I/O * * @returns 32-byte master key as Buffer */ export function generateMasterKey(): Buffer { return randomBytes(MASTER_KEY_LENGTH); } /** * Validate master key format * * Policy function - validates only * * @param key - Buffer to validate * @returns True if valid 32-byte key */ export function isValidMasterKey(key: Buffer): boolean { return Buffer.isBuffer(key) && key.length === MASTER_KEY_LENGTH; } /** * Write master key to file * * Execution function (Rule 10.1) - performs file I/O * * @param key - Master key to write * @param path - File path (optional) */ export async function writeMasterKey( key: Buffer, path: string = getMasterKeyPath(), ): Promise { if (!isValidMasterKey(key)) { throw new Error('Invalid master key: must be 32 bytes'); } // Ensure directory exists const dir = dirname(path); await mkdir(dir, { recursive: true }); // Write key as hex string await writeFile(path, key.toString('hex'), { mode: 0o600 }); } /** * Read master key from file * * Execution function - performs file I/O * * @param path - File path (optional) * @returns Master key as Buffer * @throws Error if file doesn't exist or key is invalid */ export async function readMasterKey(path: string = getMasterKeyPath()): Promise { if (!existsSync(path)) { throw new Error(`Master key file not found: ${path}`); } const hexString = await readFile(path, 'utf-8'); const key = Buffer.from(hexString.trim(), 'hex'); if (!isValidMasterKey(key)) { throw new Error(`Invalid master key in file: ${path} (must be 32 bytes)`); } return key; } /** * Get or create master key * * Orchestration function (Rule 10.1) - coordinates policy and execution * * @param path - File path (optional) * @returns Master key as Buffer */ export async function getOrCreateMasterKey(path: string = getMasterKeyPath()): Promise { // Check if key exists if (existsSync(path)) { return readMasterKey(path); } // Generate new key const key = generateMasterKey(); // Write to file await writeMasterKey(key, path); return key; } /** * Check if master key exists * * @param path - File path (optional) * @returns True if master key file exists */ export function masterKeyExists(path: string = getMasterKeyPath()): boolean { return existsSync(path); }