68 lines
6.5 KiB
JavaScript
68 lines
6.5 KiB
JavaScript
// Controlul negativ al probei SD-JWT VC (sdjwt-vc.mjs, proba-sdjwt-vc.mjs): fiecare paznic se strica intr-o COPIE, proba ruleaza pe copie si
|
|
// proba NUMITA trebuie sa iasa rosie, cu proba chiar rulata; pe copia neatinsa, verde. Trei stari: un tipar care nu apare exact o data sau o
|
|
// proba care nu ajunge la rezumat e STRICAT si se numara esec. Plantarile sunt ancorate pe randuri STABILE si UNICE ale paznicului.
|
|
// node control-negativ-sdjwt-vc.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 S = 'sdjwt-vc.mjs';
|
|
const PLANTARI = [
|
|
// [nume, tipar, inlocuire, proba (inceputul numelui ei)]
|
|
['vct-ul asteptat nu se mai cere verificatorului', "if (!(Array.isArray(vcts) && vcts.length > 0 && vcts.every((v) => typeof v === 'string' && !!v) /*PAZA:vct-expected*/)) return refuz(", 'if (false) return refuz(', 'PROFIL vct'],
|
|
['vct lipsa din jeton primit', "if (!(typeof p.vct === 'string' && !!p.vct /*PAZA:vct*/)) return refuz('vct'", "if (false) return refuz('vct'", 'PROFIL vct'],
|
|
['alt vct decat cel asteptat primit', 'if (!(tipuri.includes(p.vct) /*PAZA:vct-mismatch*/)) return refuz(', 'if (false) return refuz(', 'PROFIL vct'],
|
|
['typ-ul dc+sd-jwt nu se mai cere (orice ...+sd-jwt)', 'requireStatus = true, expectedTyp = TYP_VC, ...o } = {}) {', 'requireStatus = true, expectedTyp = undefined, ...o } = {}) {', 'PROFIL typ'],
|
|
['o afirmatie din s.2.2.2.3 dezvaluita selectiv primita', 'if (!(dez.length === 0 /*PAZA:disclosed-protected*/)) return refuz(', 'if (false) return refuz(', 'PROFIL s.2.2.2.3'],
|
|
['emitentul face dezvaluibile afirmatiile din s.2.2.2.3', 'for (const n of [...disclosable, ...arrayDisclosable]) if (NEDEZVALUIBILE.includes(n)) throw', 'for (const n of [...disclosable, ...arrayDisclosable]) if (false) throw', 'PROFIL s.2.2.2.3'],
|
|
['emitentul nu mai cere vct', "if (typeof vct !== 'string' || !vct) throw new Error('sd-jwt-vc: vct is required", "if (false) throw new Error('sd-jwt-vc: vct is required", 'PROFIL s.2.2.2.3'],
|
|
['starea nejudecata trece implicit drept nerevocata', 'if (!(status.judged || !requireStatus /*PAZA:status-unjudged*/)) return refuz(', 'if (false) return refuz(', 'STARE: lista lipsa'],
|
|
['starea revocata/suspendata primita', 'if (!(v === 0 /*PAZA:status-revoked*/)) return refuz(', 'if (false) return refuz(', 'STARE: revocat'],
|
|
['impachetarea bitilor citita de la bitul cel mai semnificativ (ordinea gresita)', '(octeti[Math.floor(idx * bits / 8)] >> ((idx * bits) % 8)) & ((1 << bits) - 1)', '(octeti[Math.floor(idx * bits / 8)] >> (8 - bits - ((idx * bits) % 8))) & ((1 << bits) - 1)', 'STARE: revocat'],
|
|
['sub-ul listei nu se mai compara cu uri-ul din jeton', 'if (!(corp.sub === uri /*PAZA:status-list*/)) return nu(', 'if (false) return nu(', 'STARE: lista pentru alt uri'],
|
|
['lista expirata primita', 'if (!(corp.exp === undefined || (Number.isFinite(corp.exp) && acum < corp.exp) /*PAZA:status-list*/)) return nu(', 'if (false) return nu(', 'STARE: lista pentru alt uri'],
|
|
['typ-ul statuslist+jwt al listei nu se mai cere', "verifyJws(jwt, publicJwk(key, alg), { typ: TYP_STATUS_LIST })", "verifyJws(jwt, publicJwk(key, alg), { typ: null })", 'STARE: lista pentru alt uri'],
|
|
['iat-ul listei nu se mai cere', "if (!(Number.isFinite(corp.iat) /*PAZA:status-list*/)) return nu('iat is required", "if (false) return nu('iat is required", 'STARE: lista pentru alt uri'],
|
|
['indexul dincolo de lista citit ca 0', 'if (!(v !== null /*PAZA:status-index*/)) return refuz(', 'if (false) return refuz(', 'STARE: lista pentru alt uri'],
|
|
// fixturile straine: cu impachetarea gresita, listele @owf/token-status-list pe 1 bit cu indexul 5 = 1 ar fi citite altfel
|
|
['fixturile straine: lista LOR revocata citita cu ordinea gresita a bitilor', 'const statusAt = (idx) => (Number.isInteger(idx) && idx >= 0 && idx < entries ?', 'const statusAt = (idx) => (false && Number.isInteger(idx) && idx >= 0 && idx < entries ?', 'INTEROP'],
|
|
];
|
|
function copie() {
|
|
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-sdjwt-vc-ctl-'));
|
|
for (const f of [S, 'sdjwt.mjs', 'identity.mjs', 'identity-cli.mjs', 'proba-sdjwt-vc.mjs']) fs.copyFileSync(path.join(AICI, f), path.join(t, f));
|
|
fs.mkdirSync(path.join(t, 'interop'));
|
|
fs.copyFileSync(path.join(AICI, 'interop', 'fixturi-interop-sdjwt-vc.json'), path.join(t, 'interop', 'fixturi-interop-sdjwt-vc.json'));
|
|
return t;
|
|
}
|
|
function ruleaza(t) {
|
|
return new Promise((resolve) => {
|
|
const c = spawn(process.execPath, [path.join(t, 'proba-sdjwt-vc.mjs')]); 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-sd-jwt-vc: \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => l.startsWith(' RAU ')) }); });
|
|
});
|
|
}
|
|
async function planteaza([nume, din, inl, tinta]) {
|
|
const t = copie();
|
|
try {
|
|
const f = path.join(t, S); const src = fs.readFileSync(f, 'utf8');
|
|
if (src.split(din).length !== 2) return [false, ` STRICAT ${nume}: tiparul apare de ${src.split(din).length - 1} ori`];
|
|
fs.writeFileSync(f, src.replace(din, inl));
|
|
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.cod === 0 && !m.rosii.length) console.log(' OK martorul: copia neatinsa verde'); 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;
|