// Detectorul JavaScript / TypeScript: node:crypto, WebCrypto, optiuni TLS, biblioteci din importuri. import { argumente, imparteArgumente, valoareArgument, necunoscut, obiectulDin, cifruOpenssl, identificator, rezolva } from './context.mjs'; import { hashCanonic, grupTls, jws, JWS_RE } from './catalog.mjs'; const MODP = { modp1: 768, modp2: 1024, modp5: 1536, modp14: 2048, modp15: 3072, modp16: 4096, modp17: 6144, modp18: 8192 }; const TLSV = { TLSv1: '1.0', 'TLSv1.0': '1.0', 'TLSv1.1': '1.1', 'TLSv1.2': '1.2', 'TLSv1.3': '1.3', SSLv3: 'ssl3' }; export function importuriJs(ctx) { const r = []; for (const m of ctx.potriviri(/(?= 0) s = s.slice(k + 'node_modules/'.length); if (/^(@[\w.-]+\/)?[\w.-]+\/.+\.(m?js|cjs)$/.test(s) && k >= 0) s = s.replace(/\.(m?js|cjs)$/, ''); else if (/^@noble\//.test(s)) s = s.replace(/\.(m?js|cjs)$/, ''); return s; } function descrieImport(spec0, pos, clauza, implicitDinRequire = null, proprietate = null) { const spec = normalizeazaSpec(spec0); const importate = []; const locale = []; let implicit = implicitDinRequire; const acolade = /\{([^}]*)\}/.exec(clauza || ''); if (acolade) { for (const bucata of acolade[1].split(',')) { const t = bucata.trim().replace(/^type\s+/, ''); if (!t) continue; const m = /^([\w$]+)(?:\s*(?:as|:)\s*([\w$]+))?$/.exec(t); if (m) { importate.push(m[1]); locale.push(m[2] || m[1]); } } } const rest = (clauza || '').replace(/\{[^}]*\}/, '').replace(/^type\s+/, ''); const ns = /\*\s*as\s+([\w$]+)/.exec(rest); if (ns) implicit = ns[1]; const def = /^\s*([\w$]+)\s*(?:,|$)/.exec(rest); if (!implicit && def && def[1] !== 'type') implicit = def[1]; if (proprietate) { importate.push(proprietate); if (implicit) locale.push(implicit); } return { spec, pos, importate, locale, implicit }; } export function detecteazaJs(ctx) { const c = ctx.code; const imp = importuriJs(ctx); const importa = (re) => imp.filter((i) => re.test(i.spec)); const cryptoImp = importa(/^(node:)?crypto$/); const aliasCrypto = new Set(['crypto']); for (const i of cryptoImp) { if (i.implicit) aliasCrypto.add(i.implicit); } const numeImportate = new Set(cryptoImp.flatMap((i) => i.locale)); const nodeCrypto = cryptoImp.length > 0 || ctx.potriviri(/(? 0; const apeluri = (nume) => ctx.potriviri(new RegExp(`(? m[1] !== 'subtle' && (m[1] ? aliasCrypto.has(m[1]) || nodeCrypto : (numeImportate.has(m[2]) || nodeCrypto))); const argsDe = (m) => { const a = argumente(ctx, m.index + m[0].length); return imparteArgumente(ctx, a.start, a.end); }; const cuValoare = (m, arg, api, fn) => { const v = valoareArgument(ctx, arg); if (v.fel === 'literal' || v.fel === 'rezolvat') return fn(v.valoare, v.fel === 'rezolvat' ? { rezolvatDin: v.din } : {}); if (v.fel === 'necunoscut') ctx.adauga(m.index, api, 'call', necunoscut(v.expresie, api), { bucata: m[0] + v.expresie }); return null; }; if (nodeCrypto) { for (const m of apeluri('createHash|createHmac')) { const api = m[2]; const p = argsDe(m); cuValoare(m, p[0], api, (val, ex) => { const h = hashCanonic(val) || val; if (api === 'createHash') ctx.adauga(m.index, api, 'call', { grup: 'HASH', hash: h, functii: ['digest'] }, { ...ex, bucata: `${m[0]}'${val}'` }); else ctx.adauga(m.index, api, 'call', { grup: 'MAC', hash: h, functii: ['tag'] }, { ...ex, bucata: `${m[0]}'${val}'` }); }); } for (const m of apeluri('createCipheriv|createDecipheriv|createCipher|createDecipher')) { const api = m[2]; const p = argsDe(m); cuValoare(m, p[0], api, (val, ex) => { const cf = cifruOpenssl(val) || { grup: 'CIPHER', nume: val }; ctx.adauga(m.index, api, 'call', { ...cf, functii: [/Decipher/.test(api) ? 'decrypt' : 'encrypt'] }, { ...ex, bucata: `${m[0]}'${val}'` }); }); } for (const m of apeluri('createSign|createVerify')) { const api = m[2]; const p = argsDe(m); cuValoare(m, p[0], api, (val, ex) => { const f = [api === 'createSign' ? 'sign' : 'verify']; const h = hashCanonic(val.replace(/^(RSA-|ecdsa-with-|DSA-|RSA-PSS-)/i, '').replace(/with(RSA|DSA)Encryption$/i, '').replace(/WithRSAEncryption$/i, '')); let a; if (/rsa/i.test(val)) a = { grup: 'RSA', primitiv: 'signature', hash: h }; else if (/ecdsa/i.test(val)) a = { grup: 'ECDSA', hash: h }; else if (/dsa/i.test(val)) a = { grup: 'DSA', hash: h }; else a = { grup: 'CLASSIC-SIG', hash: h, motiv: `${api} with digest ${val} (Sign/Verify objects take RSA, RSA-PSS, DSA or EC keys; the key type comes from the key object)` }; ctx.adauga(m.index, api, 'call', { ...a, functii: f }, { ...ex, bucata: `${m[0]}'${val}'` }); }); } for (const m of apeluri('createECDH')) { const p = argsDe(m); cuValoare(m, p[0], 'createECDH', (val, ex) => ctx.adauga(m.index, 'createECDH', 'call', { grup: 'ECDH', curba: val, functii: ['keygen'] }, { ...ex, bucata: `${m[0]}'${val}'` })); } for (const m of apeluri('getDiffieHellman|createDiffieHellmanGroup')) { const p = argsDe(m); cuValoare(m, p[0], m[2], (val, ex) => ctx.adauga(m.index, m[2], 'call', { grup: 'DH', param: MODP[val] ? String(MODP[val]) : val, functii: ['keygen'] }, { ...ex, bucata: `${m[0]}'${val}'` })); } for (const m of apeluri('createDiffieHellman')) { const p = argsDe(m); const bits = p[0] && /^\d+$/.test(p[0].text) ? p[0].text : undefined; ctx.adauga(m.index, 'createDiffieHellman', 'call', { grup: 'DH', param: bits, functii: ['keygen'] }, { bucata: m[0] + (bits || '') }); } for (const m of apeluri('generateKeySync|generateKey')) { const p = argsDe(m); const v = valoareArgument(ctx, p[0]); if ((v.fel === 'literal' || v.fel === 'rezolvat') && v.valoare.toLowerCase() === 'aes') { const len = p[1] && /length\s*:\s*(\d+)/.exec(p[1].text); ctx.adauga(m.index, m[2], 'call', { grup: 'CIPHER', nume: 'AES', param: len ? len[1] : undefined, functii: ['keygen'] }, { bucata: `${m[0]}'aes'` }); } } for (const m of apeluri('publicEncrypt|privateDecrypt|privateEncrypt|publicDecrypt')) { const api = m[2]; const a = argumente(ctx, m.index + m[0].length); const t = a.text; const enc = api === 'publicEncrypt' || api === 'privateDecrypt'; let padding = enc ? 'oaep' : 'pkcs1v15'; if (/RSA_PKCS1_OAEP_PADDING|oaepHash/.test(t)) padding = 'oaep'; else if (/RSA_PKCS1_PADDING/.test(t)) padding = 'pkcs1v15'; else if (/RSA_NO_PADDING/.test(t)) padding = 'raw'; ctx.adauga(m.index, api, 'call', { grup: 'RSA', primitiv: enc ? 'pke' : 'signature', padding, functii: [api === 'publicEncrypt' || api === 'privateEncrypt' ? 'encrypt' : 'decrypt'] }, { bucata: m[0] }); } for (const m of apeluri('pbkdf2Sync|pbkdf2')) { const p = argsDe(m); cuValoare(m, p[4], m[2], (val, ex) => { const h = hashCanonic(val) || val; ctx.adauga(m.index, m[2], 'call', { grup: 'KDF', nume: `PBKDF2-HMAC-${val.toUpperCase()}`, hash: h, functii: ['keyderive'] }, { ...ex, bucata: m[0] }); }); } for (const m of apeluri('hkdfSync|hkdf')) { const p = argsDe(m); cuValoare(m, p[0], m[2], (val, ex) => ctx.adauga(m.index, m[2], 'call', { grup: 'KDF', nume: `HKDF-${val.toUpperCase()}`, hash: hashCanonic(val) || val, functii: ['keyderive'] }, { ...ex, bucata: m[0] })); } // chei si certificate incarcate la rulare: algoritmul vine din material, nu din sursa for (const m of [...apeluri('createPrivateKey|createPublicKey'), ...ctx.potriviri(/(? !x[1] || aliasCrypto.has(x[1]))]) { const api = m[2]; ctx.adauga(m.index, api, 'call', { grup: 'UNKNOWN', motiv: `${api}: key material loaded at run time; its algorithm (RSA, EC, Ed25519, ML-DSA, ...) comes from the key or certificate, which is not in the scanned source. This is a place where keys enter the program: check which algorithm is deployed there.`, functii: ['other'] }, { bucata: m[0] }); } // crypto.sign / crypto.verify (si importurile numite sign/verify) for (const m of ctx.potriviri(/(? ctx.adauga(m.index, api, 'call', { grup: 'CLASSIC-SIG', hash: hashCanonic(val) || val, motiv: `${api} with digest ${val} (a digest name is used with RSA, RSA-PSS, DSA and EC keys; EdDSA and ML-DSA keys take null)`, functii: [m[2]] }, { ...ex, bucata: `${m[0]}'${val}'` })); } } // generateKeyPair: node:crypto sau jose (acolo primul argument e un algoritm JWS) const jwtLib = importa(/^(jsonwebtoken|jose|jwt-simple|express-jwt|@fastify\/jwt|passport-jwt|koa-jwt|fast-jwt|jws)$/); for (const m of ctx.potriviri(/(? { if (JWS_RE.test(val) && jwtLib.length) return; // se raporteaza de regula JWT const t = val.toLowerCase(); const ml = /modulusLength\s*:\s*(\d+)/.exec(opt); const nc = /namedCurve\s*:\s*(['"])([^'"]+)\1/.exec(opt); let act; if (t === 'rsa') act = { grup: 'RSA', param: ml && ml[1] }; else if (t === 'rsa-pss') act = { grup: 'RSA', param: ml && ml[1], primitiv: 'signature', padding: 'other' }; else if (t === 'dsa') act = { grup: 'DSA', param: ml && ml[1] }; else if (t === 'ec') act = { grup: 'EC', curba: nc && nc[2] }; else if (t === 'ed25519' || t === 'ed448') act = { grup: 'EDDSA', nume: t === 'ed25519' ? 'Ed25519' : 'Ed448' }; else if (t === 'x25519' || t === 'x448') act = { grup: 'XDH', nume: t === 'x25519' ? 'X25519' : 'X448' }; else if (t === 'dh') { const pl = /primeLength\s*:\s*(\d+)/.exec(opt); const gr = /group\s*:\s*(['"])(modp\d+)\1/.exec(opt); act = { grup: 'DH', param: pl ? pl[1] : gr ? String(MODP[gr[2]] || gr[2]) : undefined }; } else if (/^ml-dsa-(44|65|87)$/.test(t)) act = { grup: 'MLDSA', param: t.slice(7) }; else if (/^ml-kem-(512|768|1024)$/.test(t)) act = { grup: 'MLKEM', param: t.slice(7) }; else if (/^slh-dsa-(sha2|shake)-(128|192|256)[sf]$/.test(t)) act = { grup: 'SLHDSA', param: t.slice(8).toUpperCase().replace(/([SF])$/, (x) => x.toLowerCase()) }; else act = { grup: 'UNKNOWN', motiv: `Key type "${val}" is not in this tool's catalog.` }; ctx.adauga(m.index, m[2], 'call', { ...act, functii: ['keygen'] }, { ...ex, bucata: `${m[0]}'${val}'` }); }); } detecteazaTlsJs(ctx); detecteazaWebCrypto(ctx); detecteazaBiblioteci(ctx, imp, importa, jwtLib); } function detecteazaTlsJs(ctx) { for (const m of ctx.potriviri(/(? 80 ? t.slice(0, 77) + '...' : t; } // valoarea unei expresii: identificator rezolvat, sau template ale carui ${ID} se rezolva toate; altfel null function valoareExpresie(ctx, expr) { const id = identificator(expr); if (id) { const r = rezolva(ctx, id); return r ? { valoare: r.valoare, din: `${id} (line ${r.line})` } : null; } const t = /^`((?:[^`$\\]|\$\{\s*[A-Za-z_$][\w$]*\s*\})*)`$/.exec(expr); if (!t) return null; const din = []; let ok = true; const val = t[1].replace(/\$\{\s*([A-Za-z_$][\w$]*)\s*\}/g, (_, n) => { const r = rezolva(ctx, n); if (!r) { ok = false; return ''; } din.push(`${n} (line ${r.line})`); return r.valoare; }); return ok ? { valoare: val, din: din.join(', ') } : null; } const WEBCRYPTO_ALG = /^(RSA-OAEP|RSASSA-PKCS1-v1_5|RSA-PSS|ECDSA|ECDH|Ed25519|Ed448|X25519|X448|AES-GCM|AES-CBC|AES-CTR|AES-KW|HMAC|HKDF|PBKDF2|ML-KEM-(?:512|768|1024)|ML-DSA-(?:44|65|87)|ChaCha20-Poly1305)$/i; function activWebCrypto(nume, fereastra) { const n = nume.toUpperCase(); const ml = /modulusLength\s*:\s*(\d+)/.exec(fereastra); const nc = /namedCurve\s*:\s*(['"])([^'"]+)\1/.exec(fereastra); const len = /(? lista[0]; // Ethereum: conturile si semnaturile sunt secp256k1 ECDSA const eth = importa(ETH); if (eth.length) { const i = primul(eth); ctx.adauga(i.pos, `import ${i.spec}`, 'import', { grup: 'SECP256K1', nota: `Imported library: ${i.spec}, whose accounts and transaction signatures are secp256k1 ECDSA; the import alone does not show that this file signs.` }, { suprimaDe: ['SECP256K1'], bucata: `import ${i.spec}` }); const re1 = /(?