aere-quantum/crypto-inventory/lib/detect-js.mjs

511 lines
30 KiB
JavaScript

// 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(/(?<![\w$.])import\s+((?:type\s+)?(?:[\w$*{},\s]|\bas\b)+?)\s*from\s*(['"])([^'"\n]+)\2/g)) {
r.push(descrieImport(m[3], m.index, m[1]));
}
for (const m of ctx.potriviri(/(?<![\w$.])import\s*(['"])([^'"\n]+)\1/g)) r.push(descrieImport(m[2], m.index, ''));
for (const m of ctx.potriviri(/(?<![\w$.])(?:(?:const|let|var)\s+([\w$]+|\{[^}]*\})\s*=\s*(?:await\s+)?)?(?:require|import)\s*\(\s*(['"])([^'"\n]+)\2\s*\)(?:\s*\.\s*([\w$]+))?/g)) {
const leg = m[1] || '';
r.push(descrieImport(m[3], m.index, leg.startsWith('{') ? leg : (leg ? leg : ''), !leg.startsWith('{') && leg ? leg : null, m[4]));
}
return r;
}
// un import printr-o cale relativa in node_modules (../x/node_modules/@noble/curves/secp256k1.js)
// e acelasi pachet: se pastreaza ce urmeaza dupa ultimul node_modules/, fara extensie
export function normalizeazaSpec(spec) {
let s = String(spec);
const k = s.lastIndexOf('node_modules/');
if (k >= 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(/(?<![\w$.])crypto\s*\.\s*(createHash|createHmac|createCipheriv|createDecipheriv|createSign|createVerify|createECDH|generateKeyPairSync|generateKeyPair|generateKeySync|publicEncrypt|privateDecrypt|getDiffieHellman|createDiffieHellman|pbkdf2Sync|pbkdf2|hkdfSync)\s*\(/g).length > 0;
const apeluri = (nume) => ctx.potriviri(new RegExp(`(?<![\\w$.])(?:([\\w$]+)\\s*\\.\\s*)?(${nume})\\s*\\(`, 'g'))
.filter((m) => 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(/(?<![\w$.])new\s+(?:([\w$]+)\s*\.\s*)?(X509Certificate)\s*\(/g).filter((x) => !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(/(?<![\w$.])(?:([\w$]+)\s*\.\s*)?(sign|verify)\s*\(/g)) {
if (m[1] ? !aliasCrypto.has(m[1]) : !numeImportate.has(m[2])) continue;
const p = argsDe(m);
const api = `crypto.${m[2]}`;
if (!p[0] || /^(null|undefined)$/.test(p[0].text)) {
ctx.adauga(m.index, api, 'call', { grup: 'UNKNOWN', motiv: `${api} with a null algorithm: the algorithm is determined by the key object (for example Ed25519, Ed448, ML-DSA, RSA or ECDSA), which is not visible statically.`, functii: [m[2]] }, { bucata: `${m[0]}null` });
continue;
}
cuValoare(m, p[0], api, (val, ex) => 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(/(?<![\w$.])(?:([\w$]+)\s*\.\s*)?(generateKeyPairSync|generateKeyPair)\s*\(/g)) {
if (!nodeCrypto && !jwtLib.length) continue;
const a = argumente(ctx, m.index + m[0].length);
const p = imparteArgumente(ctx, a.start, a.end);
const opt = p[1] ? p[1].text : '';
cuValoare(m, p[0], m[2], (val, ex) => {
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(/(?<![\w$])(minVersion|maxVersion|DEFAULT_MIN_VERSION|DEFAULT_MAX_VERSION)\s*[:=]\s*(['"])(TLSv1(?:\.[0-3])?|SSLv3)\2/g)) {
const rol = /min/i.test(m[1]) ? 'min' : 'max';
ctx.adauga(m.index, `tls ${m[1]}`, 'config', { grup: 'TLS', param: TLSV[m[3]], rol }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])secureProtocol\s*:\s*(['"])(\w+)\1/g)) {
const map = { SSLv2_method: 'ssl2', SSLv3_method: 'ssl3', TLSv1_method: '1.0', TLSv1_1_method: '1.1', TLSv1_2_method: '1.2', TLS_method: 'negotiated', SSLv23_method: 'negotiated' };
const v = map[m[2].replace(/_(client|server)_method$/, '_method')];
if (v) ctx.adauga(m.index, 'tls secureProtocol', 'config', { grup: 'TLS', param: v, rol: 'only' }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])ecdhCurve\s*:\s*/g)) {
const baza = m.index + m[0].length;
const expr = expresiePanaLaVirgula(ctx, baza);
const lit = /^(['"])([^'"\n]*)\1$/.exec(expr);
if (lit) {
// fiecare grup la coloana lui din sir
let off = 1;
for (const g of lit[2].split(/[:,/]/)) {
const t = g.trim().replace(/^[*?]+/, '');
if (t && t !== 'DEFAULT') ctx.adauga(baza + off + g.indexOf(t), 'tls ecdhCurve', 'config', { ...grupTls(t), functii: ['keygen'] }, { bucata: `ecdhCurve: ${t}` });
off += g.length + 1;
}
continue;
}
const v = valoareExpresie(ctx, expr);
if (!v) {
ctx.adauga(m.index, 'tls ecdhCurve', 'config', necunoscut(expr || '?', 'tls ecdhCurve'), { bucata: m[0] + expr });
continue;
}
for (const g of v.valoare.split(/[:,/]/)) {
const t = g.trim().replace(/^[*?]+/, '');
if (t && t !== 'DEFAULT') ctx.adauga(m.index, 'tls ecdhCurve', 'config', { ...grupTls(t), functii: ['keygen'] }, { bucata: `ecdhCurve: ${t}`, rezolvatDin: v.din });
}
}
}
// textul unei expresii pana la virgula, acolada sau paranteza de nivel zero (fara siruri), max 80 caractere
function expresiePanaLaVirgula(ctx, start) {
const c = ctx.code;
let adancime = 0;
let j = start;
while (j < c.length && j - start < 400) {
if (!ctx.inSir(j)) {
const ch = c[j];
if (ch === '(' || ch === '[' || ch === '{') adancime++;
else if (ch === ')' || ch === ']' || ch === '}') { if (adancime === 0) break; adancime--; }
else if ((ch === ',' || ch === '\n' || ch === ';') && adancime === 0) break;
}
j++;
}
const t = c.slice(start, j).trim();
return t.length > 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 = /(?<![\w$])length\s*:\s*(\d+)/.exec(fereastra);
const hm = /hash\s*:\s*(?:\{\s*name\s*:\s*)?(['"])([^'"]+)\1/.exec(fereastra);
const hash = hm ? hashCanonic(hm[2]) || hm[2] : undefined;
if (n === 'RSA-OAEP') return { grup: 'RSA', primitiv: 'pke', padding: 'oaep', param: ml && ml[1] };
if (n === 'RSASSA-PKCS1-V1_5') return { grup: 'RSA', primitiv: 'signature', padding: 'pkcs1v15', param: ml && ml[1], hash };
if (n === 'RSA-PSS') return { grup: 'RSA', primitiv: 'signature', padding: 'other', param: ml && ml[1], hash };
if (n === 'ECDSA') return { grup: 'ECDSA', curba: nc && nc[2], hash };
if (n === 'ECDH') return { grup: 'ECDH', curba: nc && nc[2] };
if (n === 'ED25519' || n === 'ED448') return { grup: 'EDDSA', nume: n === 'ED25519' ? 'Ed25519' : 'Ed448' };
if (n === 'X25519' || n === 'X448') return { grup: 'XDH', nume: n === 'X25519' ? 'X25519' : 'X448' };
if (/^AES-/.test(n)) return { grup: 'CIPHER', nume: 'AES', param: len && len[1], mod: n.slice(4).toLowerCase() };
if (n === 'HMAC') return { grup: 'MAC', hash };
if (n === 'HKDF') return { grup: 'KDF', nume: 'HKDF', hash };
if (n === 'PBKDF2') return { grup: 'KDF', nume: 'PBKDF2', hash };
if (/^ML-KEM-/.test(n)) return { grup: 'MLKEM', param: n.slice(7) };
if (/^ML-DSA-/.test(n)) return { grup: 'MLDSA', param: n.slice(7) };
if (n === 'CHACHA20-POLY1305') return { grup: 'CIPHER', nume: 'ChaCha20-Poly1305' };
return null;
}
function detecteazaWebCrypto(ctx) {
if (!ctx.potriviri(/(?<![\w$])subtle\b/g).length) return;
for (const m of ctx.potriviri(/(?<![\w$])name\s*:\s*(['"])([\w-]+)\1/g)) {
if (!WEBCRYPTO_ALG.test(m[2])) continue;
const ob = obiectulDin(ctx, m.index);
const act = activWebCrypto(m[2], ob ? ob.text : '');
if (act) ctx.adauga(m.index, 'WebCrypto', 'call', act, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])subtle\s*\.\s*(generateKey|importKey|sign|verify|encrypt|decrypt|deriveBits|deriveKey|digest|encapsulateKey|encapsulateBits|decapsulateKey|decapsulateBits)\s*\(/g)) {
const a = argumente(ctx, m.index + m[0].length);
const p = imparteArgumente(ctx, a.start, a.end);
const idx = m[1] === 'importKey' ? 2 : 0;
const arg = p[idx];
if (!arg) continue;
const v = valoareArgument(ctx, arg);
if (m[1] === 'digest') {
if (v.fel === 'literal' || v.fel === 'rezolvat') ctx.adauga(m.index, 'subtle.digest', 'call', { grup: 'HASH', hash: hashCanonic(v.valoare) || v.valoare, functii: ['digest'] }, { bucata: `${m[0]}'${v.valoare}'`, rezolvatDin: v.din });
else {
const ob = /name\s*:\s*(['"])([^'"]+)\1/.exec(arg.text);
if (ob) ctx.adauga(m.index, 'subtle.digest', 'call', { grup: 'HASH', hash: hashCanonic(ob[2]) || ob[2], functii: ['digest'] }, { bucata: `${m[0]}{name:'${ob[2]}'}` });
else if (v.fel === 'necunoscut') ctx.adauga(m.index, 'subtle.digest', 'call', necunoscut(v.expresie, 'subtle.digest'), { bucata: m[0] });
}
continue;
}
if ((v.fel === 'literal' || v.fel === 'rezolvat') && WEBCRYPTO_ALG.test(v.valoare)) {
const act = activWebCrypto(v.valoare, '');
if (act) ctx.adauga(arg.start, `subtle.${m[1]}`, 'call', act, { bucata: `subtle.${m[1]}('${v.valoare}')`, rezolvatDin: v.din });
}
}
}
const ETH = /^(ethers|web3|viem|viem\/accounts|@ethersproject\/[\w-]+|ethereumjs-[\w-]+|@ethereumjs\/[\w-]+|ethereum-cryptography(\/.*)?|web3-eth-accounts)$/;
const SECP_LIB = /^(secp256k1|tiny-secp256k1|@noble\/secp256k1|@bitcoinerlab\/secp256k1|ecpair|bitcoinjs-lib|eccrypto)$/;
function detecteazaBiblioteci(ctx, imp, importa, jwtLib) {
const c = ctx.code;
const primul = (lista) => 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 = /(?<![\w$.])(?:new\s+(?:[\w$]+\s*\.\s*)*(?:Wallet|SigningKey|HDNodeWallet)\s*\(|(?:[\w$]+\s*\.\s*)?(?:(?:Wallet|HDNodeWallet)\s*\.\s*(?:createRandom|fromPhrase|fromMnemonic|fromEncryptedJson|fromEncryptedJsonSync|fromSeed)|privateKeyToAccount|mnemonicToAccount|generatePrivateKey|recoverAddress|recoverMessageAddress|verifyMessage|verifyTypedData|recoverPublicKey|ecrecover|ecsign)\s*\()/g;
const re2 = /\.\s*(?:signMessage|signTransaction|signTypedData|_signTypedData|signAuthorization)\s*\(|\baccounts\s*\.\s*(?:create|sign|signTransaction|privateKeyToAccount|recover)\s*\(/g;
for (const m of [...ctx.potriviri(re1), ...ctx.potriviri(re2)]) {
const f = /verify|recover/i.test(m[0]) ? ['verify'] : /sign/i.test(m[0]) ? ['sign'] : ['keygen'];
ctx.adauga(m.index, m[0].replace(/[\s(]/g, ''), 'call', { grup: 'SECP256K1', functii: f }, { bucata: m[0] });
}
}
for (const i of importa(SECP_LIB)) {
ctx.adauga(i.pos, `import ${i.spec}`, 'import', { grup: 'SECP256K1', nota: `Imported library: ${i.spec} (secp256k1 only).` }, { bucata: `import ${i.spec}` });
}
// elliptic
const ell = importa(/^elliptic$/);
if (ell.length) {
for (const m of ctx.potriviri(/(?<![\w$.])new\s+(?:[\w$]+\s*\.\s*)?(ec|EC)\s*\(\s*(['"])([\w-]+)\2/g)) {
ctx.adauga(m.index, 'elliptic ec', 'call', { grup: 'EC', curba: m[3], functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$.])new\s+(?:[\w$]+\s*\.\s*)?(eddsa|EdDSA)\s*\(\s*(['"])([\w-]+)\2/g)) {
ctx.adauga(m.index, 'elliptic eddsa', 'call', { grup: 'EDDSA', nume: /448/.test(m[3]) ? 'Ed448' : 'Ed25519' }, { bucata: m[0] });
}
ctx.adauga(ell[0].pos, 'import elliptic', 'import', { grup: 'LIB-MULTI', nume: 'elliptic', motiv: 'Imports elliptic (classical elliptic-curve library, quantum-vulnerable in every mode); no curve construction recognized in this file.' }, { suprimaDe: ['EC', 'ECDSA', 'ECDH', 'SECP256K1', 'EDDSA'], bucata: 'import elliptic' });
}
// node-forge
const forge = importa(/^node-forge$/);
if (forge.length) {
for (const m of ctx.potriviri(/(?<![\w$])pki\s*\.\s*rsa\s*\.\s*generateKeyPair\s*\(/g)) {
const a = argumente(ctx, m.index + m[0].length);
const b = /bits\s*:\s*(\d+)/.exec(a.text) || /^\s*(\d+)/.exec(a.text);
ctx.adauga(m.index, 'forge.pki.rsa.generateKeyPair', 'call', { grup: 'RSA', param: b && b[1], functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])md\s*\.\s*(md5|sha1|sha256|sha384|sha512)\s*\.\s*create\s*\(/g)) {
ctx.adauga(m.index, `forge.md.${m[1]}`, 'call', { grup: 'HASH', hash: hashCanonic(m[1]), functii: ['digest'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])cipher\s*\.\s*create(?:De)?cipher\s*\(\s*(['"])([\w-]+)\1/g)) {
const t = /^(AES|3DES|DES|RC2)(?:-(\w+))?$/i.exec(m[2]);
ctx.adauga(m.index, 'forge.cipher', 'call', t ? { grup: 'CIPHER', nume: t[1].toUpperCase(), mod: t[2] } : { grup: 'CIPHER', nume: m[2] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$])ed25519\s*\.\s*(generateKeyPair|sign|verify)\s*\(/g)) {
ctx.adauga(m.index, `forge.ed25519.${m[1]}`, 'call', { grup: 'EDDSA', nume: 'Ed25519' }, { bucata: m[0] });
}
ctx.adauga(forge[0].pos, 'import node-forge', 'import', { grup: 'LIB-MULTI', nume: 'node-forge' }, { suprimaDe: ['RSA', 'HASH', 'CIPHER', 'EDDSA'], bucata: 'import node-forge' });
}
// JWT: literalele de algoritm JWS din fisierele care importa o biblioteca JWT
if (jwtLib.length) {
let gasit = false;
for (const [s, e] of ctx.siruri) {
if (!`'"`.includes(ctx.text[s]) || ctx.text[e - 1] !== ctx.text[s]) continue;
const v = ctx.text.slice(s + 1, e - 1);
if (!JWS_RE.test(v)) continue;
if (v === 'none') {
const inainte = ctx.code.slice(Math.max(0, s - 60), s);
if (!/alg(?:orithms?)?\s*:\s*\[?[^\]\n]*$/.test(inainte)) continue;
}
gasit = true;
ctx.adauga(s, 'JWT algorithm', 'config', { ...jws(v), functii: ['sign', 'verify'] }, { bucata: `'${v}'` });
}
if (!gasit) {
ctx.adauga(jwtLib[0].pos, `import ${jwtLib[0].spec}`, 'import', { grup: 'LIB-MULTI', nume: jwtLib[0].spec, motiv: `Imports ${jwtLib[0].spec}; no JWS algorithm literal found in this file (the algorithm may come from configuration or library defaults).` }, { bucata: `import ${jwtLib[0].spec}` });
}
}
// @noble/curves
for (const i of importa(/^@noble\/curves(\/.*)?$/)) {
const map = {
secp256k1: { grup: 'SECP256K1' }, schnorr: { grup: 'SECP256K1', nume: 'Schnorr-secp256k1' },
ed25519: { grup: 'EDDSA', nume: 'Ed25519' }, ed25519ph: { grup: 'EDDSA', nume: 'Ed25519' }, ed25519ctx: { grup: 'EDDSA', nume: 'Ed25519' },
x25519: { grup: 'XDH', nume: 'X25519' }, ed448: { grup: 'EDDSA', nume: 'Ed448' }, x448: { grup: 'XDH', nume: 'X448' },
p256: { grup: 'EC', curba: 'secp256r1' }, secp256r1: { grup: 'EC', curba: 'secp256r1' },
p384: { grup: 'EC', curba: 'secp384r1' }, secp384r1: { grup: 'EC', curba: 'secp384r1' },
p521: { grup: 'EC', curba: 'secp521r1' }, secp521r1: { grup: 'EC', curba: 'secp521r1' },
bls12_381: { grup: 'PAIRING', nume: 'BLS12-381' }, bn254: { grup: 'PAIRING', nume: 'BN254' },
};
const sub = (/^@noble\/curves\/([\w-]+)/.exec(i.spec) || [])[1];
const nume = i.importate.length ? i.importate : (sub ? [sub.replace(/-/g, '_').replace('bls12_381', 'bls12_381')] : []);
for (const n of nume) {
const act = map[n] || (n === 'nist' ? { grup: 'EC' } : null);
if (act) ctx.adauga(i.pos, `import ${i.spec}`, 'import', act, { bucata: `import { ${n} } from '${i.spec}'` });
}
}
// @noble/post-quantum
for (const i of importa(/^@noble\/post-quantum(\/.*)?$/)) {
const sub = (/^@noble\/post-quantum\/([\w-]+)/.exec(i.spec) || [])[1] || '';
const lista = i.importate.length ? i.importate : [sub];
for (const n of lista) {
let m;
let act = null;
if ((m = /^ml_kem(512|768|1024)$/.exec(n))) act = { grup: 'MLKEM', param: m[1] };
else if ((m = /^ml_dsa(44|65|87)$/.exec(n))) act = { grup: 'MLDSA', param: m[1] };
else if ((m = /^slh_dsa_(sha2|shake)_(128|192|256)([sf])$/.exec(n))) act = { grup: 'SLHDSA', param: `${m[1].toUpperCase()}-${m[2]}${m[3]}` };
else if ((m = /^ml_kem(768|1024)_(x25519|p256|p384)$/i.exec(n))) act = { grup: 'HYBRID-KEX', nume: `ML-KEM-${m[1]}+${m[2].toUpperCase()}` };
else if (/^xwing$/i.test(n)) act = { grup: 'HYBRID-KEX', nume: 'X-Wing' };
else if (n === 'ml-kem') act = { grup: 'MLKEM' };
else if (n === 'ml-dsa') act = { grup: 'MLDSA' };
else if (n === 'slh-dsa') act = { grup: 'SLHDSA' };
if (act) ctx.adauga(i.pos, `import ${i.spec}`, 'import', act, { bucata: `import { ${n} } from '${i.spec}'` });
}
}
// @noble/hashes
for (const i of importa(/^@noble\/hashes(\/.*)?$/)) {
for (const n of i.importate) {
const h = hashCanonic(n.replace(/_/g, '-')) || ({ keccak_256: 'KECCAK256', sha3_256: 'SHA3-256', sha3_512: 'SHA3-512', ripemd160: 'RIPEMD160' })[n];
if (h) ctx.adauga(i.pos, `import ${i.spec}`, 'import', { grup: 'HASH', hash: h }, { bucata: `import { ${n} } from '${i.spec}'` });
}
}
// crypto-js
const cjs = importa(/^crypto-js(\/.*)?$/);
if (cjs.length) {
for (const m of ctx.potriviri(/(?<![\w$.])[\w$]+\s*\.\s*(MD5|SHA1|SHA224|SHA256|SHA384|SHA512|RIPEMD160|HmacMD5|HmacSHA1|HmacSHA256|HmacSHA384|HmacSHA512)\s*\(/g)) {
const hmac = /^Hmac/.test(m[1]);
const h = hashCanonic(m[1].replace(/^Hmac/, ''));
ctx.adauga(m.index, `CryptoJS.${m[1]}`, 'call', hmac ? { grup: 'MAC', hash: h, functii: ['tag'] } : { grup: 'HASH', hash: h, functii: ['digest'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$.])[\w$]+\s*\.\s*(AES|DES|TripleDES|RC4|RC4Drop|Rabbit|Blowfish)\s*\.\s*(encrypt|decrypt)\s*\(/g)) {
const nume = m[1] === 'TripleDES' ? '3DES' : m[1] === 'RC4Drop' ? 'RC4' : m[1];
ctx.adauga(m.index, `CryptoJS.${m[1]}.${m[2]}`, 'call', { grup: 'CIPHER', nume, functii: [m[2]] }, { bucata: m[0] });
}
ctx.adauga(cjs[0].pos, `import ${cjs[0].spec}`, 'import', { grup: 'LIB-MULTI', nume: 'crypto-js' }, { suprimaDe: ['HASH', 'MAC', 'CIPHER'], bucata: `import ${cjs[0].spec}` });
}
// tweetnacl / libsodium
const nacl = importa(/^(tweetnacl|libsodium-wrappers|libsodium-wrappers-sumo|sodium-native)$/);
if (nacl.length) {
for (const m of ctx.potriviri(/(?<![\w$.])[\w$]+\s*\.\s*(sign(?:\s*\.\s*(?:keyPair|detached))?|crypto_sign\w*)\s*\(/g)) {
ctx.adauga(m.index, 'nacl.sign', 'call', { grup: 'EDDSA', nume: 'Ed25519', functii: ['sign'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$.])[\w$]+\s*\.\s*(box(?:\s*\.\s*(?:keyPair|before))?|crypto_box\w*|crypto_kx\w*|crypto_scalarmult\w*)\s*\(/g)) {
ctx.adauga(m.index, 'nacl.box', 'call', { grup: 'XDH', nume: 'X25519', functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w$.])[\w$]+\s*\.\s*(secretbox|crypto_secretbox\w*|crypto_aead_xchacha20poly1305\w*)\s*\(/g)) {
ctx.adauga(m.index, 'nacl.secretbox', 'call', { grup: 'CIPHER', nume: /xchacha/.test(m[1]) ? 'XChaCha20-Poly1305' : 'XSalsa20-Poly1305', functii: ['encrypt'] }, { bucata: m[0] });
}
ctx.adauga(nacl[0].pos, `import ${nacl[0].spec}`, 'import', { grup: 'LIB-MULTI', nume: nacl[0].spec }, { suprimaDe: ['EDDSA', 'XDH', 'CIPHER'], bucata: `import ${nacl[0].spec}` });
}
// node-rsa
const nrsa = importa(/^node-rsa$/);
if (nrsa.length) {
for (const m of ctx.potriviri(/(?<![\w$.])new\s+[\w$]+\s*\(\s*\{\s*b\s*:\s*(\d+)/g)) {
ctx.adauga(m.index, 'new NodeRSA', 'call', { grup: 'RSA', param: m[1], functii: ['keygen'] }, { bucata: m[0] });
}
ctx.adauga(nrsa[0].pos, 'import node-rsa', 'import', { grup: 'RSA' }, { suprimaDe: ['RSA'], bucata: 'import node-rsa' });
}
// parole: bcrypt / argon2 / scrypt
for (const i of importa(/^(bcrypt|bcryptjs|argon2|@node-rs\/argon2|@node-rs\/bcrypt|scrypt-js)$/)) {
const nume = /argon2/.test(i.spec) ? 'Argon2' : /scrypt/.test(i.spec) ? 'scrypt' : 'bcrypt';
ctx.adauga(i.pos, `import ${i.spec}`, 'import', { grup: 'KDF', nume }, { bucata: `import ${i.spec}` });
}
// biblioteci cu multi algoritmi, fara apel recunoscut
for (const i of importa(/^(openpgp|jsrsasign|sshpk|@peculiar\/x509|pkijs|node-jose)$/)) {
ctx.adauga(i.pos, `import ${i.spec}`, 'import', { grup: 'LIB-MULTI', nume: i.spec }, { bucata: `import ${i.spec}` });
}
void c;
}