diff --git a/apps/api/scripts/decrypt-fiel.ts b/apps/api/scripts/decrypt-fiel.ts new file mode 100644 index 0000000..c960b0a --- /dev/null +++ b/apps/api/scripts/decrypt-fiel.ts @@ -0,0 +1,82 @@ +/** + * CLI script to decrypt FIEL credentials from filesystem backup. + * Usage: FIEL_ENCRYPTION_KEY= npx tsx scripts/decrypt-fiel.ts + * + * Decrypted files are written to /tmp/horux-fiel-/ and auto-deleted after 30 minutes. + */ +import { readFile, writeFile, mkdir, rm } from 'fs/promises'; +import { join } from 'path'; +import { createDecipheriv, createHash } from 'crypto'; + +const FIEL_PATH = process.env.FIEL_STORAGE_PATH || '/var/horux/fiel'; +const FIEL_KEY = process.env.FIEL_ENCRYPTION_KEY; + +const rfc = process.argv[2]; +if (!rfc) { + console.error('Usage: FIEL_ENCRYPTION_KEY= npx tsx scripts/decrypt-fiel.ts '); + process.exit(1); +} +if (!FIEL_KEY) { + console.error('Error: FIEL_ENCRYPTION_KEY environment variable is required'); + process.exit(1); +} + +function deriveKey(): Buffer { + return createHash('sha256').update(FIEL_KEY!).digest(); +} + +function decryptBuffer(encrypted: Buffer, iv: Buffer, tag: Buffer): Buffer { + const key = deriveKey(); + const decipher = createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(encrypted), decipher.final()]); +} + +async function main() { + const fielDir = join(FIEL_PATH, rfc.toUpperCase()); + const outputDir = `/tmp/horux-fiel-${rfc.toUpperCase()}`; + + console.log(`Reading encrypted FIEL from: ${fielDir}`); + + // Read encrypted certificate + const cerEnc = await readFile(join(fielDir, 'certificate.cer.enc')); + const cerIv = await readFile(join(fielDir, 'certificate.cer.iv')); + const cerTag = await readFile(join(fielDir, 'certificate.cer.tag')); + + // Read encrypted private key + const keyEnc = await readFile(join(fielDir, 'private_key.key.enc')); + const keyIv = await readFile(join(fielDir, 'private_key.key.iv')); + const keyTag = await readFile(join(fielDir, 'private_key.key.tag')); + + // Read and decrypt metadata + const metaEnc = await readFile(join(fielDir, 'metadata.json.enc')); + const metaIv = await readFile(join(fielDir, 'metadata.json.iv')); + const metaTag = await readFile(join(fielDir, 'metadata.json.tag')); + + // Decrypt all + const cerData = decryptBuffer(cerEnc, cerIv, cerTag); + const keyData = decryptBuffer(keyEnc, keyIv, keyTag); + const metadata = JSON.parse(decryptBuffer(metaEnc, metaIv, metaTag).toString('utf-8')); + + // Write decrypted files + await mkdir(outputDir, { recursive: true, mode: 0o700 }); + await writeFile(join(outputDir, 'certificate.cer'), cerData, { mode: 0o600 }); + await writeFile(join(outputDir, 'private_key.key'), keyData, { mode: 0o600 }); + await writeFile(join(outputDir, 'metadata.json'), JSON.stringify(metadata, null, 2), { mode: 0o600 }); + + console.log(`\nDecrypted files written to: ${outputDir}`); + console.log('Metadata:', metadata); + console.log('\nFiles will be auto-deleted in 30 minutes.'); + + // Auto-delete after 30 minutes + setTimeout(async () => { + await rm(outputDir, { recursive: true, force: true }); + console.log(`Cleaned up ${outputDir}`); + process.exit(0); + }, 30 * 60 * 1000); +} + +main().catch((err) => { + console.error('Failed to decrypt FIEL:', err.message); + process.exit(1); +});