// Proba SD-JWT (sdjwt.mjs): intai pe vectorii STANDARDULUI (RFC 9901 s.5 si A.5, extrasi in fixturi-rfc9901.json: facuti de altii, cu // cheia si semnaturile lor), apoi pe jetoane emise cu chei AERE (Ed25519 si ML-DSA-65), apoi fiecare atac ca proba numita. Offline. // node proba-sdjwt.mjs iesire 0 = toate cum trebuia import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import os from 'node:os'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import * as I from './identity.mjs'; import * as J from './sdjwt.mjs'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const F = JSON.parse(fs.readFileSync(path.join(AICI, 'fixturi-rfc9901.json'), 'utf8')); let treceri = 0; const esecuri = []; function test(nume, fn) { try { fn(); treceri++; console.log(' OK ' + nume); } catch (e) { esecuri.push(nume); console.log(' RAU ' + nume + ' -- ' + (e.message || e)); } } const cere = (c, m) => { if (!c) throw new Error(m); }; const sortat = (o) => (Array.isArray(o) ? o.map(sortat) : o && typeof o === 'object' ? Object.fromEntries(Object.keys(o).sort().map((k) => [k, sortat(o[k])])) : o); const egal = (a, b) => JSON.stringify(sortat(a)) === JSON.stringify(sortat(b)); const b64u = (x) => Buffer.from(x).toString('base64url'); const KB = F.keyBindingPayload, LA_KB = new Date(KB.iat * 1000); const refuz = (r, re) => !r.valid && re.test(r.reason); // ---------------------------------------------------------------- vectorii RFC 9901 test('RFC 9901 s.5.1: fiecare din cele 10 dezvaluiri are digestul SHA-256 base64url scris in standard', () => { for (const d of F.disclosures) cere(crypto.createHash('sha256').update(Buffer.from(d.disclosure, 'ascii')).digest('base64url') === d.digest, d.digest); cere(F.disclosures.length === 10, String(F.disclosures.length)); }); test('RFC 9901 s.5.1: SD-JWT-ul standardului (ES256, cheia din A.5) verifica si se reface in afirmatiile de intrare, toate', () => { const r = J.verifySdJwt(F.sdJwt, { issuerKey: F.issuerJwk, requireKeyBinding: false, now: new Date('2026-09-30T00:00:00Z') }); cere(r.valid, r.reason); for (const [k, v] of Object.entries(F.inputClaims)) cere(egal(r.payload[k], v), 'afirmatia ' + k); cere(r.payload.iss === 'https://issuer.example.com' && r.alg === 'ES256' && !('_sd' in r.payload) && !('_sd_alg' in r.payload), JSON.stringify(Object.keys(r.payload))); }); test('RFC 9901 s.5.2: prezentarea standardului cu Key Binding JWT verifica (aud, nonce, iat, sd_hash, cheia din cnf) si da exact payload-ul procesat din standard', () => { const r = J.verifySdJwt(F.presentation, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: KB.nonce, now: LA_KB }); cere(r.valid, r.reason); cere(egal(r.payload, F.processedPayload), JSON.stringify(r.payload)); cere(r.keyBinding && r.keyBinding.nonce === '1234567890' && r.keyBinding.aud === 'https://verifier.example.org' && r.keyBinding.alg === 'ES256', JSON.stringify(r.keyBinding)); }); test('RFC 9901: aceeasi prezentare pentru ALT verificator, cu ALT nonce, sau judecata la o ora dupa iat -> refuzata', () => { cere(refuz(J.verifySdJwt(F.presentation, { issuerKey: F.issuerJwk, audience: 'https://other.example', nonce: KB.nonce, now: LA_KB }), /made for/), 'alt aud'); cere(refuz(J.verifySdJwt(F.presentation, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: 'x', now: LA_KB }), /nonce/), 'alt nonce'); cere(refuz(J.verifySdJwt(F.presentation, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: KB.nonce, now: new Date((KB.iat + 3600) * 1000) }), /outside 300 s/), 'veche'); }); test('RFC 9901: SD-JWT-ul standardului cu ALTA cheie de emitent (P-256 a noastra) -> refuzat; fara Key Binding cand e cerut -> refuzat', () => { const alta = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }).publicKey.export({ format: 'jwk' }); cere(refuz(J.verifySdJwt(F.presentation, { issuerKey: alta, audience: KB.aud, nonce: KB.nonce, now: LA_KB }), /does not verify/), 'alta cheie'); cere(refuz(J.verifySdJwt(F.sdJwt, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: KB.nonce, now: LA_KB }), /key binding is required/), 'fara KB'); }); test('RFC 9901: o dezvaluire scoasa din prezentare dupa semnarea KB (sd_hash) sau una adaugata -> refuzata', () => { const p = F.presentation.split('~'); const kb = p.pop(); const scoasa = [...p.slice(0, 2), ...p.slice(3), kb].join('~'); cere(refuz(J.verifySdJwt(scoasa, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: KB.nonce, now: LA_KB }), /sd_hash/), 'scoasa: ' + J.verifySdJwt(scoasa, { issuerKey: F.issuerJwk, now: LA_KB }).reason); const adaugata = [...p, F.disclosures[2].disclosure, kb].join('~'); cere(refuz(J.verifySdJwt(adaugata, { issuerKey: F.issuerJwk, audience: KB.aud, nonce: KB.nonce, now: LA_KB }), /sd_hash/), 'adaugata'); }); // ---------------------------------------------------------------- jetoane emise cu chei AERE const NOW = Math.floor(Date.parse('2026-09-30T08:00:00Z') / 1000), LA = new Date(NOW * 1000); const iss = I.generateKeys(), hol = I.generateKeys(), strain = I.generateKeys(); const CL = { given_name: 'Ana', family_name: 'Pop', age_over_18: true, nationalities: ['RO', 'DE'], employer: 'Example Ltd' }; const emite = (o = {}) => J.issueSdJwt({ issuer: iss, holder: hol, claims: CL, disclosable: ['given_name', 'family_name', 'age_over_18'], arrayDisclosable: ['nationalities'], iss: 'https://issuer.aere.example', iat: NOW, exp: NOW + 86400, decoys: 2, ...o }); const AUD = 'https://verifier.example', NONCE = 'n-' + crypto.randomBytes(4).toString('hex'); const arata = (sd, reveal, o = {}) => J.presentSdJwt({ sdJwt: sd, reveal, holder: hol, audience: AUD, nonce: NONCE, iat: NOW, ...o }); const judeca = (p, o = {}) => J.verifySdJwt(p, { issuerKey: iss, audience: AUD, nonce: NONCE, now: LA, ...o }); const E = emite(); test('AERE -> SD-JWT (EdDSA, cheia Ed25519 a emitentului AERE): detinatorul arata age_over_18 si RO, verificatorul vede numai atat plus afirmatiile in clar', () => { const r = judeca(arata(E.sdJwt, ['age_over_18', { element: 'RO', in: 'nationalities' }])); cere(r.valid, r.reason); cere(r.payload.age_over_18 === true && !('given_name' in r.payload) && egal(r.payload.nationalities, ['RO']) && r.payload.employer === 'Example Ltd' && r.alg === 'EdDSA', JSON.stringify(r.payload)); }); test('AERE -> SD-JWT in forma standardului: typ explicit, _sd sortat (cu momeli), _sd_alg sha-256, cnf.jwk = cheia Ed25519 a detinatorului, dezvaluiri [sare, nume, valoare]', () => { const [h, p] = E.sdJwt.split('~')[0].split('.').slice(0, 2).map((x) => JSON.parse(Buffer.from(x, 'base64url').toString('utf8'))); cere(h.alg === 'EdDSA' && h.typ === J.TYP, JSON.stringify(h)); cere(Array.isArray(p._sd) && p._sd.length === 5 && egal([...p._sd].sort(), p._sd) && p._sd_alg === 'sha-256', JSON.stringify(p._sd)); cere(p.cnf.jwk.kty === 'OKP' && p.cnf.jwk.crv === 'Ed25519' && p.cnf.jwk.x === J.publicJwk(hol).x && !('d' in p.cnf.jwk), JSON.stringify(p.cnf)); const c = JSON.parse(Buffer.from(E.disclosures[0].disclosure, 'base64url').toString('utf8')); cere(c.length === 3 && typeof c[0] === 'string' && c[0].length >= 22 && c[1] === 'given_name', JSON.stringify(c)); }); test('AERE -> SD-JWT post-cuantic (ML-DSA-65, cheia AKP): verifica cu cheia ML-DSA-65 a emitentului, nu cu cea Ed25519; detinatorul poate semna KB tot cu ML-DSA-65', () => { const M = emite({ alg: 'ML-DSA-65', holderAlg: 'ML-DSA-65' }); const p = arata(M.sdJwt, ['age_over_18'], { holderAlg: 'ML-DSA-65' }); const r = judeca(p, { issuerAlg: 'ML-DSA-65' }); cere(r.valid && r.alg === 'ML-DSA-65' && r.keyBinding.alg === 'ML-DSA-65', r.reason); cere(refuz(judeca(p, { issuerAlg: 'EdDSA' }), /does not match/), 'cheia Ed25519 a primit un jeton ML-DSA-65'); }); // ---------------------------------------------------------------- atacuri const jwtDin = (antet, corp, cheie) => { const cap = b64u(JSON.stringify(antet)) + '.' + b64u(JSON.stringify(corp)); return cap + '.' + b64u(crypto.sign(null, Buffer.from(cap), cheie)); }; const corpDe = (sd) => JSON.parse(Buffer.from(sd.split('~')[0].split('.')[1], 'base64url').toString('utf8')); test('ATAC: alg none si alg ES256 cu cheie Ed25519 (confuzia algoritmului) -> refuzate', () => { const corp = corpDe(E.sdJwt); const none = b64u(JSON.stringify({ alg: 'none', typ: J.TYP })) + '.' + b64u(JSON.stringify(corp)) + '.'; cere(refuz(J.verifySdJwt(none + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /not accepted/), 'none a trecut'); const conf = jwtDin({ alg: 'ES256', typ: J.TYP }, corp, iss.privat.ed25519); cere(refuz(J.verifySdJwt(conf + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /does not match/), 'confuzia a trecut'); }); test('ATAC: o dezvaluire cu valoarea schimbata (alt digest, nelegata) -> refuzata; aceeasi dezvaluire de doua ori -> refuzata', () => { const p = arata(E.sdJwt, ['given_name']).split('~'); const c = JSON.parse(Buffer.from(p[1], 'base64url').toString('utf8')); c[2] = 'Maria'; const schimbat = [p[0], b64u(JSON.stringify(c)), ''].join('~'); cere(refuz(J.verifySdJwt(schimbat, { issuerKey: iss, requireKeyBinding: false, now: LA }), /not referenced/), 'valoarea schimbata a trecut'); const dublu = [p[0], p[1], p[1], ''].join('~'); cere(refuz(J.verifySdJwt(dublu, { issuerKey: iss, requireKeyBinding: false, now: LA }), /twice/), 'dublura a trecut'); }); test('ATAC: un emitent care pune in _sd o dezvaluire cu numele "_sd", una care acopera o afirmatie in clar, sau acelasi digest de doua ori -> refuzat', () => { const faci = (dezvaluiri, extra = {}) => { const ds = dezvaluiri.map((x) => b64u(JSON.stringify(x))); const sd = ds.map((d) => crypto.createHash('sha256').update(d).digest('base64url')); return jwtDin({ alg: 'EdDSA', typ: J.TYP }, { _sd: extra.dublu ? [...sd, sd[0]] : sd, _sd_alg: 'sha-256', iss: 'x', ...(extra.clar || {}) }, iss.privat.ed25519) + '~' + ds.map((d) => d + '~').join(''); }; cere(refuz(J.verifySdJwt(faci([['s1', '_sd', ['x']]]), { issuerKey: iss, requireKeyBinding: false, now: LA }), /may not name _sd/), '_sd a trecut'); cere(refuz(J.verifySdJwt(faci([['s1', 'role', 'admin']], { clar: { role: 'user' } }), { issuerKey: iss, requireKeyBinding: false, now: LA }), /already exists/), 'coliziunea a trecut'); cere(refuz(J.verifySdJwt(faci([['s1', 'a', 1]], { dublu: true }), { issuerKey: iss, requireKeyBinding: false, now: LA }), /more than once/), 'digestul dublu a trecut'); cere(refuz(J.verifySdJwt(faci([['s1', '__proto__', { admin: true }]]), { issuerKey: iss, requireKeyBinding: false, now: LA }), /may not name __proto__/), '__proto__ a trecut'); }); test('ATAC: o afirmatie numita __proto__ in clar sau intr-o valoare dezvaluita (prototipul payload-ului schimbat) -> refuzata', () => { const cap = (corp) => jwtDin({ alg: 'EdDSA', typ: J.TYP }, corp, iss.privat.ed25519); const clar = JSON.parse('{"iss":"x","_sd_alg":"sha-256","__proto__":{"admin":true}}'); cere(refuz(J.verifySdJwt(cap(clar) + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /__proto__/), 'in clar a trecut'); const d = b64u('["s1","profil",' + '{"__proto__":{"admin":true}}' + ']'); const sd = crypto.createHash('sha256').update(d).digest('base64url'); cere(refuz(J.verifySdJwt(cap({ _sd: [sd], _sd_alg: 'sha-256', iss: 'x' }) + '~' + d + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /__proto__/), 'in valoarea dezvaluita a trecut'); }); test('ATAC: KB semnat de alta cheie decat cnf.jwk, KB cu typ gresit, KB fara nonce -> refuzate', () => { const alt = J.presentSdJwt({ sdJwt: E.sdJwt, reveal: ['age_over_18'], holder: strain, audience: AUD, nonce: NONCE, iat: NOW }); cere(refuz(judeca(alt), /key binding: the signature does not verify/), 'cheia straina a trecut'); const baza = arata(E.sdJwt, ['age_over_18']).split('~').slice(0, -1).join('~') + '~'; const sh = crypto.createHash('sha256').update(Buffer.from(baza, 'ascii')).digest('base64url'); const tip = jwtDin({ alg: 'EdDSA', typ: 'JWT' }, { iat: NOW, aud: AUD, nonce: NONCE, sd_hash: sh }, hol.privat.ed25519); cere(refuz(judeca(baza + tip), /typ/), 'typ gresit a trecut'); const faraNonce = jwtDin({ alg: 'EdDSA', typ: 'kb+jwt' }, { iat: NOW, aud: AUD, sd_hash: sh }, hol.privat.ed25519); cere(refuz(judeca(baza + faraNonce), /required/), 'fara nonce a trecut'); }); test('timpul: exp trecut -> refuzat (fara toleranta); nbf peste 30 s -> admis (toleranta de ceas), peste 90 s -> refuzat', () => { cere(refuz(judeca(arata(emite({ exp: NOW - 1 }).sdJwt, ['age_over_18'])), /expired/), 'expirat a trecut'); cere(judeca(arata(emite({ nbf: NOW + 30 }).sdJwt, ['age_over_18'])).valid, 'nbf +30 s refuzat'); cere(refuz(judeca(arata(emite({ nbf: NOW + 90 }).sdJwt, ['age_over_18'])), /not valid before/), 'nbf +90 s a trecut'); }); // revizuirea din 30 sept: fara public si nonce, o prezentare facuta pentru altul trecea (numai un steag spunea ca nu s-au comparat) test('REVIZUIRE: fara publicul si nonce-ul verificatorului, o prezentare cu Key Binding NU se judeca (RFC 9901 s.7.3)', () => { cere(refuz(J.verifySdJwt(arata(E.sdJwt, ['age_over_18']), { issuerKey: iss, now: LA }), /must give its audience and nonce/), 'fara aud/nonce a trecut'); cere(refuz(J.verifySdJwt(arata(E.sdJwt, ['age_over_18']), { issuerKey: iss, audience: AUD, now: LA }), /must give its audience and nonce/), 'fara nonce a trecut'); }); test('REVIZUIRE: emitentul refuza structura SD-JWT venita in valori (_sd imbricat, _sd de sus, element {"...": d}) - altfel detinatorul dezvaluie ce emitentul n-a vazut', () => { const h = crypto.createHash('sha256').update(b64u(JSON.stringify(['s', 'country', 'US']))).digest('base64url'); const incearca = (claims) => { try { J.issueSdJwt({ issuer: iss, holder: hol, claims, iss: 'x', iat: NOW }); return null; } catch (e) { return e.message; } }; cere(/SD-JWT structure/.test(incearca({ address: { street: 'a', _sd: [h] } }) || ''), '_sd imbricat emis'); cere(/SD-JWT structure/.test(incearca({ name: 'x', _sd: [h] }) || ''), '_sd de sus emis'); cere(/SD-JWT structure/.test(incearca({ roles: ['user', { '...': h }] }) || ''), 'element ... emis'); cere(incearca({ roles: ['user', { note: '...' }] }) === null, 'o valoare obisnuita refuzata'); }); test('REVIZUIRE: prezentarea arata elementul NUMAI din lista numita (acelasi RO in doua liste), iar o alegere fara corespondent e o eroare', () => { const D = J.issueSdJwt({ issuer: iss, holder: hol, claims: { nationalities: ['RO', 'DE'], criminal_record_countries: ['RO', 'FR'] }, arrayDisclosable: ['nationalities', 'criminal_record_countries'], iss: 'x', iat: NOW }); const r = judeca(arata(D.sdJwt, [{ element: 'RO', in: 'nationalities' }])); cere(r.valid && egal(r.payload.nationalities, ['RO']) && egal(r.payload.criminal_record_countries, []), JSON.stringify(r.payload)); let m = null; try { arata(D.sdJwt, [{ element: 'IT', in: 'nationalities' }]); } catch (e) { m = e.message; } cere(/nothing to reveal/.test(m || ''), 'alegerea fara corespondent a tacut'); }); test('REVIZUIRE: cheia emitentului data ca KeyObject public sau ca identitate AERE publica (keys.public) e primita; detinatorul dat ca cheie publica', () => { const pubKO = crypto.createPublicKey(iss.privat.ed25519); const D = J.issueSdJwt({ issuer: iss, holder: crypto.createPublicKey(hol.privat.ed25519), claims: { age_over_18: true }, disclosable: ['age_over_18'], iss: 'x', iat: NOW }); const p = arata(D.sdJwt, ['age_over_18']); cere(J.verifySdJwt(p, { issuerKey: pubKO, audience: AUD, nonce: NONCE, now: LA }).valid, 'KeyObject public refuzat'); cere(J.verifySdJwt(p, { issuerKey: iss.public, audience: AUD, nonce: NONCE, now: LA }).valid, 'identitatea AERE publica refuzata'); }); test('REVIZUIRE: exp cerut implicit (un exp dezvaluibil si retinut de detinator nu mai lasa jetonul fara expirare); requireExp:false il lasa', () => { const dExp = b64u(JSON.stringify(['s', 'exp', NOW - 10])); const corp = { _sd: [crypto.createHash('sha256').update(dExp).digest('base64url')], _sd_alg: 'sha-256', iss: 'x' }; const jwt = jwtDin({ alg: 'EdDSA', typ: J.TYP }, corp, iss.privat.ed25519); cere(refuz(J.verifySdJwt(jwt + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /exp is required/), 'fara exp a trecut'); cere(refuz(J.verifySdJwt(jwt + '~' + dExp + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /expired/), 'exp dezvaluit si trecut a trecut'); cere(J.verifySdJwt(jwt + '~', { issuerKey: iss, requireKeyBinding: false, requireExp: false, now: LA }).valid, 'requireExp:false a refuzat'); }); test('REVIZUIRE: tipul jetonului: un KB-JWT sau un JWT cu typ JWT dat drept jeton de emitent -> refuzat implicit; expectedIssuer altul -> refuzat', () => { const corp = { iss: 'https://issuer.aere.example', exp: NOW + 100, _sd_alg: 'sha-256' }; cere(refuz(J.verifySdJwt(jwtDin({ alg: 'EdDSA', typ: 'kb+jwt' }, corp, iss.privat.ed25519) + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /typ/), 'kb+jwt drept emitent a trecut'); const simplu = jwtDin({ alg: 'EdDSA', typ: 'JWT' }, corp, iss.privat.ed25519) + '~'; cere(refuz(J.verifySdJwt(simplu, { issuerKey: iss, requireKeyBinding: false, now: LA }), /typ/), 'typ JWT a trecut implicit'); cere(J.verifySdJwt(simplu, { issuerKey: iss, requireKeyBinding: false, expectedTyp: null, now: LA }).valid, 'expectedTyp:null a refuzat un JWT simplu'); const bun = J.verifySdJwt(arata(E.sdJwt, ['age_over_18']), { issuerKey: iss, audience: AUD, nonce: NONCE, now: LA, expectedIssuer: 'https://issuer.aere.example' }); cere(bun.valid && bun.issuerChecked, 'emitentul asteptat refuzat'); cere(refuz(J.verifySdJwt(arata(E.sdJwt, ['age_over_18']), { issuerKey: iss, audience: AUD, nonce: NONCE, now: LA, expectedIssuer: 'https://altul.example' }), /issued by/), 'alt emitent a trecut'); }); test('REVIZUIRE: crit (orice forma), base64url necanonic al semnaturii, KB expirat -> refuzate; _sd_alg imbricat ramane afirmatie', () => { const corp = { iss: 'x', exp: NOW + 100, _sd_alg: 'sha-256', meta: { _sd_alg: 'x', note: 1 } }; for (const crit of [[], 'b64', ['b64']]) cere(refuz(J.verifySdJwt(jwtDin({ alg: 'EdDSA', typ: J.TYP, crit, b64: false }, corp, iss.privat.ed25519) + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /crit/), 'crit ' + JSON.stringify(crit)); const j = jwtDin({ alg: 'EdDSA', typ: J.TYP }, corp, iss.privat.ed25519); const r = J.verifySdJwt(j + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }); cere(r.valid && r.payload.meta._sd_alg === 'x' && !('_sd_alg' in r.payload), '_sd_alg imbricat: ' + JSON.stringify(r.payload)); const ALF = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; const u = j[j.length - 1], alt = ALF[ALF.indexOf(u) ^ 1]; const nec = j.slice(0, -1) + alt; cere(Buffer.from(nec.split('.')[2], 'base64url').equals(Buffer.from(j.split('.')[2], 'base64url')), 'proba: scrierea alternativa trebuie sa dea aceiasi octeti'); cere(refuz(J.verifySdJwt(nec + '~', { issuerKey: iss, requireKeyBinding: false, now: LA }), /canonical/), 'semnatura necanonica a trecut'); const baza = arata(E.sdJwt, ['age_over_18']).split('~').slice(0, -1).join('~') + '~'; const sh = crypto.createHash('sha256').update(Buffer.from(baza, 'ascii')).digest('base64url'); const kbExp = jwtDin({ alg: 'EdDSA', typ: 'kb+jwt' }, { iat: NOW, aud: AUD, nonce: NONCE, sd_hash: sh, exp: NOW - 100 }, hol.privat.ed25519); cere(refuz(judeca(baza + kbExp), /key binding: expired/), 'KB expirat a trecut'); }); test('linia de comanda: verify-sdjwt pe prezentarea RFC 9901 s.5.2 -> 0 VALID la momentul ei (--at), 1 INVALID acum (KB vechi); o cheie privata -> 2', () => { const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-sdjwt-cli-')); try { fs.writeFileSync(path.join(T, 'p.txt'), F.presentation); fs.writeFileSync(path.join(T, 'k.json'), JSON.stringify(F.issuerJwk)); const run = (...a) => spawnSync(process.execPath, [path.join(AICI, 'identity-cli.mjs'), 'verify-sdjwt', '--sd-jwt', 'p.txt', '--issuer-key', 'k.json', '--issuer-alg', 'ES256', '--audience', KB.aud, '--nonce', KB.nonce, ...a], { cwd: T, encoding: 'utf8' }); const a = run('--at', new Date(KB.iat * 1000).toISOString(), '--json'); cere(a.status === 0 && JSON.parse(a.stdout).valid === true, 'la iat: ' + a.status + ' ' + a.stderr); const b = run(); cere(b.status === 1 && /outside 300 s/.test(b.stdout), 'acum: ' + b.status + ' ' + b.stdout); fs.writeFileSync(path.join(T, 'k.json'), JSON.stringify(crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey.export({ format: 'jwk' }))); const c = run(); cere(c.status === 2 && /private JWK/.test(c.stderr), 'cheia privata: ' + c.status + ' ' + c.stderr); } finally { fs.rmSync(T, { recursive: true, force: true }); } }); console.log(`\naere-sd-jwt: ${treceri}/${treceri + esecuri.length} cum trebuia`); process.exitCode = esecuri.length ? 1 : 0;