#!/usr/bin/env node 'use strict'; // AERE Quantum Security Control Plane (B1), CLI cap la cap: inventar criptografic + (optional) scanare de pregatire PQ -> plan de // migrare prioritizat. Compune uneltele care exista deja - nu reinventeaza: inventarul (crypto-inventory), scanerul (dat ca JSON), // planificatorul (plan-migrare.mjs). Ruleaza offline pe un dosar de cod; scanarea unui hostname se da aici cu --scan . // Iesirea e in engleza (forma planului 2, vezi plan-migrare.mjs). // // node control-plane.mjs --code [--scan scan.json] [--json] // iesire 0 = plan produs (chiar si gol); 2 = nu s-a putut rula. import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const RAD = path.resolve(AICI, '..', '..'); // inventarul: langa planul de control intr-o copie publica (../crypto-inventory), sau in depozitul de dezvoltare (tools/) export function caleaInventarului() { const c = [path.join(AICI, '..', 'crypto-inventory', 'inventar.mjs'), path.join(RAD, 'tools', 'crypto-inventory', 'inventar.mjs')]; return c.find((p) => fs.existsSync(p)) || c[c.length - 1]; } // scanerul de pregatire PQ: in dosarul readiness/ al unei copii publice, sau langa planul de control (depozitul de dezvoltare) export function caleaScanerului() { const c = [path.join(AICI, '..', 'readiness', 'readiness-service.mjs'), path.join(AICI, '..', 'readiness-service.mjs')]; return c.find((p) => fs.existsSync(p)) || c[c.length - 1]; } // adaptor: findings-ul inventarului e DEJA clasificat (are assetType, name, primitive, quantumVulnerable, certificate in engleza); // nu se re-cheama classify. Se adauga doar ref (unic pe fisier:linie) si locatia. function laPlanificator(g) { return { ref: `crypto:${g.assetType || 'algorithm'}:${g.name || g.primitive || 'unknown'}:${g.file || ''}:${g.line || ''}`, assetType: g.assetType || 'algorithm', name: g.name, // primitiva inventarului trece NEATINSA: pentru certificate ea e 'unknown'/'other' (a cheii), iar ce inseamna asta pentru un // certificat decide planificatorul (primitivaDe), intr-un singur loc primitive: g.primitive, quantumVulnerable: g.quantumVulnerable, curve: g.curve, parameterSetIdentifier: g.parameterSetIdentifier, certificate: g.certificate, material: g.material, location: g.file ? `${g.file}:${g.line || '?'}` : undefined, }; } async function main() { const args = process.argv.slice(2); const jsonOut = args.includes('--json'); const codeI = args.indexOf('--code'); const code = codeI >= 0 ? args[codeI + 1] : null; const scanI = args.indexOf('--scan'); const scanFile = scanI >= 0 ? args[scanI + 1] : null; if (!code) { console.error('usage: node control-plane.mjs --code [--scan scan.json] [--json]'); process.exit(2); } const inv = await import(pathToFileURL(caleaInventarului()).href); const { planeaza } = await import(pathToFileURL(path.join(AICI, 'plan-migrare.mjs')).href); let rez; try { rez = inv.scan(code); } catch (e) { console.error('the inventory could not scan: ' + e.message); process.exit(2); } const clasificate = (rez.findings || []).map(laPlanificator); let scan = null; if (scanFile) { try { scan = JSON.parse(fs.readFileSync(scanFile, 'utf8')); } catch (e) { console.error('cannot read the scan: ' + e.message); process.exit(2); } } const plan = planeaza({ inventory: clasificate, scan }); const iesire = { generatedAt: new Date().toISOString(), code, scan: scanFile || null, inventory: { findings: (rez.findings || []).length, quantumVulnerable: clasificate.filter((c) => c.quantumVulnerable).length }, ...plan, }; if (jsonOut) { console.log(JSON.stringify(iesire, null, 1)); process.exit(0); } console.log(`AERE Control Plane - post-quantum migration plan for ${code}${scanFile ? ' + ' + scanFile : ''}`); console.log(` inventory: ${iesire.inventory.findings} uses, ${iesire.inventory.quantumVulnerable} quantum-vulnerable`); console.log(` plan: ${plan.summary.total} actions | ${JSON.stringify(plan.summary.byUrgency)} | auto=${plan.summary.autoAere} manual=${plan.summary.manual} blocked=${plan.summary.blocked}`); for (const a of plan.actions) { console.log(` [${a.urgency.padEnd(8)}] ${a.asset}${a.location ? ' (' + a.location + ')' : ''}`); console.log(` ${a.problem} -> ${a.target} [${a.method}${a.product ? ' via ' + a.product : ''}]${a.blocker ? ' BLOCKED BY: ' + a.blocker : ''}`); } process.exit(0); } if (import.meta.url === pathToFileURL(process.argv[1] || '').href) { main().catch((e) => { console.error('error: ' + (e.stack || e.message)); process.exit(2); }); }