75 lines
7.2 KiB
JavaScript
75 lines
7.2 KiB
JavaScript
// Controlul negativ al Travel Rule (travel-rule.mjs, proba-travel-rule.mjs): fiecare paznic scos intr-o COPIE trebuie sa inroseasca
|
|
// proba NUMITA, cu proba chiar rulata; pe copia neatinsa, verde. O plantare poate avea mai multe inlocuiri (un paznic dublat, de pilda
|
|
// antetul legat si ca AAD si prin derivarea cheii, se scoate intreg). Trei stari: un tipar care nu apare exact o data sau o proba care
|
|
// nu ajunge la rezumat e STRICAT si se numara esec.
|
|
// node control-negativ-travel-rule.mjs iesire 0 = martorul verde si toate plantarile rosii pe proba lor
|
|
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';
|
|
import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const DEV_VERIFY = path.resolve(AICI, '..', 'aere-proof-protocol', 'verify.mjs');
|
|
const VERIFY = process.env.AERE_VERIFY_PROOF || (fs.existsSync(DEV_VERIFY) ? DEV_VERIFY : '');
|
|
const T = 'travel-rule.mjs';
|
|
const PLANTARI = [
|
|
// [nume, [[tipar, inlocuire], ...], proba (inceputul numelui ei)]
|
|
['legarea KEM nesemnata de VASP-ul ei primita', [["if (!verifyText('kem-binding', canonical(S), binding.signature, S.vasp.keys)) motive.push(", 'if (false) motive.push(']], 'ATAC: legarea KEM semnata de alt VASP'],
|
|
['credentialul altei identitati primit', [['if (!holder || holder.id !== id) motive.push(', 'if (false) motive.push(']], 'ATAC: legarea KEM a lui C cu prezentarea'],
|
|
['un credential fara vasp:true primit', [['if (v.valid && (!v.claims || v.claims.vasp !== true)) motive.push(', 'if (false) motive.push(']], 'ATAC: credential de VASP de la un registru'],
|
|
['registrele de incredere nu mai ajung la verificare', [['trustedIssuers: registries,', 'trustedIssuers: null,']], 'ATAC: credential de VASP de la un registru'],
|
|
['starea nejudecata a VASP-ului primita', [['if (!(stare && stare.pass === true)) motive.push(', 'if (false) motive.push(']], 'ATAC: un VASP revocat'],
|
|
['legarea expirata primita', [["if (!(timp(S.validFrom, 'validFrom') <= acum && acum <= timp(S.validUntil, 'validUntil'))) motive.push(", 'if (false) motive.push(']], 'ATAC: prezentarea beneficiarului facuta pentru alt initiator'],
|
|
['semnatura mesajului nu se mai verifica', [["if (!verifyText('travel-rule', canonical(semnat), m.signature, m.from.keys)) motive.push(", 'if (false) motive.push(']], 'ATAC: mesaj semnat de un strain'],
|
|
['destinatarul nu se mai compara', [['if (m.to.id !== me) motive.push(', 'if (false) motive.push(']], 'ATAC: alt VASP (C)'],
|
|
['cheile KEM ale destinatarului nu se mai compara', [['if (canonical(m.to.kem) !== canonical(kem.public)) motive.push(', 'if (false) motive.push(']], 'ATAC: alt VASP (C)'],
|
|
['prospetimea nu se mai cere', [["if (Math.abs(acum - timp(m.createdAt, 'createdAt')) > maxAgeS * 1000) motive.push(", 'if (false) motive.push(']], 'ATAC: acelasi mesaj primit a doua oara'],
|
|
['reluarea nu se mai opreste', [['if (seen && seen.has(m.id)) motive.push(', 'if (false) motive.push(']], 'ATAC: acelasi mesaj primit a doua oara'],
|
|
['initiatorul nu mai trebuie dovedit VASP', [["motive.push(...v.motive.map((x) => 'originator VASP: ' + x));", '']], 'ATAC: initiatorul nedovedit ca VASP'],
|
|
['antetul nu mai e legat de cifru (nici AAD, nici derivarea)', [["const c = crypto.createCipheriv('aes-256-gcm', d.key, d.iv); c.setAAD(aad);", "const c = crypto.createCipheriv('aes-256-gcm', d.key, d.iv);"], ['dc.setAAD(aad); dc.setAuthTag', 'dc.setAuthTag'], ["crypto.createHash('sha256').update(transcript).digest()", 'Buffer.alloc(32)']], 'ATAC: suma din antet schimbata'],
|
|
['numai secretul X25519 intra in cheie', [['const ikm = Buffer.concat([ssK, ssX]);', 'const ikm = Buffer.concat([ssX]);']], 'KEM hibrid'],
|
|
['confirmarea altui VASP primita', [['if (S.from.id !== beneficiaryId || idOf(S.from.keys) !== S.from.id) motive.push(', 'if (false) motive.push(']], 'ATAC: confirmare semnata de C'],
|
|
['confirmarea altui mesaj primita', [["if (S.messageId !== message.id || S.messageHash !== sha(Buffer.from(canonical(semnat), 'utf8'))) motive.push(", 'if (false) motive.push(']], 'ATAC: confirmare semnata de C'],
|
|
['id-ul initiatorului scris in inregistrare', [["const subject = 'transfer:' + sha(", 'const subject = message.from.id + sha(']], 'inregistrarea (plic AIP-23 compliance)'],
|
|
['un fisier de chei KEM cu partea publica a altcuiva primit', [['if (canonical(pub) !== canonical(j.public)) throw', 'if (false) throw']], 'cheile KEM'],
|
|
];
|
|
const FISIERE = ['identity.mjs', T, 'proba-travel-rule.mjs'];
|
|
function copie() {
|
|
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-tr-ctl-'));
|
|
fs.mkdirSync(path.join(t, 'aere-identity')); fs.mkdirSync(path.join(t, 'proof-kinds'));
|
|
for (const f of FISIERE) fs.copyFileSync(path.join(AICI, f), path.join(t, 'aere-identity', f));
|
|
fs.copyFileSync(path.join(AICI, '..', 'proof-kinds', 'proof-kinds.mjs'), path.join(t, 'proof-kinds', 'proof-kinds.mjs'));
|
|
return t;
|
|
}
|
|
function ruleaza(t) {
|
|
const env = { ...process.env }; if (VERIFY) env.AERE_VERIFY_PROOF = VERIFY; else delete env.AERE_VERIFY_PROOF;
|
|
return new Promise((resolve) => {
|
|
const c = spawn(process.execPath, [path.join(t, 'aere-identity', 'proba-travel-rule.mjs')], { env }); let out = '';
|
|
const ceas = setTimeout(() => c.kill(), 180000);
|
|
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; });
|
|
c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /aere-travel-rule: \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => l.startsWith(' RAU ')) }); });
|
|
});
|
|
}
|
|
async function planteaza([nume, perechi, tinta]) {
|
|
const t = copie();
|
|
try {
|
|
const f = path.join(t, 'aere-identity', T); let src = fs.readFileSync(f, 'utf8');
|
|
for (const [din, inl] of perechi) {
|
|
if (src.split(din).length !== 2) return [false, ` STRICAT ${nume}: tiparul "${din.slice(0, 40)}" apare de ${src.split(din).length - 1} ori`];
|
|
src = src.replace(din, inl);
|
|
}
|
|
fs.writeFileSync(f, src);
|
|
const r = await ruleaza(t);
|
|
if (!r.rulat) return [false, ` STRICAT ${nume}: proba nu a ajuns la rezumat (cod ${r.cod})`];
|
|
if (r.rosii.some((l) => l.startsWith(' RAU ' + tinta))) return [true, ` ROSU cum trebuia ${nume} (proba '${tinta}' pica)`];
|
|
return [false, ` CONTROL CAZUT ${nume}: proba '${tinta}' a ramas verde (${r.rosii.length} rosii altundeva)`];
|
|
} finally { fs.rmSync(t, { recursive: true, force: true }); }
|
|
}
|
|
let rele = 0;
|
|
const t0 = copie(); const m = await ruleaza(t0); fs.rmSync(t0, { recursive: true, force: true });
|
|
if (m.rulat && !m.rosii.length && (m.cod === 0 || (m.cod === 2 && !VERIFY))) console.log(' OK martorul: copia neatinsa verde' + (m.cod === 2 ? ' (plicul AIP-23 NEMASURAT: fara verificator)' : ''));
|
|
else { rele++; console.log(` STRICAT martorul nu e verde (cod ${m.cod}, ${m.rosii.length} rosii)`); }
|
|
const rez = new Array(PLANTARI.length); let i = 0;
|
|
await Promise.all(Array.from({ length: 4 }, async () => { while (i < PLANTARI.length) { const k = i++; rez[k] = await planteaza(PLANTARI[k]); } }));
|
|
for (const [bun, linie] of rez) { console.log(linie); if (!bun) rele++; }
|
|
console.log(rele ? `CONTROL NEGATIV: ${rele} probleme` : `DOVEDIT: martorul verde, ${PLANTARI.length} din ${PLANTARI.length} paznici scosi -> proba lor rosie`);
|
|
process.exitCode = rele ? 1 : 0;
|