123 lines
12 KiB
JavaScript
123 lines
12 KiB
JavaScript
// Proba raportului de conformitate post-cuantica (raport-conformitate.mjs), pe iesirile REALE ale producatorilor, nu pe o fixtura
|
|
// scrisa in forma presupusa (lectia din 27 sept: consola, planificatorul si verificatorul tradus au iesit verzi pe forme inventate):
|
|
// - inventarul: crypto-inventory rulat prin control-plane.mjs --json pe un dosar temporar cu un certificat RSA facut de openssl si un
|
|
// fisier de cod care cere ECDH si o semnatura RSA;
|
|
// - scanarea: constatari cu id-urile, severitatile si titlurile EXTRASE din sursa scanerului (readiness-service.mjs, apelurile add()).
|
|
// Plus plicul AIP-23 trecut prin verificatorul de referinta publicat, si controale negative (raport atins, inventar numai PQ, risc).
|
|
// node cloud-gateway/control-plane/proba-raport-conformitate.mjs -> 0 toate cum trebuia, 1 altfel, 2 NEMASURAT (producator nepornit)
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
// AERE_RAPORT_MODUL: numai pentru controlul negativ (control-negativ-raport-conformitate.mjs: o copie plantata, langa original)
|
|
const { raportConformitate, verificaRaport, natura, sha256Canonic } = await import(process.env.AERE_RAPORT_MODUL || './raport-conformitate.mjs');
|
|
import { planeaza } from './plan-migrare.mjs';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const RAD = path.resolve(AICI, '..', '..');
|
|
// verificatorul AIP-23: AERE_VERIFY_PROOF, sau cel din depozitul de dezvoltare
|
|
const VERIFY = process.env.AERE_VERIFY_PROOF ? path.resolve(process.env.AERE_VERIFY_PROOF) : path.join(RAD, 'verificator-falcon', 'verify-proof.mjs');
|
|
if (!fs.existsSync(VERIFY)) { console.log(`NEMASURAT: verificatorul AIP-23 nu e la ${VERIFY}; dati AERE_VERIFY_PROOF (de ex. verify-proof.mjs din aere-node/tools)`); process.exit(2); }
|
|
let rele = 0, n = 0;
|
|
const cer = (nume, ok, extra = '') => { n++; console.log(` [${ok ? 'OK ' : 'RAU '}] ${nume}${extra ? ' (' + extra + ')' : ''}`); if (!ok) rele++; };
|
|
const nemasurat = (m) => { console.log('NEMASURAT: ' + m); process.exit(2); };
|
|
|
|
// ---- producatorul 1: inventarul real, pe material facut acum
|
|
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'raport-conf-'));
|
|
let plan;
|
|
try {
|
|
try {
|
|
execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', path.join(T, 'api.key'), '-out', path.join(T, 'api.crt'),
|
|
'-days', '800', '-subj', '/CN=api.intern'], { stdio: 'ignore' });
|
|
} catch (e) { nemasurat('openssl nu a putut face certificatul de proba: ' + e.message); }
|
|
fs.writeFileSync(path.join(T, 'serviciu.js'), "const crypto = require('crypto');\nconst e = crypto.createECDH('prime256v1');\nconst s = crypto.sign('RSA-SHA256', Buffer.from('x'), cheie);\n");
|
|
const { caleaScanerului } = await import('./control-plane.mjs');
|
|
const scanerSrc = fs.readFileSync(caleaScanerului(), 'utf8');
|
|
const re = /add\('([a-z0-9-]+)',\s*'([a-z]+)',\s*[`']([^`']+)[`']/g; const reale = []; let m;
|
|
while ((m = re.exec(scanerSrc))) reale.push({ id: m[1], severity: m[2], title: m[3] });
|
|
cer(`scanerul: id-uri extrase din sursa (control pozitiv al extragerii: >= 10)`, new Set(reale.map((x) => x.id)).size >= 10, String(new Set(reale.map((x) => x.id)).size));
|
|
const ia = (id) => reale.find((x) => x.id === id);
|
|
const scan = { domain: 'client.exemplu.com', findings: ['hndl-exposed', 'tls12-accepted', 'auth-classical'].map(ia).filter(Boolean) };
|
|
cer('scanarea de proba are cele trei constatari reale', scan.findings.length === 3);
|
|
fs.writeFileSync(path.join(T, 'scan.json'), JSON.stringify(scan));
|
|
const r = spawnSync(process.execPath, [path.join(AICI, 'control-plane.mjs'), '--code', T, '--scan', path.join(T, 'scan.json'), '--json'], { encoding: 'utf8' });
|
|
if (r.status !== 0) nemasurat('control-plane.mjs a iesit ' + r.status + ': ' + (r.stderr || '').slice(0, 200));
|
|
plan = JSON.parse(r.stdout);
|
|
cer(`inventarul real a vazut ceva vulnerabil (${plan.inventory.quantumVulnerable} folosiri) si planul are actiuni (${plan.summary.total})`, plan.inventory.quantumVulnerable > 0 && plan.summary.total > 0);
|
|
} finally { fs.rmSync(T, { recursive: true, force: true }); }
|
|
|
|
// ---- raportul
|
|
const { report: raport, envelope: plic, evidenceHash } = raportConformitate({ plan, organization: 'Exemplu SRL', date: '2026-09-28' });
|
|
const dupa = (f) => raport.findings.find(f);
|
|
const kex = dupa((c) => c.ref.startsWith('tls-kex:'));
|
|
cer('schimbul de cheie clasic (scanare) e schimb-de-cheie, expus AZI, cu termenele NIST 2030 si UE 2030 (risc nedeclarat = inalt)',
|
|
kex && kex.nature === 'key-exchange' && kex.exposedToday && kex.regimes.some((x) => x.regime === 'nist-ir-8547' && x.deadline === '2030-12-31')
|
|
&& kex.regimes.some((x) => x.regime === 'eu-2025' && x.deadline === '2030-12-31' && /not declared/.test(x.what)));
|
|
cer('zilele pana la termen se numara de la data raportului (2026-09-28 -> 2030-12-31 = 1555)', kex && kex.regimes[0].days === 1555, kex && String(kex.regimes[0].days));
|
|
const auth = dupa((c) => c.ref.startsWith('tls-cert:'));
|
|
cer('autentificarea clasica e semnatura, BLOCATA de ecosistem, NU expusa azi, dar cu termen', auth && auth.nature === 'signature' && auth.method === 'blocked' && !auth.exposedToday && auth.nearestDeadline === '2030-12-31');
|
|
const conf = dupa((c) => c.ref.includes(':tls12-accepted'));
|
|
cer('TLS 1.2 acceptat e configuratie, fara termen de algoritm', conf && conf.nature === 'configuration' && conf.regimes.length === 0 && conf.nearestDeadline === null);
|
|
const inv = raport.findings.filter((c) => c.ref && !c.ref.startsWith('tls-'));
|
|
cer(`activele din inventar sunt clasificate (niciunul neclasificat): ${inv.map((c) => c.nature).join(', ')}`, inv.length > 0 && inv.every((c) => c.nature !== 'unclassified'));
|
|
const cheie = inv.find((c) => c.nature === 'key-use-unknown');
|
|
cer('cheia privata RSA a inventarului: folosire necunoscuta, cu termenele NIST/UE, dar "expusa azi" = NESTIUT (null), nu fals', cheie && cheie.exposedToday === null && cheie.nearestDeadline === '2030-12-31');
|
|
cer('rezumatul: niciun neclasificat, cel mai apropiat termen 2030-12-31, verdictul numeste expunerea de azi',
|
|
raport.summary.unclassified === 0 && raport.summary.nearestDeadline === '2030-12-31' && /exposed TODAY/.test(raport.summary.verdict));
|
|
cer('fara CNSA cerut, regimul CNSA nu apare in raport', !raport.regimes['cnsa-2.0'] && raport.findings.every((c) => c.regimes.every((x) => x.regime !== 'cnsa-2.0')));
|
|
cer('fiecare regim din raport isi poarta sursa, data sursei si data citirii', Object.values(raport.regimes).every((g) => g.source && g.sourceDate && g.read));
|
|
|
|
// ---- plicul AIP-23, prin verificatorul de referinta publicat
|
|
const T2 = fs.mkdtempSync(path.join(os.tmpdir(), 'raport-plic-'));
|
|
try {
|
|
fs.writeFileSync(path.join(T2, 'plic.json'), JSON.stringify(plic));
|
|
const v = spawnSync(process.execPath, [VERIFY, path.join(T2, 'plic.json'), '--json'], { encoding: 'utf8' });
|
|
let j = null; try { j = JSON.parse(v.stdout); } catch { /* */ }
|
|
cer('plicul compliance trece prin verify-proof.mjs: VALID, integritate PASSED', j && v.status === 0 && j.verdict === 'VALID' && j.levels.some((l) => l.level === 'integrity' && l.state === 'PASSED'), j ? j.verdict : 'fara JSON');
|
|
cer('plicul leaga raportul: evidenceHash = amprenta canonica a raportului', plic.statement.evidenceHash === evidenceHash && evidenceHash === sha256Canonic(raport));
|
|
cer('verificaRaport pe raportul emis: ok', verificaRaport(raport, plic).ok);
|
|
// CONTROL NEGATIV: un raport atins (o zi mai mult pana la un termen) nu mai e cel legat de plic
|
|
const atins = JSON.parse(JSON.stringify(raport)); atins.findings[0].regimes[0] && (atins.findings[0].regimes[0].days += 1);
|
|
cer('CONTROL: raportul atins NU mai trece verificaRaport', !verificaRaport(atins, plic).ok);
|
|
// CONTROL NEGATIV: plicul atins dupa hash pica la verificatorul de referinta
|
|
const pa = JSON.parse(JSON.stringify(plic)); pa.statement.result = 'conform';
|
|
fs.writeFileSync(path.join(T2, 'atins.json'), JSON.stringify(pa));
|
|
const va = spawnSync(process.execPath, [VERIFY, path.join(T2, 'atins.json'), '--json'], { encoding: 'utf8' });
|
|
let ja = null; try { ja = JSON.parse(va.stdout); } catch { /* */ }
|
|
cer('CONTROL: plicul cu rezultatul schimbat dupa hash e INVALID la verificator', ja && va.status === 1 && ja.verdict === 'INVALID', ja ? ja.verdict : 'fara JSON');
|
|
} finally { fs.rmSync(T2, { recursive: true, force: true }); }
|
|
|
|
// ---- riscul si CNSA
|
|
const rm = raportConformitate({ plan, organization: 'Exemplu SRL', date: '2026-09-28', risk: 'medium' }).report;
|
|
cer('risc mediu: termenul UE devine 2035-12-31', rm.findings.find((c) => c.ref.startsWith('tls-kex:')).regimes.some((x) => x.regime === 'eu-2025' && x.deadline === '2035-12-31'));
|
|
const rc = raportConformitate({ plan, organization: 'Exemplu SRL', date: '2026-09-28', cnsa: true }).report;
|
|
const kc = rc.findings.find((c) => c.ref.startsWith('tls-kex:')).regimes.find((x) => x.regime === 'cnsa-2.0');
|
|
cer('CNSA: TLS-ul scanat e "servere web si servicii cloud", exclusiv 2033, marcat PRESUPUS', kc && kc.category === 'web-cloud' && kc.deadline === '2033-12-31' && kc.assumed === true);
|
|
const ic = rc.findings.find((c) => !c.ref.startsWith('tls-') && c.nature !== 'configuration');
|
|
const icc = ic && ic.regimes.find((x) => x.regime === 'cnsa-2.0');
|
|
cer('CNSA: un activ din inventar fara categorie data NU primeste un termen inventat', icc && icc.deadline === null && /not given/.test(icc.what));
|
|
const refInv = ic && ic.ref;
|
|
const rcs = raportConformitate({ plan, organization: 'Exemplu SRL', date: '2026-09-28', cnsa: true, cnsaCategories: { [refInv]: 'software-signing' } }).report;
|
|
const icx = rcs.findings.find((c) => c.ref === refInv).regimes.find((x) => x.regime === 'cnsa-2.0');
|
|
cer('CNSA: categoria data explicit (semnare software) da termenul ei, 2030, nepresupus', icx && icx.deadline === '2030-12-31' && icx.assumed === false);
|
|
|
|
// ---- CONTROL NEGATIV al metodei: un plan pe un inventar numai PQ nu are nimic de raportat
|
|
const curat = raportConformitate({ plan: planeaza({ inventory: [{ ref: 'a', assetType: 'algorithm', name: 'ML-KEM-768', primitive: 'kem', quantumVulnerable: false }] }), organization: 'X', date: '2026-09-28' }).report;
|
|
cer('CONTROL: inventar numai PQ -> zero actiuni, fara termen, verdictul o spune', curat.summary.actions === 0 && curat.summary.nearestDeadline === null && /no quantum-vulnerable/.test(curat.summary.verdict));
|
|
cer('CONTROL: o actiune cu ref necunoscut NU e ghicita, e numarata neclasificata', natura({ ref: 'altceva:x', source: 'scan' }) === 'unclassified');
|
|
let arunca = false; try { raportConformitate({ plan, organization: 'X' }); } catch { arunca = true; }
|
|
cer('CONTROL: fara data raportul refuza (e o fotografie datata, nu "azi" implicit)', arunca);
|
|
// 2026-09-29, revizuirea adversariala inainte de publicare (RC1-RC3)
|
|
const numaiNeclasificat = raportConformitate({ plan: { actions: [{ ref: 'altceva:x', source: 'scan', asset: 'x' }] }, organization: 'X', date: '2026-09-29' }).report;
|
|
cer('RC1: numai actiuni neclasificate -> verdictul NU spune "fara algoritmi vulnerabili"', !/no quantum-vulnerable/.test(numaiNeclasificat.summary.verdict) && /UNCLASSIFIED/.test(numaiNeclasificat.summary.verdict), numaiNeclasificat.summary.verdict.slice(0, 80));
|
|
const refuza = (o) => { try { raportConformitate(o); return false; } catch (e) { return /real calendar day/.test(e.message); } };
|
|
cer('RC2: o data de forma buna dar inexistenta (2026-13-45, 2026-02-30) e refuzata cu motivul ei', refuza({ plan, organization: 'X', date: '2026-13-45' }) && refuza({ plan, organization: 'X', date: '2026-02-30' }));
|
|
const refKex = plan.actions.find((a) => String(a.ref).startsWith('tls-kex:'))?.ref;
|
|
let rc3; try { rc3 = raportConformitate({ plan, organization: 'X', date: '2026-09-29', cnsa: true, cnsaCategories: { [refKex]: 'constructor' } }).report; } catch (e) { rc3 = e; }
|
|
const kc3 = rc3 && rc3.findings ? rc3.findings.find((c) => c.ref === refKex).regimes.find((x) => x.regime === 'cnsa-2.0') : null;
|
|
cer('RC3: o categorie CNSA care e un nume mostenit din prototip ("constructor") nu primeste termen si nu arunca', !!kc3 && kc3.deadline === null, rc3 instanceof Error ? rc3.message.slice(0, 60) : kc3 && kc3.what);
|
|
|
|
console.log(`\nraportul de conformitate: ${n - rele}/${n} cum trebuia`);
|
|
process.exitCode = rele ? 1 : 0;
|