511 lines
30 KiB
JavaScript
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;
|
|
}
|