const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const { decryptBuffer } = require('./driverLicenseCrypto'); const DEFAULT_STORAGE_DIR = path.resolve(__dirname, '..', '..', 'secure_storage', 'driver-license'); const getStorageDir = () => process.env.DRIVER_LICENSE_STORAGE_DIR || DEFAULT_STORAGE_DIR; const assertSafeStorageKey = (storageKey) => { const normalized = String(storageKey || '').trim(); if (!normalized || normalized.includes('..') || normalized.startsWith('/')) { throw new Error('Invalid storage key'); } return normalized; }; const resolveStoragePath = (storageKey) => { const safeStorageKey = assertSafeStorageKey(storageKey); const baseDir = path.resolve(getStorageDir()); const candidatePath = path.resolve(baseDir, ...safeStorageKey.split('/')); if (candidatePath !== baseDir && !candidatePath.startsWith(`${baseDir}${path.sep}`)) { throw new Error('Invalid storage key path'); } return candidatePath; }; const ensureStorageDirExists = async (absolutePath) => { await fs.promises.mkdir(path.dirname(absolutePath), { recursive: true, mode: 0o700 }); }; const generateStorageKey = () => { const now = new Date(); const year = String(now.getUTCFullYear()); const month = String(now.getUTCMonth() + 1).padStart(2, '0'); const uuid = crypto.randomUUID(); const hash = crypto .createHash('sha256') .update(`${uuid}:${Date.now()}:${crypto.randomBytes(32).toString('hex')}`) .digest('hex') .slice(0, 24); return path.posix.join(year, month, `${uuid}_${hash}.bin`); }; const persistEncryptedBuffer = async (encryptedBuffer) => { if (!Buffer.isBuffer(encryptedBuffer)) { throw new Error('encryptedBuffer must be a Buffer'); } const storageKey = generateStorageKey(); const absolutePath = resolveStoragePath(storageKey); await ensureStorageDirExists(absolutePath); await fs.promises.writeFile(absolutePath, encryptedBuffer, { mode: 0o600, flag: 'wx' }); return { storageKey }; }; const readEncryptedBuffer = async (storageKey) => { const absolutePath = resolveStoragePath(storageKey); return fs.promises.readFile(absolutePath); }; const readDecryptedBuffer = async (storageKey, encryptionMetadata) => { const ciphertext = await readEncryptedBuffer(storageKey); return decryptBuffer({ ciphertext, ivHex: encryptionMetadata?.ivHex, authTagHex: encryptionMetadata?.authTagHex, algorithm: encryptionMetadata?.algorithm }); }; const removeStoredFile = async (storageKey) => { try { await fs.promises.unlink(resolveStoragePath(storageKey)); } catch (error) { if (error.code !== 'ENOENT') { throw error; } } }; module.exports = { getStorageDir, persistEncryptedBuffer, readEncryptedBuffer, readDecryptedBuffer, removeStoredFile };