// Proof of Software: fiecare afirmatie cu perechea ei negativa. Offline; cu AERE_SCRIE=1 si AERE_CHEIE_FISIER face o // notarizare reala (o tranzactie pe 2800) si citeste dovada ei. import assert from 'node:assert'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { execFileSync } from 'node:child_process'; import { attest, verify, buildStatement, packNpmFromTree } from '../pos.mjs'; import * as pq from '../../../sdk-pq-sign/index.mjs'; 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 D = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-')); const A = path.join(D, 'app.tgz'), B = path.join(D, 'app.sha'), S = path.join(D, 'sbom.json'); fs.writeFileSync(A, Buffer.from('binary artifact bytes 12345')); fs.writeFileSync(B, 'checksums'); fs.writeFileSync(S, JSON.stringify({ bomFormat: 'CycloneDX', components: [] })); const keys = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-65' }); await test('statement: artifacts with sha256 + size, sbom hashed, builder, time', () => { const s = buildStatement({ artifacts: [A, B], sbom: S, name: 'app', version: '1.0.0', cwd: D }); assert.strictEqual(s.artifacts.length, 2); assert.match(s.artifacts[0].sha256, /^0x[0-9a-f]{64}$/); assert.strictEqual(s.artifacts[0].bytes, 27); assert.strictEqual(s.sbom.name, 'sbom.json'); assert.strictEqual(s.subject.version, '1.0.0'); assert.ok(s.createdAt); }); await test('two artifacts with the same name are refused (verification matches by name)', () => { const D2 = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-')); fs.mkdirSync(path.join(D2, 'x')); fs.writeFileSync(path.join(D2, 'x', 'app.tgz'), 'a'); assert.throws(() => buildStatement({ artifacts: [A, path.join(D2, 'x', 'app.tgz')], cwd: D }), /share the name/); }); const att = await attest({ artifacts: [A, B], sbom: S, name: 'app', version: '1.0.0', keys, cwd: D }); await test('attest + verify with the same files: VALID, signature both halves, on chain absent (reported, not assumed)', async () => { const r = await verify(att, [A, B, S]); assert.strictEqual(r.valid, true, JSON.stringify(r.checks)); assert.ok(r.checks.find((c) => c.name.startsWith('hybrid signature') && c.pass === true)); assert.ok(r.checks.find((c) => c.name === 'on chain' && c.pass === null)); }); await test('a tampered artifact (one byte) makes verification INVALID', async () => { const T = path.join(D, 'app.tgz.tampered'); fs.mkdirSync(path.join(D, 't'), { recursive: true }); const T2 = path.join(D, 't', 'app.tgz'); fs.writeFileSync(T2, Buffer.from('binary artifact bytes 12346')); const r = await verify(att, [T2, B, S]); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.includes('app.tgz') && c.pass === false)); }); await test('a missing artifact is INVALID (absent artifact is a failure, absent SBOM is only reported)', async () => { const r = await verify(att, [A]); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.includes('app.sha') && c.pass === false)); assert.ok(r.checks.find((c) => c.name.startsWith('sbom') && c.pass === null)); }); await test('an edited statement (version changed) breaks statementHash and the signature', async () => { const e = JSON.parse(JSON.stringify(att)); e.statement.subject.version = '1.0.1'; const r = await verify(e, [A, B, S]); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.startsWith('statementHash') && c.pass === false)); assert.ok(r.checks.find((c) => c.name.startsWith('hybrid signature') && c.pass === false)); }); await test('a flipped bit in the post-quantum half of the signature is INVALID', async () => { const e = JSON.parse(JSON.stringify(att)); const b = pq.fromHex(e.signature.pqSignature); b[5] ^= 1; e.signature.pqSignature = pq.toHex(b); const r = await verify(e, [A, B, S]); assert.strictEqual(r.valid, false); }); await test('without keys: no signature, still VALID on digests alone, signature reported absent', async () => { const a2 = await attest({ artifacts: [A], cwd: D }); const r = await verify(a2, [A]); assert.strictEqual(r.valid, true); assert.ok(r.checks.find((c) => c.name === 'hybrid signature' && c.pass === null)); }); await test('CLI: keygen, attest, verify round trip through files; tampered file fails with exit 1', () => { const pos = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')), '..', 'pos.mjs'); const K = path.join(D, 'keys.json'), O = path.join(D, 'attestation.json'); execFileSync('node', [pos, 'keygen', '--out', K, '--alg', 'ed25519+ml-dsa-44'], { stdio: 'pipe' }); const out = execFileSync('node', [pos, 'attest', '--out', O, '--name', 'app', '--version', '2', '--sbom', S, '--keys', K, A, B], { stdio: 'pipe' }).toString(); assert.match(out, /signature ed25519\+ml-dsa-44/); const v = execFileSync('node', [pos, 'verify', O, A, B, S], { stdio: 'pipe' }).toString(); assert.match(v, /VALID: every present claim holds/); let code = 0; try { execFileSync('node', [pos, 'verify', O, path.join(D, 't', 'app.tgz'), B, S], { stdio: 'pipe' }); } catch (e) { code = e.status; } assert.strictEqual(code, 1); }); // ---- legarea de ARBORELE git si verificarea prin RECONSTRUCTIE (1.1.0). Un depozit de proba cu un pachet npm mic. const G = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-git-')); const g = (args, cwd = G) => execFileSync('git', ['-c', 'core.autocrlf=false', '-c', 'user.name=proba', '-c', 'user.email=proba@example.invalid', ...args], { cwd, stdio: 'pipe' }).toString().trim(); g(['init', '-q']); fs.mkdirSync(path.join(G, 'pkg', 'lib'), { recursive: true }); fs.mkdirSync(path.join(G, 'altceva')); fs.writeFileSync(path.join(G, 'pkg', 'package.json'), JSON.stringify({ name: 'proba-pos', version: '0.0.1', main: 'lib/index.js', files: ['lib', 'LICENSE'] }, null, 1) + '\n'); fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 1;\n'); fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n'); fs.writeFileSync(path.join(G, 'altceva', 'x.txt'), 'nu face parte din pachet\n'); g(['add', '-A']); g(['commit', '-q', '-m', 'pachet de proba']); const OUT1 = path.join(G, '..', path.basename(G) + '-out'); fs.mkdirSync(OUT1); const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT1); await test('source tree: --source-path records path, tree and pathDirty; build records npm-pack and the npm version', () => { const s = buildStatement({ artifacts: [TGZ], sourcePath: 'pkg', build: 'npm-pack', cwd: G }); assert.strictEqual(s.source.path, 'pkg'); assert.strictEqual(s.source.tree, g(['rev-parse', 'HEAD:pkg'])); assert.strictEqual(s.source.pathDirty, false); assert.strictEqual(s.build.kind, 'npm-pack'); assert.match(s.build.npm, /^\d+\./); assert.throws(() => buildStatement({ artifacts: [TGZ], sourcePath: 'nu-exista', cwd: G }), /not tracked at HEAD/); }); await test('the listing of a tree does not depend on the directory it is asked from (ls-tree --full-tree)', () => { const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sub-')); const t2 = packNpmFromTree(path.join(G, 'altceva'), 'HEAD:pkg', o); assert.strictEqual(fs.readFileSync(t2).equals(fs.readFileSync(TGZ)), true, 'same tree asked from a subdirectory gave other bytes'); }); await test('the artifact is a function of the COMMITTED tree: CRLF in the working copy changes a working-copy pack, not this one', () => { fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\r\nline two\r\n'); const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-crlf-')); const again = packNpmFromTree(G, 'HEAD:pkg', o); assert.strictEqual(fs.readFileSync(again).equals(fs.readFileSync(TGZ)), true); const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop(); fs.renameSync(path.join(o, wc), path.join(o, 'din-copia-de-lucru.tgz')); assert.strictEqual(fs.readFileSync(path.join(o, 'din-copia-de-lucru.tgz')).equals(fs.readFileSync(TGZ)), false, 'control: the working-copy pack should differ'); fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n'); }); const attG = await attest({ artifacts: [TGZ], name: 'proba-pos', version: '0.0.1', sourcePath: 'pkg', build: 'npm-pack', keys, cwd: G }); await test('verify --rebuild-from: a clone chosen by the verifier reproduces the attested artifact byte for byte', async () => { const C = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-clona-')); g(['clone', '-q', G, C], os.tmpdir()); const r = await verify(attG, [TGZ], { rebuildFrom: C }); assert.strictEqual(r.valid, true, JSON.stringify(r.checks)); assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === true)); const r0 = await verify(attG, [TGZ]); assert.ok(r0.checks.find((c) => c.name === 'rebuild' && c.pass === null), 'without --rebuild-from the rebuild is reported as not attempted'); }); await test('an artifact that did NOT come from the attested tree fails the rebuild (digest differs), though its own digest matches', async () => { fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 2; // schimbat si necomis\n'); const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-strain-')); const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop(); const strain = path.join(o, wc); const a = await attest({ artifacts: [strain], sourcePath: 'pkg', build: 'npm-pack', cwd: G }); assert.strictEqual(a.statement.source.pathDirty, true, 'the statement itself says the path was dirty'); const plain = await verify(a, [strain]); assert.strictEqual(plain.valid, true, 'digests alone hold: that is why the rebuild matters'); const r = await verify(a, [strain], { rebuildFrom: G }); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === false && /got 0x/.test(c.detail))); g(['checkout', '-q', '--', 'pkg/lib/index.js']); }); await test('a clone that does not hold the attested commit fails on the SOURCE check, before any build', async () => { const E = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-gol-')); g(['init', '-q'], E); fs.writeFileSync(path.join(E, 'a'), 'a'); g(['add', '-A'], E); g(['commit', '-q', '-m', 'alt depozit'], E); const r = await verify(attG, [TGZ], { rebuildFrom: E }); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name.startsWith('source:') && c.pass === false && /not in this clone/.test(c.detail))); assert.ok(!r.checks.find((c) => c.name.startsWith('rebuild: npm pack')), 'no build was attempted'); }); await test('a statement without a source tree cannot be rebuilt, and asking for it is INVALID, not silently skipped', async () => { const r = await verify(att, [A, B, S], { rebuildFrom: G }); assert.strictEqual(r.valid, false); assert.ok(r.checks.find((c) => c.name === 'rebuild: the statement names a source tree' && c.pass === false)); }); const CHEIE_F = process.env.AERE_CHEIE_FISIER; if (process.env.AERE_SCRIE === '1' && CHEIE_F) { const { AereCloud } = await import('../../../sdk/index.mjs'); const cloud = new AereCloud({ apiKey: fs.readFileSync(CHEIE_F, 'utf8').trim() }); await test('LIVE: attest with notarization on chain 2800, then verify reads the proof (notarized, receipt matches)', async () => { const a = await attest({ artifacts: [A, B], sbom: S, name: 'app', version: 'live', keys, cloud, cwd: D }); assert.ok(a.notarization && a.notarization.txHash && a.notarization.block > 0, JSON.stringify(a.notarization)); await new Promise((r) => setTimeout(r, 4000)); const r = await verify(a, [A, B, S], { cloud }); assert.strictEqual(r.valid, true, JSON.stringify(r.checks)); assert.ok(r.checks.find((c) => c.name === 'on chain: statementHash is notarized' && c.pass === true)); console.log(' ' + r.checks.filter((c) => c.name.startsWith('on chain')).map((c) => c.name).join(' | ')); }); } else console.log(' (sarit) notarizarea reala: AERE_SCRIE=1 si AERE_CHEIE_FISIER= (scrie o tranzactie pe 2800)'); console.log(`\n${treceri} treceri, ${esecuri.length} esecuri`); if (esecuri.length) process.exitCode = 1;