aere-proof-of-software/tools/proof-of-software/test/semnatar.test.mjs
Aere Network 065f84e34a Aere Proof of Software 1.4.0: an attestation of what was built, from what, by whom and when, verifiable without trusting Aere Network
Every artifact's SHA-256 and size, the SBOM's digest, the git commit and tree of the source, a hybrid signature (classical + ML-DSA,
both required) and, when notarized, a first-seen time on Aere Network covered by the validators' post-quantum certificate. verify
recomputes everything from the files; --rebuild-from repeats an npm pack build from a clone the verifier chose; the SBOM is
re-derived from the committed package-lock.json or, new in 1.4.0, from the committed go.mod and go.sum; --signer requires the
signing keys you expect (1.4.0); a developer credential binds the keys to a person, judged against a trust root you choose. The
builder's declared date can only accuse, never acquit (1.4.0). Includes the GitHub Action and the hybrid signature library it uses.

Laid out as in the development repository (tools/proof-of-software/, sdk-pq-sign/, sdk/) so that nothing is rewritten for
publication. Tests and negative controls measured on 2026-09-29 are listed in README.md.
2026-09-29 22:46:57 +03:00

68 lines
5.4 KiB
JavaScript

// Proof of Software, B-18 (2026-09-29): o semnatura valida spune ca NISTE chei au semnat, nu CARE. Pe 1.4.0 un strain isi semna cu
// cheile lui declaratia despre artefactul lui (acelasi nume si versiune) si verificarea spunea VALID fara sa numeasca semnatarul. Acum
// numele verificarii poarta amprenta cheilor, `--signer` cere cheile asteptate, iar fara el (si fara o acreditare judecata) se spune
// nejudecat. Fiecare afirmatie cu perechea ei negativa. Offline.
// node test/semnatar.test.mjs iesire 0 = toate cum trebuia
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { attest, verify, publicKeysOf, issueCredential } from '../pos.mjs';
import * as pq from '../../../sdk-pq-sign/index.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
let treceri = 0; const esecuri = [];
async function test(nume, fn) { try { await fn(); treceri++; console.log(' OK ' + nume); } catch (e) { esecuri.push(nume); console.log(' ESEC ' + nume + ' — ' + (e.message || e)); } }
const cere = (c, m) => { if (!c) throw new Error(m); };
const check = (r, re) => r.checks.find((c) => re.test(c.name));
const D = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-semnatar-'));
const BUN = path.join(D, 'app.tgz'); fs.writeFileSync(BUN, 'artefactul constructorului');
fs.mkdirSync(path.join(D, 'strain')); const RAU = path.join(D, 'strain', 'app.tgz'); fs.writeFileSync(RAU, 'artefactul unui strain');
const dev = pq.generateKeyPair(), strain = pq.generateKeyPair(), org = pq.generateKeyPair();
const aBun = await attest({ artifacts: [BUN], name: 'app', version: '1.0.0', keys: dev, cwd: D });
const aRau = await attest({ artifacts: [RAU], name: 'app', version: '1.0.0', keys: strain, cwd: D });
await test('1. numele verificarii semnaturii poarta amprenta cheilor care au semnat (doua chei -> doua amprente)', async () => {
const r1 = await verify(aBun, [BUN]), r2 = await verify(aRau, [RAU]);
const n1 = check(r1, /^hybrid signature/).name, n2 = check(r2, /^hybrid signature/).name;
cere(/by keys 0x[0-9a-f]{16}/.test(n1) && n1 !== n2, `${n1} | ${n2}`);
});
await test('2. fara --signer si fara acreditare judecata: cine a semnat e raportat NEJUDECAT, nu tacut', async () => {
const r = await verify(aRau, [RAU]);
cere(r.valid && check(r, /^signer$/) && check(r, /^signer$/).pass === null && /anyone can sign/.test(check(r, /^signer$/).detail), JSON.stringify(check(r, /^signer$/)));
});
await test('3. ATAC: atestarea strainului, verificata cu --signer = cheile constructorului -> INVALIDA', async () => {
const r = await verify(aRau, [RAU], { signer: publicKeysOf(dev) });
cere(!r.valid && check(r, /signed by the keys you gave/).pass === false, JSON.stringify(check(r, /signed by the keys you gave/)));
});
await test('4. CONTROL: atestarea constructorului, cu --signer = cheile lui (fisierul public) -> VALIDA', async () => {
const r = await verify(aBun, [BUN], { signer: publicKeysOf(dev) });
cere(r.valid && check(r, /signed by the keys you gave/).pass === true, JSON.stringify(r.checks.filter((c) => c.pass === false)));
});
await test('5. o atestare nesemnata, cu --signer -> INVALIDA (nu "absenta")', async () => {
const a = await attest({ artifacts: [BUN], name: 'app', version: '1.0.0', cwd: D });
const r = await verify(a, [BUN], { signer: publicKeysOf(dev) });
cere(!r.valid && /not signed/.test(check(r, /signed by the keys you gave/).detail), JSON.stringify(check(r, /signed by the keys you gave/)));
});
await test('6. cu o acreditare judecata (--trust-issuer), semnatarul e judecat de acreditare si randul "signer" nu mai apare', async () => {
const cred = issueCredential({ issuerKeys: org, issuerName: 'Org', subjectKeys: publicKeysOf(dev), subjectName: 'Dev', validFrom: '2026-01-01T00:00:00Z', validUntil: '2027-01-01T00:00:00Z' });
const a = await attest({ artifacts: [BUN], name: 'app', version: '1.0.0', keys: dev, credential: cred, cwd: D, now: new Date('2026-06-01T00:00:00Z') });
const r = await verify(a, [BUN], { trustIssuer: publicKeysOf(org) });
cere(!check(r, /^signer$/) && check(r, /names the keys that signed/).pass === true, JSON.stringify(r.checks.map((c) => c.name)));
});
await test('7. linia de comanda: verify --signer (0 pentru constructor, 1 pentru strain), si "not judged" numarat pe randul de verdict', async () => {
const pos = path.join(AICI, '..', 'pos.mjs'); const f = (n) => path.join(D, n);
fs.writeFileSync(f('dev.json'), JSON.stringify(dev));
execFileSync(process.execPath, [pos, 'pubkey', '--keys', f('dev.json'), '--out', f('dev.pub.json')], { stdio: 'pipe' });
fs.writeFileSync(f('bun.json'), JSON.stringify(aBun)); fs.writeFileSync(f('rau.json'), JSON.stringify(aRau));
const run = (args) => { try { return { cod: 0, out: execFileSync(process.execPath, [pos, ...args], { stdio: 'pipe' }).toString() }; } catch (e) { return { cod: e.status, out: String(e.stdout || '') }; } };
const b = run(['verify', f('bun.json'), '--signer', f('dev.pub.json'), BUN]), r = run(['verify', f('rau.json'), '--signer', f('dev.pub.json'), RAU]);
const fara = run(['verify', f('rau.json'), RAU]);
cere(b.cod === 0 && r.cod === 1 && fara.cod === 0 && /not judged/.test(fara.out), `${b.cod} ${r.cod} ${fara.cod} ${fara.out.split('\n').slice(-2).join(' ')}`);
});
fs.rmSync(D, { recursive: true, force: true });
console.log(`\n${treceri} treceri, ${esecuri.length} esecuri`);
process.exitCode = esecuri.length ? 1 : 0;