aere-quantum/pq-kms/test/proba.mjs

1340 lines
62 KiB
JavaScript

// test/proba.mjs - proba Aere PQ KMS, fara framework.
//
// Fiecare afirmatie are perechea ei negativa, iar refuzurile se judeca dupa MOTIV (codul
// erorii), nu doar dupa faptul ca a aruncat ceva. Interoperabilitatea se masoara cu o a doua
// implementare a formatului, scrisa aici independent (HKDF de mana, peste HMAC din node:crypto),
// care decapsuleaza si construieste plicuri direct cu primitivele din node:crypto.
//
// Iesire: tabel pe stdout; cod 0 daca toate trec, 1 daca una pica, 2 daca harnessul a cazut.
// Cu AERE_KMS_PROBA_JSON=<fisier> scrie si rezultatul complet ca JSON (il citeste
// test/control-negativ.mjs; "completed: true" inseamna ca s-a ajuns la capat).
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import http from 'node:http';
import { spawn, spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { openKms, KmsError } from '../kms.mjs';
import { createServer, configFromEnv } from '../server.mjs';
const HERE = path.dirname(fileURLToPath(import.meta.url));
const SERVER_PATH = path.join(HERE, '..', 'server.mjs');
// ---------------------------------------------------------------------------------- harness
const probe = [];
function proba(nume, fn) {
if (probe.some((p) => p.nume === nume)) throw new Error(`proba dublata: ${nume}`);
probe.push({ nume, fn });
}
class Esec extends Error {}
function adevarat(c, ce) {
if (!c) throw new Esec(ce);
}
function egal(a, b, ce) {
if (a !== b) throw new Esec(`${ce}: asteptat ${JSON.stringify(b)}, primit ${JSON.stringify(a)}`);
}
function refuza(fn, cod, ce) {
try {
fn();
} catch (e) {
if (!(e instanceof KmsError)) throw new Esec(`${ce}: a aruncat altceva decat KmsError: ${e && e.message}`);
if (e.code !== cod) throw new Esec(`${ce}: motiv asteptat ${cod}, primit ${e.code} (${e.message})`);
return e;
}
throw new Esec(`${ce}: trebuia refuzat cu ${cod}, a trecut`);
}
const tmpDirs = [];
function tmpDir(tag = 'd') {
const d = fs.mkdtempSync(path.join(os.tmpdir(), `aerekms-proba-${tag}-`));
tmpDirs.push(d);
return d;
}
function rootHex() {
return crypto.randomBytes(32).toString('hex');
}
function nouKms(dir = tmpDir(), root = rootHex()) {
return { kms: openKms({ dataDir: dir, rootKey: root }), dir, root };
}
// ---------------------------------------------------------------------------------- a doua implementare a formatului
const L = {
fp: 'aerekms/v1/fingerprint',
kem: 'aerekms/v1/hybrid-kem',
commit: 'aerekms/v1/commit',
aad: 'aerekms/v1/aad',
dek: 'aerekms/v1/dek',
sig: 'aerekms/v1/hybrid-sig',
sigAlg: 'ed25519+ml-dsa-65',
sigCtx: 'aerekms/v1',
};
const OFF = { ver: 5, fp: 9, ePub: 17, ctK: 49, aadTag: 1137, commit: 1153, payload: 1169 };
const u32 = (n) => {
const b = Buffer.alloc(4);
b.writeUInt32BE(n);
return b;
};
const sha256 = (...p) => {
const h = crypto.createHash('sha256');
p.forEach((x) => h.update(x));
return h.digest();
};
const hmac = (k, ...p) => {
const h = crypto.createHmac('sha256', k);
p.forEach((x) => h.update(x));
return h.digest();
};
// HKDF-SHA-256 (RFC 5869) scris de mana, nu crypto.hkdfSync: a doua implementare, nu aceeasi.
function hkdfManual(ikm, salt, info, len) {
const prk = hmac(salt, ikm);
const out = [];
let t = Buffer.alloc(0);
for (let i = 1; out.reduce((s, b) => s + b.length, 0) < len; i++) {
t = hmac(prk, t, info, Buffer.from([i]));
out.push(t);
}
return Buffer.concat(out).subarray(0, len);
}
const x25519Pub = (raw) => crypto.createPublicKey({ key: { kty: 'OKP', crv: 'X25519', x: Buffer.from(raw).toString('base64url') }, format: 'jwk' });
const ed25519Pub = (raw) => crypto.createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: Buffer.from(raw).toString('base64url') }, format: 'jwk' });
const pkcs8 = (b64) => crypto.createPrivateKey({ key: Buffer.from(b64, 'base64'), format: 'der', type: 'pkcs8' });
const spki = (b64) => crypto.createPublicKey({ key: Buffer.from(b64, 'base64'), format: 'der', type: 'spki' });
function deriveManual({ name, ver, fp, xPubRaw, ekRaw, ePub, ctK, ikm, aad }) {
const tr = Buffer.concat([Buffer.from(L.kem + '\0'), u32(Buffer.byteLength(name)), Buffer.from(name), u32(ver), fp, xPubRaw, sha256(ekRaw), ePub, ctK]);
const salt = sha256(tr);
const commitKey = hkdfManual(ikm, salt, Buffer.from(L.commit), 32);
const aadKey = hkdfManual(ikm, salt, Buffer.from(L.aad), 32);
const dek = hkdfManual(ikm, salt, Buffer.concat([Buffer.from(L.dek + '\0'), sha256(aad)]), 44);
return { commitKey, aadTag: hmac(aadKey, aad).subarray(0, 16), key: dek.subarray(0, 32), iv: dek.subarray(32, 44) };
}
function parseManual(ct) {
const m = /^aerekms:v(\d+):(.+)$/.exec(ct);
if (!m) throw new Error('forma');
const body = Buffer.from(m[2], 'base64');
return {
prefixVer: Number(m[1]),
body,
ver: body.readUInt32BE(OFF.ver),
fp: body.subarray(OFF.fp, OFF.fp + 8),
ePub: body.subarray(OFF.ePub, OFF.ePub + 32),
ctK: body.subarray(OFF.ctK, OFF.ctK + 1088),
aadTag: body.subarray(OFF.aadTag, OFF.aadTag + 16),
commit: body.subarray(OFF.commit, OFF.commit + 16),
};
}
// Deschide un plic cu un IKM dat (ambele secrete, sau doar unul, pentru proba atacatorului).
function openManual(ct, { name, pubVer, ikm, aad }) {
const p = parseManual(ct);
const xPubRaw = Buffer.from(pubVer.public.x25519, 'base64');
const ekRaw = Buffer.from(pubVer.public.ml_kem_768, 'base64').subarray(22);
const d = deriveManual({ name, ver: p.ver, fp: p.fp, xPubRaw, ekRaw, ePub: p.ePub, ctK: p.ctK, ikm, aad });
const commit = hmac(d.commitKey, p.body.subarray(0, OFF.commit)).subarray(0, 16);
if (!commit.equals(p.commit)) throw new Error('commit');
if (!d.aadTag.equals(p.aadTag)) throw new Error('aad');
const dc = crypto.createDecipheriv('aes-256-gcm', d.key, d.iv);
dc.setAAD(p.body.subarray(0, OFF.payload));
dc.setAuthTag(p.body.subarray(p.body.length - 16));
return Buffer.concat([dc.update(p.body.subarray(OFF.payload, p.body.length - 16)), dc.final()]);
}
// Secretele unui plic, calculate cu cheile private exportate (direct cu node:crypto).
function secreteManual(ct, exp) {
const p = parseManual(ct);
const ssX = crypto.diffieHellman({ privateKey: pkcs8(exp.private.x25519_pkcs8), publicKey: x25519Pub(p.ePub) });
const ssK = crypto.decapsulate(pkcs8(exp.private.ml_kem_768_pkcs8), p.ctK);
return { ssX, ssK };
}
// Construieste un plic numai din cheile publice. mod: 'ambele' | 'doar-x' | 'doar-k'.
function sealManual({ name, ver, pubVer, pt, aad, mod = 'ambele' }) {
const xPubRaw = Buffer.from(pubVer.public.x25519, 'base64');
const kSpki = Buffer.from(pubVer.public.ml_kem_768, 'base64');
const ekRaw = kSpki.subarray(22);
const fp = Buffer.from(pubVer.fingerprint, 'hex');
const eph = crypto.generateKeyPairSync('x25519');
const ePub = Buffer.from(eph.publicKey.export({ format: 'jwk' }).x, 'base64url');
const ssX = crypto.diffieHellman({ privateKey: eph.privateKey, publicKey: x25519Pub(xPubRaw) });
const { sharedKey: ssK, ciphertext: ctK } = crypto.encapsulate(crypto.createPublicKey({ key: kSpki, format: 'der', type: 'spki' }));
const ikm = mod === 'ambele' ? Buffer.concat([ssK, ssX]) : mod === 'doar-x' ? ssX : ssK;
const d = deriveManual({ name, ver, fp, xPubRaw, ekRaw, ePub, ctK, ikm, aad });
const pre = Buffer.concat([Buffer.from('AKM1'), Buffer.from([0x45]), u32(ver), fp, ePub, ctK, d.aadTag]);
const hdr = Buffer.concat([pre, hmac(d.commitKey, pre).subarray(0, 16)]);
const c = crypto.createCipheriv('aes-256-gcm', d.key, d.iv);
c.setAAD(hdr);
const body = Buffer.concat([hdr, c.update(pt), c.final(), c.getAuthTag()]);
return `aerekms:v${ver}:${body.toString('base64')}`;
}
function reencode(ct, fn) {
const p = parseManual(ct);
const body = Buffer.from(p.body);
const prefixVer = fn(body) ?? p.prefixVer;
return `aerekms:v${prefixVer}:${body.toString('base64')}`;
}
function sigMessageManual(fpHex, ver, msg) {
return Buffer.concat([Buffer.from(L.sig + '\0' + L.sigAlg + '\0'), Buffer.from(fpHex, 'hex'), u32(ver), Buffer.from(msg)]);
}
function sigParts(sig) {
const m = /^aerekms:v(\d+):(.+)$/.exec(sig);
const body = Buffer.from(m[2], 'base64');
return { body, ver: body.readUInt32BE(5), ed: body.subarray(17, 81), ml: body.subarray(81) };
}
function sigWith(sig, fn) {
const m = /^aerekms:v(\d+):(.+)$/.exec(sig);
const body = Buffer.from(m[2], 'base64');
fn(body);
return `aerekms:v${m[1]}:${body.toString('base64')}`;
}
// ---------------------------------------------------------------------------------- scanerul de material privat
// Cauta octetii ac in fisier si in tot ce se poate decoda din el (base64, hex, JSON), recursiv.
function haystacks(buf, depth = 0, out = []) {
out.push(buf);
if (depth > 4) return out;
const text = buf.toString('utf8');
let obj;
try {
obj = JSON.parse(text);
} catch {
obj = undefined;
}
const strings = [];
const walk = (v) => {
if (typeof v === 'string') strings.push(v);
else if (v && typeof v === 'object') Object.values(v).forEach(walk);
};
if (obj !== undefined) walk(obj);
else strings.push(text);
for (const s of strings) {
if (s.length >= 16 && /^[A-Za-z0-9+/]+={0,2}$/.test(s)) haystacks(Buffer.from(s, 'base64'), depth + 1, out);
if (s.length >= 16 && /^[A-Za-z0-9_-]+$/.test(s)) haystacks(Buffer.from(s, 'base64url'), depth + 1, out);
if (s.length >= 16 && /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0) haystacks(Buffer.from(s, 'hex'), depth + 1, out);
}
return out;
}
function needlesDin(exp) {
const n = [];
for (const b64 of Object.values(exp.private)) {
const der = Buffer.from(b64, 'base64');
n.push(der);
n.push(der.subarray(der.length - 32)); // scalar X25519 / samanta Ed25519 / coada cheii PQ
if (der.length > 200) n.push(der.subarray(100, 132));
}
return n;
}
function gasesteMaterial(files, needles) {
const gasite = [];
for (const f of files) {
const hs = haystacks(fs.readFileSync(f));
for (const nd of needles) if (hs.some((h) => h.indexOf(nd) !== -1)) gasite.push(path.basename(f));
}
return gasite;
}
function toateFisierele(dir) {
const out = [];
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) out.push(...toateFisierele(p));
else out.push(p);
}
return out;
}
// ---------------------------------------------------------------------------------- HTTP
function cerere(port, method, p, { headers = {}, body, chunked = false } = {}) {
return new Promise((resolve, reject) => {
const data = body === undefined ? null : Buffer.isBuffer(body) ? body : Buffer.from(typeof body === 'string' ? body : JSON.stringify(body));
const h = { ...headers };
if (data && !chunked) h['content-length'] = data.length;
if (data && h['content-type'] === undefined) h['content-type'] = 'application/json';
const req = http.request({ host: '127.0.0.1', port, method, path: p, headers: h, agent: false }, (res) => {
const chunks = [];
res.on('data', (c) => chunks.push(c));
res.on('end', () => {
const text = Buffer.concat(chunks).toString('utf8');
let json = null;
try {
json = JSON.parse(text);
} catch {
json = null;
}
resolve({ status: res.statusCode, json, text });
});
});
req.on('error', reject);
if (data) {
if (chunked) for (let i = 0; i < data.length; i += 1024) req.write(data.subarray(i, i + 1024));
else req.write(data);
}
req.end();
});
}
async function cuServer(fn, { maxBody = 4096 } = {}) {
const { kms, dir } = nouKms();
const token = crypto.randomBytes(24).toString('base64url') + 'x'.repeat(8);
const server = createServer({ kms, token, maxBody });
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const port = server.address().port;
const auth = { authorization: `Bearer ${token}` };
try {
await fn({ port, kms, token, auth, dir });
} finally {
await new Promise((r) => server.close(r));
kms.close();
}
}
const b64 = (s) => Buffer.from(s).toString('base64');
const envCurat = () => Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith('AERE_KMS_')));
// ================================================================================== PROBELE
proba('mediu: node:crypto are ml-kem-768, ml-dsa-65, x25519, ed25519', () => {
const k = crypto.generateKeyPairSync('ml-kem-768');
const { sharedKey, ciphertext } = crypto.encapsulate(k.publicKey);
adevarat(crypto.decapsulate(k.privateKey, ciphertext).equals(sharedKey), 'ML-KEM-768 dus-intors');
const d = crypto.generateKeyPairSync('ml-dsa-65');
const s = crypto.sign(null, Buffer.from('m'), d.privateKey);
adevarat(crypto.verify(null, Buffer.from('m'), d.publicKey, s), 'ML-DSA-65 verifica');
adevarat(!crypto.verify(null, Buffer.from('n'), d.publicKey, s), 'ML-DSA-65 refuza alt mesaj');
egal(crypto.generateKeyPairSync('x25519').publicKey.asymmetricKeyType, 'x25519', 'x25519');
egal(crypto.generateKeyPairSync('ed25519').publicKey.asymmetricKeyType, 'ed25519', 'ed25519');
});
// ---------------------------------------------------------------------------------- cheia radacina
proba('radacina: cheie valida porneste serviciul', () => {
const { kms, dir } = nouKms();
adevarat(fs.existsSync(path.join(dir, 'root-check.json')), 'root-check.json scris');
kms.createKey('k', { type: 'encrypt' });
kms.close();
});
proba('radacina: lipsa cheii refuza pornirea cu ROOT_KEY_MISSING si nu creeaza nimic pe disc', () => {
for (const v of [undefined, null, '', ' ', '\r\n']) {
const dir = path.join(tmpDir('lipsa'), 'nu-exista');
refuza(() => openKms({ dataDir: dir, rootKey: v }), 'ROOT_KEY_MISSING', `rootKey=${JSON.stringify(v)}`);
adevarat(!fs.existsSync(dir), 'dosarul de date nu trebuie creat fara cheie radacina');
}
});
proba('radacina: cheie malformata refuzata cu ROOT_KEY_INVALID fara sa tipareasca valoarea', () => {
const valid = rootHex();
for (const v of ['zz' + valid.slice(2), valid.slice(1), valid + 'a', 'nu-e-hex-deloc-nu-e-hex-deloc-nu-e-hex-deloc-nu-e-hex-deloc-1234']) {
const e = refuza(() => openKms({ dataDir: tmpDir(), rootKey: v }), 'ROOT_KEY_INVALID', 'forma');
adevarat(!e.message.includes(v) && !JSON.stringify({ ...e }).includes(v), 'mesajul contine valoarea cheii');
adevarat(!e.message.includes(v.slice(0, 16)), 'mesajul contine un prefix al cheii');
}
});
proba('radacina: cheie de zerouri refuzata cu ROOT_KEY_WEAK', () => {
refuza(() => openKms({ dataDir: tmpDir(), rootKey: '0'.repeat(64) }), 'ROOT_KEY_WEAK', 'zerouri');
});
proba('radacina: CRLF la coada e taiat, aceeasi cheie redeschide depozitul', () => {
const root = rootHex();
const dir = tmpDir();
const a = openKms({ dataDir: dir, rootKey: root });
const c = a.encrypt(a.createKey('k', { type: 'encrypt' }).name, 'x').ciphertext;
a.close();
const b = openKms({ dataDir: dir, rootKey: root.toUpperCase() + '\r\n' });
egal(b.decrypt('k', c).plaintext.toString(), 'x', 'decriptare dupa redeschidere');
b.close();
});
proba('radacina: alta cheie radacina pe acelasi depozit refuzata cu ROOT_KEY_MISMATCH', () => {
const { kms, dir } = nouKms();
kms.createKey('k', { type: 'encrypt' });
kms.close();
refuza(() => openKms({ dataDir: dir, rootKey: rootHex() }), 'ROOT_KEY_MISMATCH', 'alta radacina');
});
proba('radacina: depozit cu chei dar fara root-check.json refuzat cu ROOT_CHECK_MISSING', () => {
const { kms, dir, root } = nouKms();
kms.createKey('k', { type: 'encrypt' });
kms.close();
fs.rmSync(path.join(dir, 'root-check.json'));
refuza(() => openKms({ dataDir: dir, rootKey: root }), 'ROOT_CHECK_MISSING', 'fara root-check');
});
// ---------------------------------------------------------------------------------- stocare
proba('stocare: cheia se reincarca dupa repornire, decripteaza si verifica', () => {
const { kms, dir, root } = nouKms();
kms.createKey('e', { type: 'encrypt' });
kms.createKey('s', { type: 'sign' });
const c = kms.encrypt('e', 'text', 'ctx').ciphertext;
const s = kms.sign('s', 'mesaj').signature;
kms.close();
const k2 = openKms({ dataDir: dir, rootKey: root });
egal(k2.decrypt('e', c, 'ctx').plaintext.toString(), 'text', 'decriptare dupa repornire');
egal(k2.verify('s', 'mesaj', s).valid, true, 'verificare dupa repornire');
k2.close();
});
proba('stocare: materialul privat nu apare in clar pe disc (cu controlul pozitiv al scanerului)', () => {
const { kms, dir } = nouKms();
kms.createKey('e', { type: 'encrypt', exportable: true });
kms.createKey('s', { type: 'sign', exportable: true });
kms.encrypt('e', 'x');
kms.sign('s', 'x');
const needles = [...needlesDin(kms.exportKey('e')), ...needlesDin(kms.exportKey('s'))];
// control pozitiv: scanerul gaseste materialul ascuns pe doua niveluri (base64 de JSON cu base64)
const exp = kms.exportKey('e');
const momeala = path.join(tmpDir('momeala'), 'm.json');
fs.writeFileSync(momeala, JSON.stringify({ x: Buffer.from(JSON.stringify({ a: exp.private.x25519_pkcs8 })).toString('base64') }));
adevarat(gasesteMaterial([momeala], needles).length > 0, 'CONTROL POZITIV: scanerul nu gaseste materialul plantat, deci nu masoara nimic');
const fisiere = toateFisierele(dir);
egal(fisiere.filter((f) => f.endsWith('.json')).length >= 3, true, 'fisierele de chei exista');
const gasite = gasesteMaterial(fisiere, needles);
egal(gasite.length, 0, `material privat gasit in clar in: ${[...new Set(gasite)].join(', ')}`);
kms.close();
});
proba('stocare: min_decryption_version coborat pe disc refuzat cu KEY_FILE_TAMPERED', () => {
const { kms, dir, root } = nouKms();
kms.createKey('k', { type: 'encrypt' });
kms.rotate('k');
kms.setMinDecryptionVersion('k', 2);
kms.close();
const f = path.join(dir, 'keys', 'k.json');
const rec = JSON.parse(fs.readFileSync(f, 'utf8'));
egal(rec.min_decryption_version, 2, 'minimul scris');
rec.min_decryption_version = 1;
fs.writeFileSync(f, JSON.stringify(rec, null, 2));
const k2 = openKms({ dataDir: dir, rootKey: root });
refuza(() => k2.getKey('k'), 'KEY_FILE_TAMPERED', 'minim coborat pe disc');
k2.close();
});
proba('stocare: cheie publica inlocuita pe disc refuzata cu KEY_FILE_TAMPERED', () => {
const { kms, dir, root } = nouKms();
kms.createKey('k', { type: 'encrypt' });
kms.close();
const f = path.join(dir, 'keys', 'k.json');
const rec = JSON.parse(fs.readFileSync(f, 'utf8'));
const atacator = crypto.generateKeyPairSync('x25519');
rec.versions['1'].public.x25519 = Buffer.from(atacator.publicKey.export({ format: 'jwk' }).x, 'base64url').toString('base64');
fs.writeFileSync(f, JSON.stringify(rec, null, 2));
const k2 = openKms({ dataDir: dir, rootKey: root });
refuza(() => k2.encrypt('k', 'x'), 'KEY_FILE_TAMPERED', 'cheie publica inlocuita');
k2.close();
});
// ---------------------------------------------------------------------------------- chei si politica
proba('chei: nume valid acceptat, nume invalid refuzat cu INVALID_KEY_NAME', () => {
const { kms } = nouKms();
kms.createKey('a-b_1', { type: 'encrypt' });
for (const n of ['../x', 'A', '', 'a/b', 'a'.repeat(65), '-a', 'a.json']) {
refuza(() => kms.createKey(n, { type: 'encrypt' }), 'INVALID_KEY_NAME', `nume ${JSON.stringify(n)}`);
}
kms.close();
});
proba('chei: creare dubla refuzata cu KEY_EXISTS, cheie lipsa cu KEY_NOT_FOUND, tip invalid cu INVALID_KEY_TYPE', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
refuza(() => kms.createKey('k', { type: 'sign' }), 'KEY_EXISTS', 'dubla');
refuza(() => kms.encrypt('lipsa', 'x'), 'KEY_NOT_FOUND', 'lipsa');
refuza(() => kms.createKey('t', { type: 'aes' }), 'INVALID_KEY_TYPE', 'tip');
kms.close();
});
proba('chei: operatie pe tipul gresit de cheie refuzata cu WRONG_KEY_TYPE', () => {
const { kms } = nouKms();
kms.createKey('e', { type: 'encrypt' });
kms.createKey('s', { type: 'sign' });
refuza(() => kms.encrypt('s', 'x'), 'WRONG_KEY_TYPE', 'encrypt cu cheie de semnare');
refuza(() => kms.sign('e', 'x'), 'WRONG_KEY_TYPE', 'sign cu cheie de criptare');
refuza(() => kms.datakey('s'), 'WRONG_KEY_TYPE', 'datakey cu cheie de semnare');
kms.close();
});
proba('politica: implicit neexportabila, exportul refuzat cu KEY_NOT_EXPORTABLE', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt' });
egal(d.policy.exportable, false, 'politica implicita');
refuza(() => kms.exportKey('k'), 'KEY_NOT_EXPORTABLE', 'export');
refuza(() => kms.createKey('z', { type: 'encrypt', exportable: 'da' }), 'INVALID_POLICY', 'exportable ne-boolean');
kms.close();
});
proba('politica: cheie exportabila exporta chei private care corespund cheilor publice', () => {
const { kms } = nouKms();
const e = kms.createKey('e', { type: 'encrypt', exportable: true });
const s = kms.createKey('s', { type: 'sign', exportable: true });
const xe = kms.exportKey('e');
const xs = kms.exportKey('s');
const rawX = Buffer.from(crypto.createPublicKey(pkcs8(xe.private.x25519_pkcs8)).export({ format: 'jwk' }).x, 'base64url');
egal(rawX.toString('base64'), e.versions['1'].public.x25519, 'X25519 privata -> publica');
const kPub = crypto.createPublicKey(pkcs8(xe.private.ml_kem_768_pkcs8)).export({ format: 'der', type: 'spki' });
egal(kPub.toString('base64'), e.versions['1'].public.ml_kem_768, 'ML-KEM privata -> publica');
const rawEd = Buffer.from(crypto.createPublicKey(pkcs8(xs.private.ed25519_pkcs8)).export({ format: 'jwk' }).x, 'base64url');
egal(rawEd.toString('base64'), s.versions['1'].public.ed25519, 'Ed25519 privata -> publica');
const dPub = crypto.createPublicKey(pkcs8(xs.private.ml_dsa_65_pkcs8)).export({ format: 'der', type: 'spki' });
egal(dPub.toString('base64'), s.versions['1'].public.ml_dsa_65, 'ML-DSA privata -> publica');
kms.close();
});
// ---------------------------------------------------------------------------------- criptare
proba('criptare: dus-intors cu si fara aad, formatul aerekms:v<ver>:<base64>, plicuri diferite', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const a = kms.encrypt('k', 'secret', 'ctx');
const b = kms.encrypt('k', 'secret', 'ctx');
adevarat(/^aerekms:v1:[A-Za-z0-9+/]+={0,2}$/.test(a.ciphertext), 'forma prefixului');
adevarat(a.ciphertext !== b.ciphertext, 'doua criptari ale aceluiasi text trebuie sa difere');
egal(kms.decrypt('k', a.ciphertext, 'ctx').plaintext.toString(), 'secret', 'cu aad');
const c = kms.encrypt('k', Buffer.alloc(0));
egal(kms.decrypt('k', c.ciphertext).plaintext.length, 0, 'text gol fara aad');
const mare = crypto.randomBytes(100000);
adevarat(kms.decrypt('k', kms.encrypt('k', mare).ciphertext).plaintext.equals(mare), '100 KB');
kms.close();
});
proba('criptare: un octet schimbat in fiecare camp e refuzat cu motivul campului', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const ct = kms.encrypt('k', 'text cu ceva lungime', 'ctx').ciphertext;
egal(kms.decrypt('k', ct, 'ctx').plaintext.toString(), 'text cu ceva lungime', 'plicul neatins trece');
const len = parseManual(ct).body.length;
const campuri = [
['magic', 0, 'MALFORMED_CIPHERTEXT'],
['tip', 4, 'MALFORMED_CIPHERTEXT'],
['versiune', 8, 'VERSION_MISMATCH'],
['amprenta', 9, 'KEY_MISMATCH'],
['X25519 efemer', OFF.ePub + 5, 'HEADER_AUTH_FAILED'],
['ML-KEM ct', OFF.ctK + 500, 'HEADER_AUTH_FAILED'],
['eticheta aad', OFF.aadTag + 3, 'HEADER_AUTH_FAILED'],
['angajament', OFF.commit + 3, 'HEADER_AUTH_FAILED'],
['continut', OFF.payload + 2, 'PAYLOAD_AUTH_FAILED'],
['eticheta GCM', len - 1, 'PAYLOAD_AUTH_FAILED'],
];
for (const [camp, poz, cod] of campuri) {
const rau = reencode(ct, (b) => {
b[poz] ^= 0x01;
});
refuza(() => kms.decrypt('k', rau, 'ctx'), cod, `octet schimbat in ${camp} (pozitia ${poz})`);
}
// trunchiat sub antet: forma rupta; trunchiat in continut: GCM prinde lipsa
const scurt = `aerekms:v1:${parseManual(ct).body.subarray(0, 1100).toString('base64')}`;
refuza(() => kms.decrypt('k', scurt, 'ctx'), 'MALFORMED_CIPHERTEXT', 'trunchiat sub antet');
refuza(() => kms.decrypt('k', ct.slice(0, -8), 'ctx'), 'PAYLOAD_AUTH_FAILED', 'trunchiat in continut');
refuza(() => kms.decrypt('k', 'vault:v1:AAAA', 'ctx'), 'MALFORMED_CIPHERTEXT', 'alt prefix');
kms.close();
});
proba('criptare: ORICE octet schimbat (toate pozitiile) e refuzat', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const ct = kms.encrypt('k', 'abcdefgh', 'ctx').ciphertext;
const len = parseManual(ct).body.length;
const coduri = new Set(['MALFORMED_CIPHERTEXT', 'VERSION_MISMATCH', 'KEY_MISMATCH', 'UNKNOWN_VERSION', 'HEADER_AUTH_FAILED', 'AAD_MISMATCH', 'PAYLOAD_AUTH_FAILED']);
let refuzate = 0;
for (let i = 0; i < len; i++) {
const rau = reencode(ct, (b) => {
b[i] ^= 0x80;
});
try {
kms.decrypt('k', rau, 'ctx');
throw new Esec(`octetul ${i} schimbat a trecut decriptarea`);
} catch (e) {
if (e instanceof Esec) throw e;
adevarat(e instanceof KmsError && coduri.has(e.code), `octetul ${i}: motiv neasteptat ${e.code}`);
refuzate++;
}
}
egal(refuzate, len, 'toate pozitiile refuzate');
kms.close();
});
proba('criptare: alt aad refuzat cu AAD_MISMATCH', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const cu = kms.encrypt('k', 'x', 'tenant-1').ciphertext;
const fara = kms.encrypt('k', 'x').ciphertext;
egal(kms.decrypt('k', cu, 'tenant-1').plaintext.toString(), 'x', 'aad corect');
refuza(() => kms.decrypt('k', cu, 'tenant-2'), 'AAD_MISMATCH', 'alt aad');
refuza(() => kms.decrypt('k', cu), 'AAD_MISMATCH', 'aad lipsa');
refuza(() => kms.decrypt('k', fara, 'tenant-1'), 'AAD_MISMATCH', 'aad in plus');
kms.close();
});
proba('criptare: alta cheie refuzata cu KEY_MISMATCH', () => {
const { kms } = nouKms();
kms.createKey('a', { type: 'encrypt' });
const b = kms.createKey('b', { type: 'encrypt' });
const ct = kms.encrypt('a', 'x').ciphertext;
egal(kms.decrypt('a', ct).plaintext.toString(), 'x', 'cheia proprie');
refuza(() => kms.decrypt('b', ct), 'KEY_MISMATCH', 'alta cheie');
// si cu amprenta rescrisa pe a cheii b: legatura criptografica tot refuza
const fals = reencode(ct, (body) => {
Buffer.from(b.versions['1'].fingerprint, 'hex').copy(body, OFF.fp);
});
refuza(() => kms.decrypt('b', fals), 'HEADER_AUTH_FAILED', 'amprenta falsificata');
kms.close();
});
proba('criptare: alta versiune refuzata cu VERSION_MISMATCH', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const ct1 = kms.encrypt('k', 'x').ciphertext;
const d2 = kms.rotate('k');
egal(kms.decrypt('k', ct1).plaintext.toString(), 'x', 'v1 decripteaza dupa rotire');
const doarPrefix = ct1.replace(/^aerekms:v1:/, 'aerekms:v2:');
refuza(() => kms.decrypt('k', doarPrefix), 'VERSION_MISMATCH', 'prefix schimbat');
const prefixSiCorp = reencode(ct1, (b) => {
b.writeUInt32BE(2, OFF.ver);
return 2;
});
refuza(() => kms.decrypt('k', prefixSiCorp), 'VERSION_MISMATCH', 'prefix si corp schimbate, amprenta v1');
const totul = reencode(ct1, (b) => {
b.writeUInt32BE(2, OFF.ver);
Buffer.from(d2.versions['2'].fingerprint, 'hex').copy(b, OFF.fp);
return 2;
});
refuza(() => kms.decrypt('k', totul), 'HEADER_AUTH_FAILED', 'versiune si amprenta falsificate');
const inexistenta = reencode(ct1, (b) => {
b.writeUInt32BE(7, OFF.ver);
return 7;
});
refuza(() => kms.decrypt('k', inexistenta), 'UNKNOWN_VERSION', 'versiune inexistenta');
kms.close();
});
proba('criptare: base64 necanonic refuzat cu MALFORMED_CIPHERTEXT', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const ct = kms.encrypt('k', 'a').ciphertext; // corp 1186 octeti -> "==" la coada
const ALF = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
adevarat(ct.endsWith('=='), 'forma asteptata a probei (corp cu padding)');
const i = ct.length - 3;
const rau = ct.slice(0, i) + ALF[ALF.indexOf(ct[i]) ^ 1] + ct.slice(i + 1);
adevarat(Buffer.from(rau.split(':')[2], 'base64').equals(Buffer.from(ct.split(':')[2], 'base64')), 'CONTROL: variantele decodeaza la aceiasi octeti');
adevarat(rau !== ct, 'CONTROL: textul difera');
refuza(() => kms.decrypt('k', rau), 'MALFORMED_CIPHERTEXT', 'base64 necanonic');
kms.close();
});
proba('criptare: atacatorul cu doar secretul X25519 nu poate deriva cheia plicului', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt', exportable: true });
const exp = kms.exportKey('k');
const ct = kms.encrypt('k', 'tinta', 'ctx').ciphertext;
const { ssX, ssK } = secreteManual(ct, exp);
const z = Buffer.alloc(32);
for (const ikm of [ssX, Buffer.concat([z, ssX]), Buffer.concat([ssX, z])]) {
let deschis = false;
try {
openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm, aad: Buffer.from('ctx') });
deschis = true;
} catch {
/* asteptat */
}
adevarat(!deschis, 'plicul s-a deschis numai cu secretul X25519');
}
// control pozitiv al metodei: cu AMBELE secrete, aceeasi cale deschide plicul
egal(openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm: Buffer.concat([ssK, ssX]), aad: Buffer.from('ctx') }).toString(), 'tinta', 'CONTROL POZITIV');
kms.close();
});
proba('criptare: atacatorul cu doar secretul ML-KEM nu poate deriva cheia plicului', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt', exportable: true });
const exp = kms.exportKey('k');
const ct = kms.encrypt('k', 'tinta', 'ctx').ciphertext;
const { ssX, ssK } = secreteManual(ct, exp);
const z = Buffer.alloc(32);
for (const ikm of [ssK, Buffer.concat([ssK, z]), Buffer.concat([z, ssK])]) {
let deschis = false;
try {
openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm, aad: Buffer.from('ctx') });
deschis = true;
} catch {
/* asteptat */
}
adevarat(!deschis, 'plicul s-a deschis numai cu secretul ML-KEM');
}
egal(openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm: Buffer.concat([ssK, ssX]), aad: Buffer.from('ctx') }).toString(), 'tinta', 'CONTROL POZITIV');
kms.close();
});
// ---------------------------------------------------------------------------------- interoperabilitate
proba('interop: plicul serviciului decapsulat si decriptat manual cu node:crypto', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt', exportable: true });
const exp = kms.exportKey('k');
const ct = kms.encrypt('k', 'interoperabil', 'aad-1').ciphertext;
const { ssX, ssK } = secreteManual(ct, exp);
const pt = openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm: Buffer.concat([ssK, ssX]), aad: Buffer.from('aad-1') });
egal(pt.toString(), 'interoperabil', 'decriptare manuala');
let err = '';
try {
openManual(ct, { name: 'k', pubVer: d.versions['1'], ikm: Buffer.concat([ssK, ssX]), aad: Buffer.from('aad-2') });
} catch (e) {
err = e.message;
}
egal(err, 'aad', 'implementarea manuala refuza alt aad pe eticheta aad');
kms.close();
});
proba('interop: plic construit manual din cheile publice e decriptat de serviciu', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt' });
const ct = sealManual({ name: 'k', ver: 1, pubVer: d.versions['1'], pt: Buffer.from('de la client'), aad: Buffer.from('a') });
egal(kms.decrypt('k', ct, 'a').plaintext.toString(), 'de la client', 'serviciul decripteaza plicul clientului');
refuza(() => kms.decrypt('k', ct, 'b'), 'AAD_MISMATCH', 'alt aad pe plicul clientului');
kms.close();
});
proba('interop: plic construit fara unul din secrete e refuzat de serviciu cu HEADER_AUTH_FAILED', () => {
const { kms } = nouKms();
const d = kms.createKey('k', { type: 'encrypt' });
for (const mod of ['doar-x', 'doar-k']) {
const ct = sealManual({ name: 'k', ver: 1, pubVer: d.versions['1'], pt: Buffer.from('x'), aad: Buffer.alloc(0), mod });
refuza(() => kms.decrypt('k', ct), 'HEADER_AUTH_FAILED', `KDF ${mod}`);
}
kms.close();
});
// ---------------------------------------------------------------------------------- rotire si minim
proba('rotire: versiune noua, criptarea foloseste ultima, versiunile vechi decripteaza', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const c1 = kms.encrypt('k', 'unu').ciphertext;
const r = kms.rotate('k');
egal(r.latest_version, 2, 'ultima versiune');
const c2 = kms.encrypt('k', 'doi').ciphertext;
adevarat(c2.startsWith('aerekms:v2:'), 'criptarea foloseste v2');
egal(kms.decrypt('k', c1).plaintext.toString(), 'unu', 'v1 decripteaza');
egal(kms.decrypt('k', c2).plaintext.toString(), 'doi', 'v2 decripteaza');
adevarat(r.versions['1'].fingerprint !== r.versions['2'].fingerprint, 'amprente diferite');
kms.close();
});
proba('rotire: sub min_decryption_version decriptarea e refuzata cu VERSION_BELOW_MINIMUM', () => {
const { kms, dir, root } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const c1 = kms.encrypt('k', 'unu').ciphertext;
kms.rotate('k');
const c2 = kms.encrypt('k', 'doi').ciphertext;
kms.setMinDecryptionVersion('k', 2);
refuza(() => kms.decrypt('k', c1), 'VERSION_BELOW_MINIMUM', 'v1 sub minim');
refuza(() => kms.rewrap('k', c1), 'VERSION_BELOW_MINIMUM', 'rewrap v1 sub minim');
egal(kms.decrypt('k', c2).plaintext.toString(), 'doi', 'v2 la minim decripteaza');
kms.close();
const k2 = openKms({ dataDir: dir, rootKey: root });
refuza(() => k2.decrypt('k', c1), 'VERSION_BELOW_MINIMUM', 'minimul persista dupa repornire');
k2.close();
});
proba('rotire: sub min_decryption_version verificarea semnaturii e refuzata cu VERSION_BELOW_MINIMUM', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s1 = kms.sign('s', 'm').signature;
kms.rotate('s');
const s2 = kms.sign('s', 'm').signature;
adevarat(s2.startsWith('aerekms:v2:'), 'semnarea foloseste v2');
egal(kms.verify('s', 'm', s1).valid, true, 'v1 verifica inainte de minim');
kms.setMinDecryptionVersion('s', 2);
const r = kms.verify('s', 'm', s1);
egal(r.valid, false, 'v1 sub minim');
egal(r.reason, 'VERSION_BELOW_MINIMUM', 'motivul');
egal(kms.verify('s', 'm', s2).valid, true, 'v2 verifica');
kms.close();
});
proba('rotire: minimul nu coboara (MIN_VERSION_NOT_MONOTONIC) si nu trece de ultima (VERSION_OUT_OF_RANGE)', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
kms.rotate('k');
kms.setMinDecryptionVersion('k', 2);
refuza(() => kms.setMinDecryptionVersion('k', 1), 'MIN_VERSION_NOT_MONOTONIC', 'coborare');
refuza(() => kms.setMinDecryptionVersion('k', 3), 'VERSION_OUT_OF_RANGE', 'peste ultima');
refuza(() => kms.setMinDecryptionVersion('k', 0), 'VERSION_OUT_OF_RANGE', 'zero');
refuza(() => kms.setMinDecryptionVersion('k', 1.5), 'VERSION_OUT_OF_RANGE', 'fractie');
egal(kms.setMinDecryptionVersion('k', 2).min_decryption_version, 2, 'acelasi minim e permis');
kms.close();
});
// ---------------------------------------------------------------------------------- rewrap si datakey
proba('rewrap: reincapsuleaza la ultima versiune, acelasi text clar, raspunsul nu contine text clar', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const MARCAJ = 'marcaj-rewrap-' + crypto.randomBytes(6).toString('hex');
const c1 = kms.encrypt('k', MARCAJ, 'ctx').ciphertext;
kms.rotate('k');
const r = kms.rewrap('k', c1, 'ctx');
egal(Object.keys(r).sort().join(','), 'ciphertext,version', 'campurile raspunsului');
egal(r.version, 2, 'versiunea noua');
adevarat(r.ciphertext.startsWith('aerekms:v2:'), 'prefix v2');
adevarat(!JSON.stringify(r).includes(MARCAJ) && !JSON.stringify(r).includes(b64(MARCAJ)), 'textul clar in raspuns');
egal(kms.decrypt('k', r.ciphertext, 'ctx').plaintext.toString(), MARCAJ, 'acelasi text clar');
refuza(() => kms.rewrap('k', c1, 'alt'), 'AAD_MISMATCH', 'rewrap cu alt aad');
refuza(() => kms.decrypt('k', r.ciphertext, 'alt'), 'AAD_MISMATCH', 'aad-ul ramane legat dupa rewrap');
kms.close();
});
proba('datakey: cheia clara si cea invelita corespund; forma doar-invelita nu intoarce cheia clara', () => {
const { kms } = nouKms();
kms.createKey('k', { type: 'encrypt' });
const dk = kms.datakey('k', { aad: 'fisier-7' });
const clar = Buffer.from(dk.plaintext, 'base64');
egal(clar.length, 32, '256 de biti');
adevarat(kms.decrypt('k', dk.ciphertext, 'fisier-7').plaintext.equals(clar), 'invelita decripteaza la cea clara');
refuza(() => kms.decrypt('k', dk.ciphertext, 'fisier-8'), 'AAD_MISMATCH', 'cheia de date legata de aad');
const doar = kms.datakey('k', { includePlaintext: false });
adevarat(!('plaintext' in doar), 'forma doar-invelita are camp plaintext');
egal(kms.decrypt('k', doar.ciphertext).plaintext.length, 32, 'forma doar-invelita decripteaza');
egal(Buffer.from(kms.datakey('k', { bits: 512 }).plaintext, 'base64').length, 64, '512 biti');
refuza(() => kms.datakey('k', { bits: 100 }), 'INVALID_BITS', 'biti invalizi');
kms.close();
});
// ---------------------------------------------------------------------------------- semnare
proba('semnare: semnatura hibrida se verifica, cu ambele jumatati raportate', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s = kms.sign('s', 'mesaj');
adevarat(/^aerekms:v1:/.test(s.signature), 'forma');
const r = kms.verify('s', 'mesaj', s.signature);
egal(r.valid, true, 'valida');
egal(r.classical, true, 'Ed25519');
egal(r.post_quantum, true, 'ML-DSA-65');
egal(r.reason, null, 'fara motiv');
kms.close();
});
proba('semnare: jumatatea Ed25519 stricata e refuzata cu CLASSICAL_SIGNATURE_INVALID', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s = kms.sign('s', 'mesaj').signature;
const rau = sigWith(s, (b) => {
b[17 + 10] ^= 0x01;
});
const r = kms.verify('s', 'mesaj', rau);
egal(r.valid, false, 'valid');
egal(r.reason, 'CLASSICAL_SIGNATURE_INVALID', 'motivul');
egal(r.post_quantum, true, 'jumatatea PQ ramasa buna');
kms.close();
});
proba('semnare: jumatatea ML-DSA-65 stricata e refuzata cu PQ_SIGNATURE_INVALID', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s = kms.sign('s', 'mesaj').signature;
const rau = sigWith(s, (b) => {
b[81 + 1000] ^= 0x01;
});
const r = kms.verify('s', 'mesaj', rau);
egal(r.valid, false, 'valid');
egal(r.reason, 'PQ_SIGNATURE_INVALID', 'motivul');
egal(r.classical, true, 'jumatatea clasica ramasa buna');
kms.close();
});
proba('semnare: ambele jumatati stricate sau mesaj schimbat refuzate cu BOTH_SIGNATURES_INVALID', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s = kms.sign('s', 'mesaj').signature;
const rau = sigWith(s, (b) => {
b[17 + 10] ^= 0x01;
b[81 + 1000] ^= 0x01;
});
egal(kms.verify('s', 'mesaj', rau).reason, 'BOTH_SIGNATURES_INVALID', 'ambele stricate');
const r = kms.verify('s', 'mesaj!', s);
egal(r.valid, false, 'mesaj schimbat');
egal(r.reason, 'BOTH_SIGNATURES_INVALID', 'motivul la mesaj schimbat');
kms.close();
});
proba('semnare: jumatati amestecate din doua semnaturi valide sunt refuzate', () => {
const { kms } = nouKms();
kms.createKey('s', { type: 'sign' });
const s1 = kms.sign('s', 'unu').signature;
const s2 = kms.sign('s', 'doi').signature;
const p2 = sigParts(s2);
const amestec = sigWith(s1, (b) => {
p2.ml.copy(b, 81);
});
const r1 = kms.verify('s', 'unu', amestec);
egal(r1.valid, false, 'amestec peste "unu"');
egal(r1.reason, 'PQ_SIGNATURE_INVALID', 'motivul peste "unu"');
const r2 = kms.verify('s', 'doi', amestec);
egal(r2.valid, false, 'amestec peste "doi"');
egal(r2.reason, 'CLASSICAL_SIGNATURE_INVALID', 'motivul peste "doi"');
kms.close();
});
proba('semnare: alta cheie refuzata cu KEY_MISMATCH, semnatura malformata cu MALFORMED_SIGNATURE', () => {
const { kms } = nouKms();
kms.createKey('a', { type: 'sign' });
kms.createKey('b', { type: 'sign' });
const s = kms.sign('a', 'm').signature;
egal(kms.verify('a', 'm', s).valid, true, 'cheia proprie');
const r = kms.verify('b', 'm', s);
egal(r.valid, false, 'alta cheie');
egal(r.reason, 'KEY_MISMATCH', 'motivul');
egal(kms.verify('a', 'm', s.slice(0, -12)).reason, 'MALFORMED_SIGNATURE', 'trunchiata');
egal(kms.verify('a', 'm', 'nu-e-semnatura').reason, 'MALFORMED_SIGNATURE', 'gunoi');
egal(kms.verify('a', 'm', s.replace('aerekms:v1:', 'aerekms:v2:')).reason, 'VERSION_MISMATCH', 'prefix schimbat');
kms.close();
});
proba('interop: semnatura verificata manual cu node:crypto; jumatatile nu sunt semnaturi ale mesajului brut', () => {
const { kms } = nouKms();
const d = kms.createKey('s', { type: 'sign' });
const pub = d.versions['1'];
const s = kms.sign('s', 'document').signature;
const p = sigParts(s);
const m = sigMessageManual(pub.fingerprint, 1, 'document');
const edPub = ed25519Pub(Buffer.from(pub.public.ed25519, 'base64'));
const mlPub = spki(pub.public.ml_dsa_65);
adevarat(crypto.verify(null, m, edPub, p.ed), 'Ed25519 verifica manual');
adevarat(crypto.verify(null, m, { key: mlPub, context: Buffer.from(L.sigCtx) }, p.ml), 'ML-DSA-65 verifica manual');
// neseparabilitate: nicio jumatate nu e o semnatura valida peste mesajul brut
adevarat(!crypto.verify(null, Buffer.from('document'), edPub, p.ed), 'Ed25519 peste mesajul brut');
adevarat(!crypto.verify(null, Buffer.from('document'), mlPub, p.ml), 'ML-DSA peste mesajul brut fara context');
adevarat(!crypto.verify(null, m, mlPub, p.ml), 'ML-DSA fara contextul aerekms');
kms.close();
});
// ---------------------------------------------------------------------------------- jurnalul de audit
function jurnalCu(n) {
const { kms, dir, root } = nouKms();
kms.createKey('k', { type: 'encrypt' });
for (let i = 0; i < n; i++) kms.encrypt('k', `t${i}`);
return { kms, dir, root, f: path.join(dir, 'audit.log') };
}
const randuri = (f) => fs.readFileSync(f, 'utf8').split('\n').filter((l) => l !== '');
const scrieRanduri = (f, rs) => fs.writeFileSync(f, rs.join('\n') + '\n');
// A4 (revizuirea din 2026-09-25): cand randul de audit nu se poate scrie, MUTATIA nu are voie sa ramana pe disc; inainte, rotirea si
// retragerea versiunii ramaneau in fisierul cheii si lantul de audit iesea valid fara nicio urma a lor.
proba('audit: o mutatie al carei rand de audit nu se poate scrie NU ramane pe disc (rotire, minim, creare), iar lantul ramane valid', () => {
const { kms, dir, f } = jurnalCu(1);
const cheie = path.join(dir, 'keys', 'k.json');
const inainte = fs.readFileSync(cheie);
fs.renameSync(f, f + '.deoparte'); fs.mkdirSync(f); // in locul jurnalului, un dosar: append-ul cade
try {
refuza(() => kms.rotate('k'), 'AUDIT_WRITE_FAILED', 'rotire cu jurnal nescriibil');
egal(fs.readFileSync(cheie).equals(inainte), true, 'rotirea a ramas pe disc fara rand de audit');
refuza(() => kms.createKey('nou', { type: 'encrypt' }), 'AUDIT_WRITE_FAILED', 'creare cu jurnal nescriibil');
egal(fs.existsSync(path.join(dir, 'keys', 'nou.json')), false, 'cheia creata a ramas pe disc fara rand de audit');
} finally {
fs.rmdirSync(f); fs.renameSync(f + '.deoparte', f);
}
// jurnalul la loc: starea vizibila e cea de dinaintea esecurilor, si lantul se verifica
egal(JSON.parse(fs.readFileSync(cheie, 'utf8')).latest_version, 1, 'versiunea dupa esec');
egal(kms.getKey('k').latest_version, 1, 'versiunea citita prin serviciu dupa esec');
egal(kms.verifyAudit().ok, true, 'lantul de audit dupa esecuri');
// controlul pozitiv al fixturii: cu jurnalul scriibil, aceeasi rotire chiar se face si se vede in fisier
kms.rotate('k');
egal(JSON.parse(fs.readFileSync(cheie, 'utf8')).latest_version, 2, 'rotirea cu jurnal scriibil');
kms.close();
});
proba('audit: jurnalul neatins se verifica OK, un rand pe operatie, si refuzurile au motivul lor', () => {
const { kms, f } = jurnalCu(3);
try {
kms.decrypt('k', kms.encrypt('k', 'x', 'a').ciphertext, 'b');
} catch {
/* refuz asteptat */
}
const v = kms.verifyAudit();
egal(v.ok, true, `jurnal neatins (${v.reason} la randul ${v.line})`);
egal(v.rows, 6, 'randuri: create + 3 encrypt + encrypt + decrypt refuzat');
const ultim = JSON.parse(randuri(f).at(-1));
egal(ultim.op, 'decrypt', 'ultima operatie');
egal(ultim.ok, false, 'refuzul e scris ca refuz');
egal(ultim.reason, 'AAD_MISMATCH', 'motivul refuzului');
egal(JSON.stringify(kms.auditHead()), JSON.stringify(v.head), 'capul din memorie = capul din fisier');
kms.close();
});
proba('audit: rand sters detectat cu AUDIT_CHAIN_BROKEN pe randul lui', () => {
const { kms, f } = jurnalCu(5);
const rs = randuri(f);
rs.splice(3, 1);
scrieRanduri(f, rs);
const v = kms.verifyAudit();
egal(v.ok, false, 'rand sters');
egal(v.reason, 'AUDIT_CHAIN_BROKEN', 'motivul');
egal(v.line, 4, 'randul');
kms.close();
});
proba('audit: rand schimbat detectat cu AUDIT_ROW_MODIFIED', () => {
const { kms, f } = jurnalCu(5);
const rs = randuri(f);
const r = JSON.parse(rs[2]);
r.op = 'decrypt';
rs[2] = JSON.stringify(r);
scrieRanduri(f, rs);
const v = kms.verifyAudit();
egal(v.ok, false, 'rand schimbat');
egal(v.reason, 'AUDIT_ROW_MODIFIED', 'motivul');
egal(v.line, 3, 'randul');
kms.close();
});
proba('audit: randuri reordonate detectate cu AUDIT_CHAIN_BROKEN', () => {
const { kms, f } = jurnalCu(5);
const rs = randuri(f);
[rs[1], rs[2]] = [rs[2], rs[1]];
scrieRanduri(f, rs);
const v = kms.verifyAudit();
egal(v.ok, false, 'reordonare');
egal(v.reason, 'AUDIT_CHAIN_BROKEN', 'motivul');
egal(v.line, 2, 'randul');
kms.close();
});
proba('audit: coada taiata se vede numai fata de un cap ancorat (AUDIT_TRUNCATED)', () => {
const { kms, f } = jurnalCu(4);
const cap = kms.auditHead();
egal(kms.verifyAudit({ expectedHead: cap }).ok, true, 'cu capul ancorat, jurnal neatins');
const rs = randuri(f);
scrieRanduri(f, rs.slice(0, -1));
egal(kms.verifyAudit().ok, true, 'LIMITA DOCUMENTATA: fara cap ancorat, taierea cozii nu se vede');
const v = kms.verifyAudit({ expectedHead: cap });
egal(v.ok, false, 'coada taiata fata de cap');
egal(v.reason, 'AUDIT_TRUNCATED', 'motivul');
kms.close();
});
proba('audit: pornirea refuza un jurnal stricat cu AUDIT_CHAIN_INVALID', () => {
const { kms, f, dir, root } = jurnalCu(3);
kms.close();
const k0 = openKms({ dataDir: dir, rootKey: root });
k0.close();
const rs = randuri(f);
rs.splice(1, 1);
scrieRanduri(f, rs);
refuza(() => openKms({ dataDir: dir, rootKey: root }), 'AUDIT_CHAIN_INVALID', 'pornire pe jurnal stricat');
});
proba('audit: jurnalul si fisierele nu contin text clar, aad, mesaje sau chei', () => {
const { kms, dir } = nouKms();
const PT = 'MARCAJ-PT-' + crypto.randomBytes(6).toString('hex');
const AAD = 'MARCAJ-AAD-' + crypto.randomBytes(6).toString('hex');
const MSG = 'MARCAJ-MSG-' + crypto.randomBytes(6).toString('hex');
kms.createKey('e', { type: 'encrypt', exportable: true });
kms.createKey('s', { type: 'sign', exportable: true });
const c = kms.encrypt('e', PT, AAD).ciphertext;
kms.decrypt('e', c, AAD);
kms.rotate('e');
kms.rewrap('e', c, AAD);
kms.datakey('e', { aad: AAD });
kms.verify('s', MSG, kms.sign('s', MSG).signature);
try {
kms.decrypt('e', c, AAD + 'x');
} catch {
/* asteptat */
}
const needles = [...needlesDin(kms.exportKey('e')), ...needlesDin(kms.exportKey('s'))];
const log = fs.readFileSync(path.join(dir, 'audit.log'), 'utf8');
for (const m of [PT, AAD, MSG]) {
for (const forma of [m, b64(m), Buffer.from(m).toString('hex')]) adevarat(!log.includes(forma), `jurnalul contine ${m.slice(0, 11)}`);
}
egal(gasesteMaterial([path.join(dir, 'audit.log')], needles).length, 0, 'material de cheie in jurnal');
for (const f of toateFisierele(dir)) {
const t = fs.readFileSync(f, 'utf8');
for (const m of [PT, AAD, MSG]) adevarat(!t.includes(m) && !t.includes(b64(m)), `${path.basename(f)} contine un marcaj`);
}
kms.close();
});
// ---------------------------------------------------------------------------------- erori
proba('erori: niciun mesaj de eroare nu contine material de cheie', () => {
const root = rootHex();
const { kms } = nouKms(tmpDir(), root);
kms.createKey('e', { type: 'encrypt', exportable: true });
kms.createKey('s', { type: 'sign', exportable: true });
const ne = needlesDin(kms.exportKey('e'));
const ns = needlesDin(kms.exportKey('s'));
const forme = [root, root.toUpperCase(), Buffer.from(root, 'hex').toString('base64')];
for (const n of [...ne, ...ns]) forme.push(n.toString('base64'), n.toString('hex'), n.toString('base64url'));
const ct = kms.encrypt('e', 'x', 'a').ciphertext;
const sig = kms.sign('s', 'm').signature;
const erori = [];
const prinde = (f) => {
try {
f();
} catch (e) {
erori.push(e);
}
};
prinde(() => kms.decrypt('e', ct, 'b'));
prinde(() => kms.decrypt('e', reencode(ct, (b) => void (b[OFF.ctK] ^= 1))));
prinde(() => kms.decrypt('e', reencode(ct, (b) => void (b[OFF.payload] ^= 1)), 'a'));
prinde(() => kms.decrypt('e', 'aerekms:v1:AAAA'));
prinde(() => kms.sign('e', 'm'));
prinde(() => kms.exportKey('e', 9));
prinde(() => kms.setMinDecryptionVersion('e', 5));
prinde(() => openKms({ dataDir: tmpDir(), rootKey: root.slice(2) }));
prinde(() => openKms({ dataDir: kms.dataDir, rootKey: rootHex() }));
egal(erori.length, 9, 'toate cele noua operatii trebuiau refuzate');
const vs = kms.verify('s', 'n', sig);
const texte = [...erori.map((e) => `${e.message} ${JSON.stringify({ ...e })}`), JSON.stringify(vs)];
for (const t of texte) for (const f of forme) adevarat(!t.includes(f), 'un mesaj de eroare contine material de cheie');
kms.close();
});
// ---------------------------------------------------------------------------------- server
proba('server: configurarea implicita asculta pe 127.0.0.1 si cere token si cheie radacina', () => {
const token = crypto.randomBytes(24).toString('hex');
const root = rootHex();
const c = configFromEnv({ AERE_KMS_TOKEN: token, AERE_KMS_ROOT_KEY: root });
egal(c.host, '127.0.0.1', 'gazda implicita');
egal(c.port, 8420, 'portul implicit');
refuza(() => configFromEnv({ AERE_KMS_ROOT_KEY: root }), 'TOKEN_MISSING', 'fara token');
refuza(() => configFromEnv({ AERE_KMS_TOKEN: 'scurt', AERE_KMS_ROOT_KEY: root }), 'TOKEN_TOO_SHORT', 'token scurt');
refuza(() => configFromEnv({ AERE_KMS_TOKEN: token }), 'ROOT_KEY_MISSING', 'fara cheie radacina');
refuza(() => configFromEnv({ AERE_KMS_TOKEN: root, AERE_KMS_ROOT_KEY: root }), 'TOKEN_EQUALS_ROOT_KEY', 'token = radacina');
});
proba('server: pornit din linia de comanda refuza fara AERE_KMS_ROOT_KEY si nu tipareste tokenul', () => {
const dir = path.join(tmpDir('cli'), 'date');
const token = 'tok-' + crypto.randomBytes(20).toString('hex');
const r = spawnSync(process.execPath, [SERVER_PATH], {
env: { ...envCurat(), AERE_KMS_TOKEN: token, AERE_KMS_DATA_DIR: dir, AERE_KMS_PORT: '0' },
encoding: 'utf8',
timeout: 20000,
});
egal(r.status, 1, `codul de iesire (stderr: ${String(r.stderr).slice(0, 200)})`);
adevarat(r.stderr.includes('ROOT_KEY_MISSING'), 'motivul pe stderr');
adevarat(!r.stderr.includes(token) && !r.stdout.includes(token), 'tokenul tiparit');
adevarat(!fs.existsSync(dir), 'dosarul de date creat fara cheie radacina');
});
proba('server: pornit din linia de comanda asculta pe 127.0.0.1 si raspunde', async () => {
const token = 'tok-' + crypto.randomBytes(20).toString('hex');
const root = rootHex();
const child = spawn(process.execPath, [SERVER_PATH], {
env: { ...envCurat(), AERE_KMS_TOKEN: token, AERE_KMS_ROOT_KEY: root, AERE_KMS_DATA_DIR: path.join(tmpDir('cli'), 'date'), AERE_KMS_PORT: '0' },
stdio: ['ignore', 'pipe', 'pipe'],
});
let out = '';
let err = '';
child.stderr.on('data', (c) => (err += c));
try {
const adresa = await new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Esec(`serverul nu a pornit in 15 s (stderr: ${err.slice(0, 200)})`)), 15000);
child.stdout.on('data', (c) => {
out += c;
const m = /listening on ([0-9.]+):(\d+)/.exec(out);
if (m) {
clearTimeout(t);
resolve({ host: m[1], port: Number(m[2]) });
}
});
child.on('exit', (code) => reject(new Esec(`serverul a iesit cu ${code}: ${err.slice(0, 200)}`)));
});
egal(adresa.host, '127.0.0.1', 'adresa de ascultare');
const h = await cerere(adresa.port, 'GET', '/v1/keys', { headers: { authorization: `Bearer ${token}` } });
egal(h.status, 200, 'cerere autentificata');
const n = await cerere(adresa.port, 'GET', '/v1/keys');
egal(n.status, 401, 'cerere neautentificata');
adevarat(!out.includes(token) && !out.includes(root) && !err.includes(token) && !err.includes(root), 'secret tiparit');
} finally {
child.kill();
await new Promise((r) => (child.exitCode !== null ? r() : child.on('exit', r)));
}
});
proba('server: fara Authorization raspunde 401 UNAUTHENTICATED', async () => {
await cuServer(async ({ port, auth }) => {
const ok = await cerere(port, 'GET', '/v1/keys', { headers: auth });
egal(ok.status, 200, 'cu token');
for (const [m, p, body] of [['GET', '/v1/keys'], ['POST', '/v1/keys/k', { type: 'encrypt' }], ['POST', '/v1/encrypt/k', { plaintext: b64('x') }]]) {
const r = await cerere(port, m, p, { body });
egal(r.status, 401, `${m} ${p} fara token`);
egal(r.json && r.json.error, 'UNAUTHENTICATED', `${m} ${p} motivul`);
}
});
});
proba('server: token gresit raspunde 401 INVALID_TOKEN, fara sa-l repete', async () => {
await cuServer(async ({ port, token }) => {
const gresit = token.slice(0, -1) + (token.endsWith('a') ? 'b' : 'a');
for (const h of [`Bearer ${gresit}`, `Basic ${token}`, `Bearer`, `bearer ${token}`]) {
const r = await cerere(port, 'GET', '/v1/keys', { headers: { authorization: h } });
egal(r.status, 401, `antet ${h.slice(0, 7)}`);
egal(r.json && r.json.error, 'INVALID_TOKEN', `motivul pentru ${h.slice(0, 7)}`);
adevarat(!r.text.includes(token) && !r.text.includes(gresit), 'raspunsul repeta tokenul');
}
});
});
proba('server: cerere peste limita cu Content-Length raspunde 413 BODY_TOO_LARGE', async () => {
await cuServer(async ({ port, auth }) => {
await cerere(port, 'POST', '/v1/keys/k', { headers: auth, body: { type: 'encrypt' } });
const sub = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: { plaintext: b64('x'.repeat(2000)) } });
egal(sub.status, 200, 'sub limita trece');
const peste = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: { plaintext: b64('x'.repeat(6000)) } });
egal(peste.status, 413, 'peste limita');
egal(peste.json && peste.json.error, 'BODY_TOO_LARGE', 'motivul');
});
});
proba('server: cerere peste limita fara Content-Length (chunked) raspunde 413 BODY_TOO_LARGE', async () => {
await cuServer(async ({ port, auth }) => {
await cerere(port, 'POST', '/v1/keys/k', { headers: auth, body: { type: 'encrypt' } });
const sub = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: { plaintext: b64('y'.repeat(2000)) }, chunked: true });
egal(sub.status, 200, 'sub limita, chunked, trece');
const peste = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: { plaintext: b64('y'.repeat(9000)) }, chunked: true });
egal(peste.status, 413, 'peste limita, chunked');
egal(peste.json && peste.json.error, 'BODY_TOO_LARGE', 'motivul');
});
});
proba('server: JSON invalid 400 INVALID_JSON, base64 invalid 400 INVALID_BASE64, ruta necunoscuta 404 NOT_FOUND', async () => {
await cuServer(async ({ port, auth }) => {
await cerere(port, 'POST', '/v1/keys/k', { headers: auth, body: { type: 'encrypt' } });
const j = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: '{"plaintext":' });
egal(j.status, 400, 'JSON invalid');
egal(j.json.error, 'INVALID_JSON', 'motivul JSON');
const b = await cerere(port, 'POST', '/v1/encrypt/k', { headers: auth, body: { plaintext: 'nu e base64!' } });
egal(b.json.error, 'INVALID_BASE64', 'motivul base64');
const n = await cerere(port, 'GET', '/v1/nimic', { headers: auth });
egal(n.status, 404, 'ruta');
egal(n.json.error, 'NOT_FOUND', 'motivul rutei');
const nm = await cerere(port, 'GET', '/v1/keys/..%2Fx', { headers: auth });
egal(nm.json.error, 'INVALID_KEY_NAME', 'nume cu traversare in cale');
});
});
proba('server: flux complet prin HTTP (creare, criptare, decriptare, rotire, rewrap, datakey, semnare, verificare, audit)', async () => {
await cuServer(async ({ port, auth }) => {
const P = (p, body) => cerere(port, 'POST', p, { headers: auth, body });
const G = (p) => cerere(port, 'GET', p, { headers: auth });
egal((await P('/v1/keys/e', { type: 'encrypt' })).status, 200, 'creare e');
egal((await P('/v1/keys/s', { type: 'sign' })).status, 200, 'creare s');
const dup = await P('/v1/keys/e', { type: 'encrypt' });
egal(dup.status, 409, 'dubla');
egal(dup.json.error, 'KEY_EXISTS', 'motivul dublei');
const enc = await P('/v1/encrypt/e', { plaintext: b64('prin http'), aad: b64('ctx') });
egal(enc.status, 200, 'encrypt');
const dec = await P('/v1/decrypt/e', { ciphertext: enc.json.ciphertext, aad: b64('ctx') });
egal(Buffer.from(dec.json.plaintext, 'base64').toString(), 'prin http', 'decrypt');
const rau = await P('/v1/decrypt/e', { ciphertext: enc.json.ciphertext, aad: b64('alt') });
egal(rau.status, 400, 'decrypt cu alt aad');
egal(rau.json.error, 'AAD_MISMATCH', 'motivul prin HTTP');
egal((await P('/v1/keys/e/rotate', {})).json.latest_version, 2, 'rotire');
const rw = await P('/v1/rewrap/e', { ciphertext: enc.json.ciphertext, aad: b64('ctx') });
egal(rw.json.version, 2, 'rewrap');
adevarat(!('plaintext' in rw.json), 'rewrap nu intoarce text clar');
egal((await P('/v1/keys/e/config', { min_decryption_version: 2 })).json.min_decryption_version, 2, 'config');
const vechi = await P('/v1/decrypt/e', { ciphertext: enc.json.ciphertext, aad: b64('ctx') });
egal(vechi.json.error, 'VERSION_BELOW_MINIMUM', 'sub minim prin HTTP');
const dk = await P('/v1/datakey/e', { include_plaintext: false });
adevarat(!('plaintext' in dk.json) && dk.json.ciphertext.startsWith('aerekms:v2:'), 'datakey doar invelita');
const ex = await G('/v1/keys/e/export');
egal(ex.status, 403, 'export refuzat');
egal(ex.json.error, 'KEY_NOT_EXPORTABLE', 'motivul exportului');
const sg = await P('/v1/sign/s', { message: b64('doc') });
egal((await P('/v1/verify/s', { message: b64('doc'), signature: sg.json.signature })).json.valid, true, 'verify');
const vr = await P('/v1/verify/s', { message: b64('doc2'), signature: sg.json.signature });
egal(vr.json.valid, false, 'verify alt mesaj');
egal(vr.json.reason, 'BOTH_SIGNATURES_INVALID', 'motivul verify');
const k = await G('/v1/keys/e');
egal(k.json.latest_version, 2, 'citire cheie');
adevarat(!JSON.stringify(k.json).includes('private'), 'citirea cheii nu expune material privat');
egal(JSON.stringify((await G('/v1/keys')).json.keys), JSON.stringify(['e', 's']), 'lista');
const au = await G('/v1/audit/verify');
egal(au.json.ok, true, 'audit prin HTTP');
}, { maxBody: 8192 });
});
proba('server: limita minima a corpului incape o verificare de semnatura (AERE_KMS_MAX_BODY >= 8192)', async () => {
const token = crypto.randomBytes(24).toString('hex');
const root = rootHex();
refuza(() => configFromEnv({ AERE_KMS_TOKEN: token, AERE_KMS_ROOT_KEY: root, AERE_KMS_MAX_BODY: '4096' }), 'INVALID_MAX_BODY', 'limita sub 8192');
const min = configFromEnv({ AERE_KMS_TOKEN: token, AERE_KMS_ROOT_KEY: root, AERE_KMS_MAX_BODY: '8192' }).maxBody;
await cuServer(async ({ port, auth }) => {
await cerere(port, 'POST', '/v1/keys/s', { headers: auth, body: { type: 'sign' } });
const msg = b64('m'.repeat(1024));
const sg = await cerere(port, 'POST', '/v1/sign/s', { headers: auth, body: { message: msg } });
const vr = await cerere(port, 'POST', '/v1/verify/s', { headers: auth, body: { message: msg, signature: sg.json.signature } });
egal(vr.status, 200, `verificare la limita minima (${min})`);
egal(vr.json.valid, true, 'valida');
}, { maxBody: min });
});
proba('server: raspunsurile nu contin tokenul', async () => {
await cuServer(async ({ port, auth, token }) => {
const rs = [];
rs.push(await cerere(port, 'POST', '/v1/keys/k', { headers: auth, body: { type: 'encrypt' } }));
rs.push(await cerere(port, 'GET', '/v1/keys/k', { headers: auth }));
rs.push(await cerere(port, 'POST', '/v1/decrypt/k', { headers: auth, body: { ciphertext: 'x' } }));
rs.push(await cerere(port, 'GET', '/v1/audit/verify', { headers: auth }));
for (const r of rs) adevarat(!r.text.includes(token), 'tokenul in raspuns');
});
});
// ================================================================================== rulare
async function main() {
const t0 = Date.now();
const rez = [];
for (const p of probe) {
const t = Date.now();
try {
await p.fn();
rez.push({ nume: p.nume, ok: true, ms: Date.now() - t });
} catch (e) {
rez.push({ nume: p.nume, ok: false, ms: Date.now() - t, motiv: String((e && e.message) || e).slice(0, 500), tip: e instanceof Esec ? 'afirmatie' : 'exceptie' });
}
}
for (const d of tmpDirs) {
try {
fs.rmSync(d, { recursive: true, force: true });
} catch {
/* dosar temporar al probei; ramane in %TEMP% daca Windows il tine ocupat */
}
}
const picate = rez.filter((r) => !r.ok);
for (const r of rez) process.stdout.write(`${r.ok ? 'TRECE' : 'PICA '} ${r.nume}${r.ok ? '' : `\n -> ${r.motiv}`}\n`);
process.stdout.write(`\n${rez.length - picate.length}/${rez.length} probe trecute, ${picate.length} picate, ${Date.now() - t0} ms\n`);
if (process.env.AERE_KMS_PROBA_JSON) {
fs.writeFileSync(process.env.AERE_KMS_PROBA_JSON, JSON.stringify({
completed: true,
total: rez.length,
passed: rez.length - picate.length,
failed: picate.map((r) => r.nume),
results: rez,
node: process.versions.node,
openssl: process.versions.openssl,
}, null, 2));
}
process.exitCode = picate.length === 0 ? 0 : 1;
}
main().catch((e) => {
process.stderr.write(`harnessul probei a cazut: ${(e && e.stack) || e}\n`);
process.exitCode = 2;
});