Decrypts .cer and .key from FIEL_STORAGE_PATH/<RFC>/ to /tmp with 30-minute auto-cleanup for security. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
/**
|
|
* CLI script to decrypt FIEL credentials from filesystem backup.
|
|
* Usage: FIEL_ENCRYPTION_KEY=<key> npx tsx scripts/decrypt-fiel.ts <RFC>
|
|
*
|
|
* Decrypted files are written to /tmp/horux-fiel-<RFC>/ 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=<key> npx tsx scripts/decrypt-fiel.ts <RFC>');
|
|
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);
|
|
});
|