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

209 lines
14 KiB
JavaScript

// Detectorul Python: cryptography (hazmat), PyCryptodome, hashlib, hmac, ecdsa, PyJWT/jose, oqs, ssl.
import { argumente, imparteArgumente, valoareArgument, necunoscut } from './context.mjs';
import { hashCanonic, jws, JWS_RE } from './catalog.mjs';
const CURBE_PY = {
SECP256R1: 'secp256r1', SECP384R1: 'secp384r1', SECP521R1: 'secp521r1', SECP224R1: 'secp224r1', SECP192R1: 'secp192r1',
SECP256K1: 'secp256k1', BrainpoolP256R1: 'brainpoolP256r1', BrainpoolP384R1: 'brainpoolP384r1', BrainpoolP512R1: 'brainpoolP512r1',
SECT571R1: 'sect571r1', SECT409R1: 'sect409r1', SECT283R1: 'sect283r1', SECT233R1: 'sect233r1', SECT163R2: 'sect163r2',
};
const CURBE_ECDSA_LIB = { SECP256k1: 'secp256k1', NIST192p: 'secp192r1', NIST224p: 'secp224r1', NIST256p: 'secp256r1', NIST384p: 'secp384r1', NIST521p: 'secp521r1', BRAINPOOLP256r1: 'brainpoolP256r1', BRAINPOOLP384r1: 'brainpoolP384r1', BRAINPOOLP512r1: 'brainpoolP512r1' };
function importaPy(ctx, re) {
return ctx.potriviri(/^[ \t]*(?:from\s+([\w.]+)\s+import\b|import\s+([\w.]+(?:\s*,\s*[\w.]+)*))/gm)
.filter((m) => (m[1] ? re.test(m[1]) : m[2].split(',').some((x) => re.test(x.trim()))));
}
export function detecteazaPy(ctx) {
const argsDe = (m) => { const a = argumente(ctx, m.index + m[0].length); return { ...a, parti: 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;
};
const numar = (a, cheie, poz) => {
const k = new RegExp(`\\b${cheie}\\s*=\\s*(\\d+)`).exec(a.text);
if (k) return k[1];
if (poz !== undefined && a.parti[poz] && /^\d+$/.test(a.parti[poz].text)) return a.parti[poz].text;
return undefined;
};
// --- cryptography.hazmat ---
if (importaPy(ctx, /^cryptography\b/).length) {
for (const m of ctx.potriviri(/(?<![\w.])(rsa|dsa|dh)\s*\.\s*(generate_private_key|generate_parameters)\s*\(/g)) {
const a = argsDe(m);
const bits = m[1] === 'rsa' ? numar(a, 'key_size', 1) : numar(a, 'key_size', m[1] === 'dh' ? 1 : 0);
ctx.adauga(m.index, `${m[1]}.${m[2]}`, 'call', { grup: m[1].toUpperCase(), param: bits, functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])ec\s*\.\s*(generate_private_key|derive_private_key)\s*\(/g)) {
const a = argsDe(m);
const k = /\bec\s*\.\s*(\w+)\s*\(/.exec(a.text);
ctx.adauga(m.index, `ec.${m[1]}`, 'call', { grup: 'EC', curba: k ? (CURBE_PY[k[1]] || k[1]) : undefined, functii: ['keygen'] }, { bucata: m[0] + (k ? k[0] : '') });
}
for (const m of ctx.potriviri(/(?<![\w.])ec\s*\.\s*ECDSA\s*\(/g)) {
const a = argsDe(m);
const h = /\bhashes\s*\.\s*(\w+)\s*\(/.exec(a.text);
ctx.adauga(m.index, 'ec.ECDSA', 'call', { grup: 'ECDSA', hash: h ? hashCanonic(h[1].replace(/_/g, '-')) : undefined, functii: ['sign', 'verify'] }, { bucata: m[0] + (h ? h[0] : '') });
}
for (const m of ctx.potriviri(/(?<![\w.])ec\s*\.\s*ECDH\s*\(/g)) {
ctx.adauga(m.index, 'ec.ECDH', 'call', { grup: 'ECDH', functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w])(?:\w+\s*\.\s*)?(Ed25519|Ed448|X25519|X448)(PrivateKey|PublicKey)\s*\.\s*(generate|from_private_bytes|from_public_bytes)\s*\(/g)) {
const ed = /^Ed/.test(m[1]);
ctx.adauga(m.index, `${m[1]}${m[2]}.${m[3]}`, 'call', { grup: ed ? 'EDDSA' : 'XDH', nume: m[1], functii: [m[3] === 'generate' ? 'keygen' : 'other'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])padding\s*\.\s*(OAEP|PSS|PKCS1v15)\s*\(/g)) {
const act = m[1] === 'OAEP' ? { grup: 'RSA', primitiv: 'pke', padding: 'oaep' } : m[1] === 'PSS' ? { grup: 'RSA', primitiv: 'signature', padding: 'other' } : { grup: 'RSA', primitiv: 'unknown', padding: 'pkcs1v15' };
ctx.adauga(m.index, `padding.${m[1]}`, 'call', act, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])hashes\s*\.\s*(MD5|SHA1|SHA224|SHA256|SHA384|SHA512|SHA512_224|SHA512_256|SHA3_224|SHA3_256|SHA3_384|SHA3_512|SHAKE128|SHAKE256|BLAKE2b|BLAKE2s|SM3)\s*\(/g)) {
const t = m[1].replace(/^SHA512_(224|256)$/, 'SHA512/$1').replace(/_/g, '-');
ctx.adauga(m.index, `hashes.${m[1]}`, 'call', { grup: 'HASH', hash: hashCanonic(t) || t, functii: ['digest'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])algorithms\s*\.\s*(AES|AES128|AES256|TripleDES|ARC4|Blowfish|CAST5|IDEA|SEED|ChaCha20|Camellia|SM4)\s*\(/g)) {
const map = { AES128: ['AES', '128'], AES256: ['AES', '256'], TripleDES: ['3DES'], ARC4: ['RC4'] };
const [nume, param] = map[m[1]] || [m[1]];
const a = argsDe(m);
const dupa = ctx.code.slice(a.end, a.end + 80);
const mod = /^\s*\)\s*,\s*modes\s*\.\s*(\w+)\s*\(/.exec(dupa);
ctx.adauga(m.index, `algorithms.${m[1]}`, 'call', { grup: 'CIPHER', nume, param, mod: mod ? mod[1].toLowerCase() : undefined, functii: ['encrypt'] }, { bucata: m[0] + (mod ? ` modes.${mod[1]}` : '') });
}
for (const m of ctx.potriviri(/(?<![\w.])(AESGCM|AESCCM|AESOCB3|AESSIV|AESGCMSIV|ChaCha20Poly1305)\s*(?:\.\s*generate_key\s*)?\(/g)) {
const a = argsDe(m);
const b = /generate_key/.test(m[0]) ? numar(a, 'bit_length', 0) : undefined;
const map = { AESGCM: 'gcm', AESCCM: 'ccm', AESOCB3: 'ocb', AESSIV: 'siv', AESGCMSIV: 'gcm-siv' };
const act = m[1] === 'ChaCha20Poly1305' ? { grup: 'CIPHER', nume: 'ChaCha20-Poly1305' } : { grup: 'CIPHER', nume: 'AES', param: b, mod: map[m[1]] };
ctx.adauga(m.index, m[1], 'call', { ...act, functii: [/generate_key/.test(m[0]) ? 'keygen' : 'encrypt'] }, { bucata: m[0] });
}
}
// --- PyCryptodome (Crypto / Cryptodome) ---
if (importaPy(ctx, /^(Crypto|Cryptodome)\b/).length) {
for (const m of ctx.potriviri(/(?<![\w.])(RSA|DSA)\s*\.\s*generate\s*\(/g)) {
const a = argsDe(m);
ctx.adauga(m.index, `${m[1]}.generate`, 'call', { grup: m[1], param: numar(a, 'bits', 0), functii: ['keygen'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])(RSA|DSA|ECC)\s*\.\s*(import_key|importKey|construct)\s*\(/g)) {
ctx.adauga(m.index, `${m[1]}.${m[2]}`, 'call', m[1] === 'ECC' ? { grup: 'EC' } : { grup: m[1] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])ECC\s*\.\s*generate\s*\(/g)) {
const a = argsDe(m);
const k = /\bcurve\s*=\s*(['"])([^'"]+)\1/.exec(a.text);
const curba = k ? k[2] : undefined;
const act = curba && /^ed(25519|448)$/i.test(curba) ? { grup: 'EDDSA', nume: /448/.test(curba) ? 'Ed448' : 'Ed25519' } : { grup: 'EC', curba };
ctx.adauga(m.index, 'ECC.generate', 'call', { ...act, functii: ['keygen'] }, { bucata: m[0] + (k ? k[0] : '') });
}
for (const m of ctx.potriviri(/(?<![\w.])(AES|DES|DES3|ARC4|ARC2|Blowfish|CAST|ChaCha20|ChaCha20_Poly1305|Salsa20)\s*\.\s*new\s*\(/g)) {
const a = argsDe(m);
const mod = /\b(?:AES|DES|DES3|ARC2|Blowfish|CAST)\s*\.\s*MODE_(\w+)/.exec(a.text);
const map = { DES3: '3DES', ARC4: 'RC4', ARC2: 'RC2', ChaCha20_Poly1305: 'ChaCha20-Poly1305' };
ctx.adauga(m.index, `${m[1]}.new`, 'call', { grup: 'CIPHER', nume: map[m[1]] || m[1], mod: mod ? mod[1].toLowerCase() : undefined, functii: ['encrypt'] }, { bucata: m[0] + (mod ? mod[0] : '') });
}
for (const m of ctx.potriviri(/(?<![\w.])(PKCS1_OAEP|PKCS1_v1_5|pkcs1_15|pss|DSS|eddsa)\s*\.\s*new\s*\(/g)) {
const map = {
PKCS1_OAEP: { grup: 'RSA', primitiv: 'pke', padding: 'oaep' },
PKCS1_v1_5: { grup: 'RSA', primitiv: 'unknown', padding: 'pkcs1v15' },
pkcs1_15: { grup: 'RSA', primitiv: 'signature', padding: 'pkcs1v15' },
pss: { grup: 'RSA', primitiv: 'signature', padding: 'other' },
DSS: { grup: 'CLASSIC-SIG', nume: 'DSS (DSA or ECDSA)', motiv: 'DSS signature (DSA or ECDSA, key type from the key object)' },
eddsa: { grup: 'EDDSA', nume: 'EdDSA' },
};
ctx.adauga(m.index, `${m[1]}.new`, 'call', map[m[1]], { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])(MD2|MD4|MD5|SHA1|SHA224|SHA256|SHA384|SHA512|SHA3_256|SHA3_384|SHA3_512|RIPEMD160|RIPEMD|BLAKE2b|BLAKE2s)\s*\.\s*new\s*\(/g)) {
const t = m[1].replace(/_/g, '-').replace(/^RIPEMD$/, 'RIPEMD160');
ctx.adauga(m.index, `${m[1]}.new`, 'call', { grup: 'HASH', hash: hashCanonic(t) || t, functii: ['digest'] }, { bucata: m[0] });
}
}
// --- hashlib ---
for (const m of ctx.potriviri(/(?<![\w.])hashlib\s*\.\s*(md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512|blake2b|blake2s|shake_128|shake_256)\b(\s*\()?/g)) {
let nota;
if (m[2]) {
const a = argumente(ctx, m.index + m[0].length);
if (/usedforsecurity\s*=\s*False/.test(a.text)) nota = 'Marked usedforsecurity=False: likely a non-security use (checksum); review and document.';
}
const t = m[1].replace(/^shake_/, 'SHAKE').replace(/_/g, '-');
ctx.adauga(m.index, `hashlib.${m[1]}`, 'call', { grup: 'HASH', hash: hashCanonic(t) || t, nota, functii: ['digest'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])hashlib\s*\.\s*new\s*\(/g)) {
const a = argsDe(m);
cuValoare(m, a.parti[0], 'hashlib.new', (val, ex) => ctx.adauga(m.index, 'hashlib.new', 'call', { grup: 'HASH', hash: hashCanonic(val) || val, functii: ['digest'] }, { ...ex, bucata: `hashlib.new('${val}')` }));
}
for (const m of ctx.potriviri(/(?<![\w.])hashlib\s*\.\s*pbkdf2_hmac\s*\(/g)) {
const a = argsDe(m);
cuValoare(m, a.parti[0], 'hashlib.pbkdf2_hmac', (val, ex) => ctx.adauga(m.index, 'hashlib.pbkdf2_hmac', 'call', { grup: 'KDF', nume: `PBKDF2-HMAC-${val.toUpperCase()}`, hash: hashCanonic(val) || val, functii: ['keyderive'] }, { ...ex, bucata: m[0] }));
}
for (const m of ctx.potriviri(/(?<![\w.])hmac\s*\.\s*(new|digest)\s*\(/g)) {
const a = argsDe(m);
const d = /\bdigestmod\s*=\s*(['"])(\w+)\1/.exec(a.text);
if (d) ctx.adauga(m.index, `hmac.${m[1]}`, 'call', { grup: 'MAC', hash: hashCanonic(d[2]) || d[2], functii: ['tag'] }, { bucata: m[0] + d[0] });
}
// --- ecdsa (python-ecdsa) ---
if (importaPy(ctx, /^ecdsa\b/).length) {
for (const m of ctx.potriviri(/(?<![\w.])(SigningKey|VerifyingKey)\s*\.\s*(generate|from_string|from_secret_exponent|from_pem|from_der|from_public_point)\s*\(/g)) {
const a = argsDe(m);
const k = /\bcurve\s*=\s*(?:ecdsa\s*\.\s*)?(\w+)/.exec(a.text);
let curba = k ? (CURBE_ECDSA_LIB[k[1]] || k[1]) : undefined;
let nota;
if (!k && m[2] === 'generate') { curba = 'secp192r1'; nota = 'No curve argument: python-ecdsa defaults to NIST192p.'; }
const act = curba && /^Ed(25519|448)$/.test(curba) ? { grup: 'EDDSA', nume: curba } : { grup: 'ECDSA', curba };
ctx.adauga(m.index, `${m[1]}.${m[2]}`, 'call', { ...act, nota, functii: [m[1] === 'SigningKey' ? 'sign' : 'verify'] }, { bucata: m[0] + (k ? k[0] : '') });
}
}
// --- JWT (PyJWT, python-jose, jwcrypto, authlib) ---
const jwtImp = importaPy(ctx, /^(jwt|jose|jwcrypto|authlib\.jose)\b/);
if (jwtImp.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(jwtImp[0].index, 'import jwt', 'import', { grup: 'LIB-MULTI', nume: 'JWT library', motiv: 'Imports a JWT library; no JWS algorithm literal found in this file (the algorithm may come from configuration or library defaults).' }, { bucata: jwtImp[0][0].trim() });
}
// --- liboqs-python ---
for (const m of ctx.potriviri(/(?<![\w.])oqs\s*\.\s*(KeyEncapsulation|Signature)\s*\(/g)) {
const a = argsDe(m);
cuValoare(m, a.parti[0], `oqs.${m[1]}`, (val, ex) => ctx.adauga(m.index, `oqs.${m[1]}`, 'call', oqsActiv(val), { ...ex, bucata: `oqs.${m[1]}('${val}')` }));
}
// --- ssl ---
for (const m of ctx.potriviri(/(?<![\w.])ssl\s*\.\s*PROTOCOL_(SSLv2|SSLv3|SSLv23|TLSv1_2|TLSv1_1|TLSv1|TLS_CLIENT|TLS_SERVER|TLS)\b/g)) {
const map = { SSLv2: 'ssl2', SSLv3: 'ssl3', TLSv1: '1.0', TLSv1_1: '1.1', TLSv1_2: '1.2' };
ctx.adauga(m.index, `ssl.PROTOCOL_${m[1]}`, 'config', { grup: 'TLS', param: map[m[1]] || 'negotiated', rol: 'only' }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])ssl\s*\.\s*TLSVersion\s*\.\s*(SSLv3|TLSv1_3|TLSv1_2|TLSv1_1|TLSv1)\b/g)) {
const map = { SSLv3: 'ssl3', TLSv1: '1.0', TLSv1_1: '1.1', TLSv1_2: '1.2', TLSv1_3: '1.3' };
const inainte = ctx.code.slice(Math.max(0, m.index - 40), m.index);
const rol = /maximum_version\s*=\s*$/.test(inainte) ? 'max' : 'min';
ctx.adauga(m.index, `ssl.TLSVersion.${m[1]}`, 'config', { grup: 'TLS', param: map[m[1]], rol }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/\.\s*set_ecdh_curve\s*\(/g)) {
const a = argsDe(m);
cuValoare(m, a.parti[0], 'set_ecdh_curve', (val, ex) => ctx.adauga(m.index, 'set_ecdh_curve', 'config', { grup: 'ECDH', curba: val, functii: ['keygen'] }, { ...ex, bucata: m[0] }));
}
}
export function oqsActiv(val) {
const t = String(val);
let m;
if ((m = /^ML-KEM-(512|768|1024)$/i.exec(t))) return { grup: 'MLKEM', param: m[1] };
if ((m = /^ML-DSA-(44|65|87)$/i.exec(t))) return { grup: 'MLDSA', param: m[1] };
if ((m = /^SLH[-_]DSA[-_](SHA2|SHAKE)[-_](128|192|256)([sf])/i.exec(t))) return { grup: 'SLHDSA', param: `${m[1].toUpperCase()}-${m[2]}${m[3].toLowerCase()}` };
if ((m = /^Falcon-(512|1024)$/i.exec(t))) return { grup: 'FALCON', param: m[1] };
if (/^(Kyber\d+|Dilithium\d|SPHINCS\+?-.*)$/i.test(t)) return { grup: 'PREPQ', nume: t, primitiv: /kyber/i.test(t) ? 'kem' : 'signature' };
if (/^HQC-\d+$/i.test(t)) return { grup: 'HQC', nume: t };
return { grup: 'UNKNOWN', motiv: `Post-quantum mechanism "${t}" is not in this tool's catalog.` };
}