aere-quantum/control-plane/fixturi-organizatie.mjs

150 lines
11 KiB
JavaScript

// fixturi-organizatie.mjs: artefactele pe care se masoara judecata organizatiei (organizatie.mjs), DERIVATE din producatori, niciunul scris de
// mana in forma pe care o presupune consumatorul:
// - pentru cele 23 de feluri ale suitei de conformitate: vectorii ei (tools/aere-conformance/vectori, publicati in aere-quantum/conformance),
// pe care generatorul lor ii deriva din producatori si ii verifica octet cu octet; fiecare vector folosit aici se cere egal cu amprenta lui din
// INDEX.json. Un vector `{ artifact, input }` se IMPARTE dupa clasele din organizatie.mjs: artefactul si ce e semnat de un tert merg in pachet,
// intrarile verificatorului (ceasul, increderea, provocarea, citirile lui) in contextul celui care verifica;
// - pentru felurile planului de control: starea Shield facuta acum de sidecar-ul real (doua inregistrari, un buraf), planificatorul real si
// executorul real (uscat), iar raportul de conformitate PQ facut de raport-conformitate.mjs pe planul acela.
// Fiecare fel are un artefact BUN si un frate STRICAT, refuzat de verificatorul lui pentru o regula numita.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { INTRARI, PACK_KIND, CONTEXT_KIND, FELURI_PROPRII } from './organizatie.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const RAD = path.resolve(AICI, '..', '..');
const sha = (b) => '0x' + crypto.createHash('sha256').update(b).digest('hex');
export function dosarulSuitei() {
const c = [path.join(AICI, '..', 'conformance'), path.join(RAD, 'tools', 'aere-conformance')];
return c.find((d) => fs.existsSync(path.join(d, 'vectori', 'INDEX.json'))) || null;
}
let INDEX = null;
export function indexul() {
if (INDEX) return INDEX;
const d = dosarulSuitei(); if (!d) throw new Error('the conformance vectors are not beside the control plane (../conformance/vectori) nor in tools/aere-conformance');
INDEX = { dir: path.join(d, 'vectori'), j: JSON.parse(fs.readFileSync(path.join(d, 'vectori', 'INDEX.json'), 'utf8')) };
return INDEX;
}
/** Un vector, cerut egal octet cu octet cu amprenta lui din INDEX.json (deci chiar cel scris de generator). */
export function vectorul(id) {
const { dir, j } = indexul();
const e = j.vectors.find((v) => v.id === id); if (!e) throw new Error('no vector ' + id + ' in INDEX.json');
const b = fs.readFileSync(path.join(dir, ...id.split('/')) + '.json');
if (sha(b) !== e.sha256) throw new Error(`vector ${id}: sha256 ${sha(b).slice(0, 14)} is not the one in INDEX.json (${e.sha256.slice(0, 14)})`);
return JSON.parse(b.toString('utf8'));
}
/** inputul unui vector -> partea pachetului (artefactul, ce e semnat de un tert) si partea contextului (verificatorul, ceasul) */
export function imparte(fel, input) {
const cls = INTRARI[fel];
const pack = {}, context = {};
for (const [k, v] of Object.entries(input)) {
// '*': o cheie pe care modulul nu o citeste pe nume, la un fel citit intreg (un membru in plus): a artefactului, ca in organizatie.mjs
const c = Object.hasOwn(cls, k) ? cls[k] : cls['*'];
if (c === undefined) throw new Error(`${fel}: the vector has an input ${k} that organizatie.mjs does not classify`);
if (c === 'artifact' || c === 'stapled') pack[k] = v; else context[k] = v;
}
return { pack, context };
}
const egal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
/** Pentru fiecare fel al suitei: un vector bun (nu INVALID, fara retea; VALID cand exista) si un frate stricat: INVALID, fara retea, facut din
* vectorul bun, cu ACELASI context si alt artefact (deci artefactul e stricat, nu increderea celui care verifica), o singura regula cand se poate. */
export function alegeVectorii() {
const { j } = indexul();
const feluri = Object.keys(j.artifacts);
const ales = {};
for (const fel of feluri) {
const ale = j.vectors.filter((v) => v.artifact === fel && !v.requires.length);
const bun = ale.find((v) => v.verdict === 'VALID') || ale.find((v) => v.verdict === 'PARTIAL');
if (!bun) throw new Error(`${fel}: no vector that is not INVALID and needs no network`);
const vb = vectorul(bun.id); const ib = imparte(fel, vb.input);
const candidati = ale.filter((v) => v.verdict === 'INVALID').map((v) => vectorul(v.id))
.filter((x) => x.base === bun.id).map((x) => ({ x, i: imparte(fel, x.input) }))
.filter(({ i }) => egal(i.context, ib.context) && !egal(i.pack, ib.pack));
// o singura regula, si nu una de forma (un artefact care nu se poate citi deloc spune putin despre compunere), cand se poate
const deForma = (r) => /[.-]form$/.test(r);
const rau = candidati.find(({ x }) => x.expected.failed.length === 1 && !deForma(x.expected.failed[0])) || candidati.find(({ x }) => x.expected.failed.length === 1) || candidati[0];
if (!rau) throw new Error(`${fel}: no INVALID vector made from ${bun.id} with the same verifier inputs and another artifact`);
ales[fel] = { bun: { id: bun.id, v: vb, ...ib }, rau: { id: rau.x.id, v: rau.x, ...rau.i } };
}
return ales;
}
/** Starea Shield, facuta de uneltele reale: un jurnal al sidecar-ului (doua inregistrari), burafu lui, planul planificatorului pe un inventar
* cu un schimb de cheie si o semnatura clasice si o scanare cu expunere HNDL, si executia lui uscata. Fratele stricat: o inregistrare a executiei
* schimbata dupa scriere (lantul executiei rupt, nimic altceva). */
export async function stareaShield() {
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'org-shield-'));
try {
const { caleaSidecarului } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href);
const SIDE = caleaSidecarului();
const side = (args) => execFileSync(process.execPath, [SIDE, ...args], { encoding: 'utf8', stdio: 'pipe' });
const log = path.join(T, 'audit.log'); const bin = path.join(T, 'node'); fs.writeFileSync(bin, 'BIN');
side(['record', '--kind', 'runtime', '--artifact', bin, '--attested', sha(fs.readFileSync(bin)), '--host', 'org-prod-1', '--log', log, '--at', '2026-10-20T08:00:00Z']);
side(['record', '--kind', 'deployment', '--name', 'checkout-api', '--version', '4.2.1', '--content-file', bin, '--log', log, '--at', '2026-10-20T08:05:00Z']);
const bf = path.join(T, 'b.json'); side(['bundle', '--log', log, '--out', bf, '--host', 'org-prod-1', '--at', '2026-10-20T08:10:00Z']);
const bundle = JSON.parse(fs.readFileSync(bf, 'utf8'));
const { planeaza } = await import(pathToFileURL(path.join(AICI, 'plan-migrare.mjs')).href);
const plan = planeaza({ inventory: [
{ assetType: 'algorithm', name: 'ECDH', primitive: 'key-agree', quantumVulnerable: true, ref: 'a:1' },
{ assetType: 'algorithm', name: 'RSA-2048', primitive: 'signature', quantumVulnerable: true, ref: 'a:2' },
{ assetType: 'algorithm', name: 'ML-KEM-768', primitive: 'kem', quantumVulnerable: false, ref: 'a:3' },
], scan: { domain: 'exemplu.test', findings: [{ id: 'hndl-exposed', severity: 'high', title: 'No post-quantum key exchange' }] } });
const { executa } = await import(pathToFileURL(path.join(AICI, 'executa-migrare.mjs')).href);
const x = await executa(plan, { consent: 'all', execute: false, out: path.join(T, 'exec') });
const execution = JSON.parse(fs.readFileSync(x.file, 'utf8'));
if (!(Array.isArray(execution.records) && execution.records.length >= 2)) throw new Error('the dry execution wrote fewer than two records');
const stricata = JSON.parse(JSON.stringify(execution)); stricata.records[1].record.verdict = 'OK';
return { bun: { bundle, plan, execution }, rau: { bundle, plan, execution: stricata }, rauRegula: 'shield.execution-chain', plan };
} finally { fs.rmSync(T, { recursive: true, force: true }); }
}
/** Raportul de conformitate PQ pe planul real, legat de plicul lui AIP-23; fratele stricat: raportul schimbat dupa legare. */
export async function raportulPQ(plan) {
const { raportConformitate } = await import(pathToFileURL(path.join(AICI, 'raport-conformitate.mjs')).href);
const { report, envelope } = raportConformitate({ plan, organization: 'Example Organization', risk: 'high', date: '2026-10-20' });
const schimbat = JSON.parse(JSON.stringify(report)); schimbat.organization = 'Another Organization';
return { bun: { report, envelope }, rau: { report: schimbat, envelope }, rauRegula: 'pqreport.binding' };
}
/**
* Pachetul bun al unei organizatii cu un artefact din fiecare fel (23 ale suitei + 2 ale planului de control), contextul derivat al celui care
* verifica, si ce se asteapta pentru fiecare (verdictul, regulile, starile nivelurilor din vector). `stricate[fel]` e fratele stricat al fiecaruia.
*/
export async function pachetulBun() {
const ales = alegeVectorii();
const artifacts = [], byArtifact = {}, asteptat = {}, stricate = {};
const idDe = (fel) => fel + '-1';
for (const [fel, { bun, rau }] of Object.entries(ales)) {
const id = idDe(fel);
artifacts.push({ id, artifact: fel, input: bun.pack });
if (Object.keys(bun.context).length) byArtifact[id] = bun.context;
asteptat[id] = { verdict: bun.v.expected.verdict, failed: bun.v.expected.failed, levels: bun.v.expected.levels, vector: bun.id };
stricate[fel] = { id, artifact: { id, artifact: fel, input: rau.pack }, failed: rau.v.expected.failed, levels: rau.v.expected.levels, vector: rau.id };
}
const sh = await stareaShield(); const pq = await raportulPQ(sh.plan);
artifacts.unshift({ id: 'shield-console-1', artifact: 'shield-console', input: sh.bun });
stricate['shield-console'] = { id: 'shield-console-1', artifact: { id: 'shield-console-1', artifact: 'shield-console', input: sh.rau }, failed: [sh.rauRegula], vector: null };
asteptat['shield-console-1'] = { verdict: 'VALID', failed: [] };
artifacts.push({ id: 'pq-compliance-report-1', artifact: 'pq-compliance-report', input: pq.bun });
stricate['pq-compliance-report'] = { id: 'pq-compliance-report-1', artifact: { id: 'pq-compliance-report-1', artifact: 'pq-compliance-report', input: pq.rau }, failed: [pq.rauRegula], vector: null };
asteptat['pq-compliance-report-1'] = { verdict: 'VALID', failed: [] };
for (const f of FELURI_PROPRII) if (!stricate[f]) throw new Error('no broken sibling for ' + f);
return {
pack: { v: 1, kind: PACK_KIND, organization: 'Example Organization', createdAt: '2026-10-20T09:00:00Z', artifacts },
context: { v: 1, kind: CONTEXT_KIND, byArtifact },
asteptat, stricate, ales,
};
}
/** pachetul cu artefactul `id` inlocuit (sau scos, cu null) */
export function inlocuieste(pack, id, artefact) {
const p = JSON.parse(JSON.stringify(pack));
const i = p.artifacts.findIndex((a) => a.id === id); if (i < 0) throw new Error('no artifact ' + id);
if (artefact === null) p.artifacts.splice(i, 1); else p.artifacts[i] = JSON.parse(JSON.stringify(artefact));
return p;
}