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

450 lines
38 KiB
JavaScript

// Detectorul Java: JCA getInstance("..."), SSLContext, protocoale activate, Bouncy Castle.
// 0.4.0 (2026-10-18): acelasi cod serveste si Kotlin (`detecteazaJvm(ctx, true)`, chemat din detect-kotlin.mjs): JCA e acelasi API,
// fara `new`, fara `;`, cu `val`/`var` si `arrayOf(...)`; si regulile noi comune JVM-ului: Android Keystore (KeyGenParameterSpec),
// Tink (sabloanele de chei), OkHttp (ConnectionSpec, TlsVersion, CipherSuite, CertificatePinner) si verificarea lanturilor de
// certificate (CertPathValidator "PKIX", TrustManagerFactory, X509TrustManager, checkServerTrusted), raportata ca autentificare.
import { argumente, imparteArgumente, valoareArgument, necunoscut, escapeRe } from './context.mjs';
import { hashCanonic, grupTls, curbaCanonica } from './catalog.mjs';
const TLSV = { TLSv1: '1.0', 'TLSv1.1': '1.1', 'TLSv1.2': '1.2', 'TLSv1.3': '1.3', SSLv3: 'ssl3', SSLv2Hello: 'ssl2', SSL: 'negotiated', TLS: 'negotiated', Default: 'negotiated' };
// transforma numele JCA intr-un activ de catalog, dupa clasa care il cere
export function activJca(clasa, val, dupa = '', variabila = null) {
const peVar = (metoda, rest) => (variabila ? new RegExp(`(?<![\\w.])${variabila}\\s*\\.\\s*${metoda}\\s*\\(\\s*${rest}`).exec(dupa) : null);
const v = String(val).trim();
const u = v.toUpperCase();
let m;
const pq = pqNume(v);
if (pq) return pq;
if (clasa === 'MessageDigest') return { grup: 'HASH', hash: hashCanonic(v) || v, functii: ['digest'] };
if (clasa === 'Mac') {
if ((m = /^HMAC-?(\S+)$/i.exec(v))) return { grup: 'MAC', hash: hashCanonic(m[1]) || m[1], functii: ['tag'] };
return { grup: 'UNKNOWN', motiv: `Mac "${v}" is not in this tool's catalog.` };
}
if (clasa === 'SecretKeyFactory') {
if ((m = /^PBKDF2WithHmac(\w+)$/i.exec(v))) return { grup: 'KDF', nume: `PBKDF2-HMAC-${m[1].toUpperCase()}`, hash: hashCanonic(m[1]) || m[1], functii: ['keyderive'] };
if (/^PBEWith(MD5|SHA1)AndDES/i.test(v)) return { grup: 'CIPHER', nume: 'DES', nota: `${v} derives a DES key with a weak hash.` };
if (/^DESede$/i.test(v)) return { grup: 'CIPHER', nume: '3DES' };
if (/^DES$/i.test(v)) return { grup: 'CIPHER', nume: 'DES' };
return { grup: 'UNKNOWN', motiv: `SecretKeyFactory "${v}" is not in this tool's catalog.` };
}
if (clasa === 'KeyGenerator') {
const init = peVar('init', '(\\d+)');
if (u === 'AES') return { grup: 'CIPHER', nume: 'AES', param: init ? init[1] : undefined, functii: ['keygen'] };
if ((m = /^HMAC(\w+)$/i.exec(v))) return { grup: 'MAC', hash: hashCanonic(m[1]) || m[1], functii: ['keygen'] };
if (u === 'DES' || u === 'DESEDE' || u === 'CHACHA20' || u === 'BLOWFISH' || u === 'RC4' || u === 'ARCFOUR') return { grup: 'CIPHER', nume: u === 'DESEDE' ? '3DES' : v, functii: ['keygen'] };
return { grup: 'UNKNOWN', motiv: `KeyGenerator "${v}" is not in this tool's catalog.` };
}
if (clasa === 'KeyPairGenerator' || clasa === 'KeyFactory' || clasa === 'AlgorithmParameterGenerator') {
const init = peVar('initialize', '(\\d+)');
const bits = clasa === 'KeyPairGenerator' && init ? init[1] : undefined;
const f = clasa === 'KeyPairGenerator' ? ['keygen'] : undefined;
if (u === 'RSA') return { grup: 'RSA', param: bits, functii: f };
if (u === 'RSASSA-PSS') return { grup: 'RSA', param: bits, primitiv: 'signature', padding: 'other', functii: f };
if (u === 'DSA') return { grup: 'DSA', param: bits, functii: f };
if (u === 'DH' || u === 'DIFFIEHELLMAN') return { grup: 'DH', param: bits, functii: f };
if (u === 'EC' || u === 'ECDSA' || u === 'ECDH') {
const spec = peVar('initialize', '(?:new\\s+)?ECGenParameterSpec\\s*\\(\\s*"([^"]+)"'); // Kotlin: fara new
return { grup: u === 'ECDH' ? 'ECDH' : 'EC', curba: spec ? spec[1] : undefined, functii: f };
}
if (u === 'ED25519' || u === 'ED448' || u === 'EDDSA') return { grup: 'EDDSA', nume: u === 'ED448' ? 'Ed448' : 'Ed25519', functii: f };
if (u === 'X25519' || u === 'X448' || u === 'XDH') return { grup: 'XDH', nume: u === 'X448' ? 'X448' : 'X25519', functii: f };
}
if (clasa === 'Signature') {
if ((m = /^(\w+?)with(RSA|ECDSA|DSA|PLAIN-ECDSA|CVC-ECDSA|RSAandMGF1)(?:\/(PSS|ISO9796-2|X9\.31))?(?:inP1363Format)?$/i.exec(v))) {
const hash = hashCanonic(m[1]) || (m[1].toUpperCase() === 'NONE' ? undefined : m[1]);
const fam = m[2].toUpperCase();
if (fam.startsWith('RSA')) return { grup: 'RSA', primitiv: 'signature', hash, padding: m[3] || /MGF1/i.test(fam) ? 'other' : 'pkcs1v15', functii: ['sign', 'verify'] };
if (fam === 'DSA') return { grup: 'DSA', hash, functii: ['sign', 'verify'] };
return { grup: 'ECDSA', hash, functii: ['sign', 'verify'] };
}
if (u === 'RSASSA-PSS') return { grup: 'RSA', primitiv: 'signature', padding: 'other', functii: ['sign', 'verify'] };
if (u === 'ED25519' || u === 'ED448' || u === 'EDDSA') return { grup: 'EDDSA', nume: u === 'ED448' ? 'Ed448' : 'Ed25519', functii: ['sign', 'verify'] };
}
if (clasa === 'KeyAgreement') {
if (u === 'ECDH' || u === 'ECMQV' || u === 'ECCDH') return { grup: 'ECDH', functii: ['keygen'] };
if (u === 'DH' || u === 'DIFFIEHELLMAN') return { grup: 'DH', functii: ['keygen'] };
if (u === 'X25519' || u === 'X448' || u === 'XDH') return { grup: 'XDH', nume: u === 'X448' ? 'X448' : 'X25519', functii: ['keygen'] };
}
if (clasa === 'KEM') {
if (u === 'DHKEM') return { grup: 'ECDH', nume: 'DHKEM', primitiv: 'kem', functii: ['encapsulate', 'decapsulate'] };
}
if (clasa === 'Cipher') return cifruJca(v);
return { grup: 'UNKNOWN', motiv: `${clasa} "${v}" is not in this tool's catalog.` };
}
function cifruJca(v) {
const [alg0, mod0, pad0] = v.split('/').map((x) => (x || '').trim());
const alg = alg0.toUpperCase();
const mod = mod0 ? mod0.toLowerCase() : undefined;
const pad = pad0 ? pad0.toUpperCase() : '';
let m;
if (alg === 'RSA') {
const padding = /OAEP/.test(pad) ? 'oaep' : /PKCS1/.test(pad) ? 'pkcs1v15' : /NOPADDING/.test(pad) ? 'raw' : (pad ? 'other' : 'pkcs1v15');
const nota = pad ? undefined : 'Cipher "RSA" without padding means RSA/ECB/PKCS1Padding in the standard JCA providers.';
return { grup: 'RSA', primitiv: 'pke', padding, nota, functii: ['encrypt', 'decrypt'] };
}
if ((m = /^AES(?:_(128|192|256))?$/.exec(alg))) {
if (!mod) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'ecb', nota: 'Cipher "AES" without mode and padding means AES/ECB/PKCS5Padding in the standard JCA providers.', functii: ['encrypt', 'decrypt'] };
return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: mod === 'nopadding' ? undefined : mod, functii: ['encrypt', 'decrypt'] };
}
if (alg === 'DES') return { grup: 'CIPHER', nume: 'DES', mod, functii: ['encrypt', 'decrypt'] };
if (alg === 'DESEDE' || alg === 'TRIPLEDES') return { grup: 'CIPHER', nume: '3DES', mod, functii: ['encrypt', 'decrypt'] };
if (alg === 'RC4' || alg === 'ARCFOUR') return { grup: 'CIPHER', nume: 'RC4', functii: ['encrypt', 'decrypt'] };
if (alg === 'RC2') return { grup: 'CIPHER', nume: 'RC2', functii: ['encrypt', 'decrypt'] };
if (alg === 'BLOWFISH') return { grup: 'CIPHER', nume: 'Blowfish', mod, functii: ['encrypt', 'decrypt'] };
if (alg === 'CHACHA20-POLY1305') return { grup: 'CIPHER', nume: 'ChaCha20-Poly1305', functii: ['encrypt', 'decrypt'] };
if (alg === 'CHACHA20') return { grup: 'CIPHER', nume: 'ChaCha20', functii: ['encrypt', 'decrypt'] };
if (/^PBEWITH(MD5|SHA1)AND(DES|TRIPLEDES|RC2|RC4)/.test(alg)) return { grup: 'CIPHER', nume: /TRIPLEDES/.test(alg) ? '3DES' : /RC2/.test(alg) ? 'RC2' : /RC4/.test(alg) ? 'RC4' : 'DES', nota: `${v} derives the key with a weak hash.` };
if (/^ECIES/.test(alg)) return { grup: 'ECDH', nume: 'ECIES', primitiv: 'pke', functii: ['encrypt', 'decrypt'] };
return { grup: 'UNKNOWN', motiv: `Cipher transformation "${v}" is not in this tool's catalog.` };
}
// nume post-cuantice in JCA sau Bouncy Castle
export function pqNume(v) {
const t = String(v).trim();
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)([sfSF]))?$/i.exec(t))) return { grup: 'SLHDSA', param: m[1] ? `${m[1].toUpperCase()}-${m[2]}${m[3].toLowerCase()}` : undefined };
if ((m = /^(?:Falcon|FN-DSA)(?:-(512|1024))?$/i.exec(t))) return { grup: 'FALCON', param: m[1] };
if (/^(Kyber\d*|Dilithium\d*|SPHINCSPlus|SPHINCS\+)$/i.test(t)) return { grup: 'PREPQ', nume: t, primitiv: /kyber/i.test(t) ? 'kem' : 'signature' };
if (/^(XMSS|XMSSMT|XMSS\^MT|LMS|HSS)$/i.test(t)) return { grup: 'HASHSIG', nume: t.toUpperCase() };
if (/^(Rainbow|SIKE)$/i.test(t)) return { grup: 'BROKENPQ', nume: t, primitiv: /rainbow/i.test(t) ? 'signature' : 'kem' };
if (/^HQC(-\d+)?$/i.test(t)) return { grup: 'HQC', nume: t.toUpperCase() };
return null;
}
const BC_CLASA = [
[/^(ECDSASigner|ECNRSigner|DSTU4145Signer)$/, { grup: 'ECDSA' }],
[/^(DSASigner)$/, { grup: 'DSA' }],
[/^(RSADigestSigner|PSSSigner|RSAEngine|RSABlindedEngine|OAEPEncoding|PKCS1Encoding|RSAKeyPairGenerator|ISO9796d2Signer)$/, { grup: 'RSA' }],
[/^(ECKeyPairGenerator|ECNamedCurveTable|SECNamedCurves|ECDomainParameters)$/, { grup: 'EC' }],
[/^(ECDHBasicAgreement|ECDHCBasicAgreement|ECDHUnifiedAgreement)$/, { grup: 'ECDH' }],
[/^(DHBasicAgreement|DHAgreement|DHKeyPairGenerator)$/, { grup: 'DH' }],
[/^(Ed25519Signer|Ed25519ctxSigner|Ed25519phSigner|Ed25519KeyPairGenerator)$/, { grup: 'EDDSA', nume: 'Ed25519' }],
[/^(Ed448Signer|Ed448KeyPairGenerator)$/, { grup: 'EDDSA', nume: 'Ed448' }],
[/^(X25519Agreement|X25519KeyPairGenerator)$/, { grup: 'XDH', nume: 'X25519' }],
[/^(X448Agreement|X448KeyPairGenerator)$/, { grup: 'XDH', nume: 'X448' }],
[/^SecP256K1Curve$/, { grup: 'SECP256K1' }],
[/^MD5Digest$/, { grup: 'HASH', hash: 'MD5' }],
[/^SHA1Digest$/, { grup: 'HASH', hash: 'SHA1' }],
[/^SHA256Digest$/, { grup: 'HASH', hash: 'SHA256' }],
[/^SHA512Digest$/, { grup: 'HASH', hash: 'SHA512' }],
[/^(DESEngine)$/, { grup: 'CIPHER', nume: 'DES' }],
[/^(DESedeEngine)$/, { grup: 'CIPHER', nume: '3DES' }],
[/^(RC4Engine)$/, { grup: 'CIPHER', nume: 'RC4' }],
[/^(BlowfishEngine)$/, { grup: 'CIPHER', nume: 'Blowfish' }],
[/^(AESEngine|AESFastEngine|AESLightEngine)$/, { grup: 'CIPHER', nume: 'AES' }],
];
function bcActiv(cale) {
const clasa = cale.split('.').pop();
let m;
if ((m = /^(MLKEM|MLDSA|SLHDSA|Falcon|Kyber|Dilithium|SPHINCSPlus|XMSS|XMSSMT|LMS|HSS|Rainbow|SIKE|HQC|NTRU|NTRUPrime|BIKE|Frodo|CMCE|Picnic|Saber)/.exec(clasa))) {
const fam = m[1];
const map = { MLKEM: 'ML-KEM', MLDSA: 'ML-DSA', SLHDSA: 'SLH-DSA', SPHINCSPlus: 'SPHINCSPlus', XMSSMT: 'XMSSMT' };
const pq = pqNume(map[fam] || fam);
if (pq) return pq;
return { grup: 'UNKNOWN', motiv: `Bouncy Castle post-quantum class ${clasa}: scheme "${fam}" is not classified by this tool (not a finalized NIST standard).` };
}
if (/\.pqc\./.test(cale)) {
if (/BouncyCastlePQCProvider$/.test(clasa)) return { grup: 'UNKNOWN', motiv: 'Bouncy Castle PQC provider registered; the scheme is chosen elsewhere (not visible in this import).' };
return { grup: 'UNKNOWN', motiv: `Bouncy Castle post-quantum package class ${clasa}; scheme not recognized by this tool.` };
}
for (const [re, act] of BC_CLASA) if (re.test(clasa)) return act;
return null;
}
// Variabila din `v = <aici>` pe acelasi rand, citita INAPOI de la pozitie (ce dadea /([A-Za-z_$][\w$]*)\s*=\s*$/ pe textul randului
// de dinainte). Forma veche taia la fiecare apel tot randul pana la pozitie, deci un rand lung cu multe apeluri costa patratic
// (2026-09-29, revizuirea adversariala); cititul inapoi se opreste la primul caracter care nu poate face parte din tipar.
export function variabilaAtribuita(c, pos) {
const sp = (ch) => ch !== '\n' && /\s/.test(ch);
let j = pos - 1;
while (j >= 0 && sp(c[j])) j--;
if (j < 0 || c[j] !== '=') return null;
j--;
while (j >= 0 && sp(c[j])) j--;
const sf = j + 1;
while (j >= 0 && /[\w$]/.test(c[j])) j--;
let st = j + 1;
while (st < sf && /[0-9]/.test(c[st])) st++;
return st < sf ? c.slice(st, sf) : null;
}
// KeyProperties.KEY_ALGORITHM_* (Android Keystore) -> numele JCA pe care il primeste getInstance
const KEY_ALGORITHM = { EC: 'EC', RSA: 'RSA', AES: 'AES', '3DES': 'DESede', HMAC_SHA1: 'HmacSHA1', HMAC_SHA224: 'HmacSHA224', HMAC_SHA256: 'HmacSHA256', HMAC_SHA384: 'HmacSHA384', HMAC_SHA512: 'HmacSHA512', XDH: 'XDH' };
const NUME_PKIX = 'X.509 chain validation (PKIX)';
// Lantul unui KeyGenParameterSpec.Builder(...)....build(): marimea cheii, curba, hash-urile, modurile, umpluturile
function citesteSpecAndroid(lant) {
const t = lant.slice(0, 3000);
const r = {};
let m;
if ((m = /setKeySize\s*\(\s*(\d+)\s*\)/.exec(t))) r.bits = m[1];
if ((m = /(?:new\s+)?ECGenParameterSpec\s*\(\s*"([^"]+)"/.exec(t))) { r.curba = m[1]; r.pozCurba = m.index; } // pozitia, ca sa nu se numere si separat
if ((m = /BLOCK_MODE_(GCM|CBC|ECB|CTR)\b/.exec(t))) r.mod = m[1].toLowerCase();
if ((m = /DIGEST_(SHA1|SHA224|SHA256|SHA384|SHA512|MD5)\b/.exec(t))) r.hash = hashCanonic(m[1]) || m[1];
if ((m = /(ENCRYPTION_PADDING_RSA_OAEP|ENCRYPTION_PADDING_RSA_PKCS1|SIGNATURE_PADDING_RSA_PSS|SIGNATURE_PADDING_RSA_PKCS1)\b/.exec(t))) r.padding = /OAEP/.test(m[1]) ? 'oaep' : /PSS/.test(m[1]) ? 'other' : 'pkcs1v15';
return r;
}
// Specificatia Android Keystore data generatorului `variabila` prin .initialize(spec) / .init(spec): inline in apel sau printr-o variabila
// definita in fisier cu KeyGenParameterSpec.Builder; intoarce { spec, pozitiiBuilder } (pozitiile builderelor consumate)
function specAndroidPentru(ctx, dupaStart, dupa, variabila) {
if (!variabila) return null;
const s = new RegExp(`(?<![\\w.])${escapeRe(variabila)}\\s*\\.\\s*(?:initialize|init)\\s*\\(`).exec(dupa);
if (!s) return null;
const a = argumente(ctx, dupaStart + s.index + s[0].length);
const RE_B = /(?<![\w.])KeyGenParameterSpec\s*\.\s*Builder\s*\(/g;
const cu = (baza, text) => { const spec = citesteSpecAndroid(text); const pozitii = [baza]; if (spec.pozCurba !== undefined) pozitii.push(baza + spec.pozCurba); return { spec, pozitii }; };
let b = RE_B.exec(a.text);
if (b) return cu(a.start + b.index, a.text.slice(b.index));
const id = /^\s*([A-Za-z_]\w*)\s*$/.exec(a.text);
if (!id) return null;
const def = new RegExp(`(?<![\\w.])${escapeRe(id[1])}\\s*(?::\\s*KeyGenParameterSpec\\s*)?=\\s*KeyGenParameterSpec\\s*\\.\\s*Builder\\s*\\(`).exec(ctx.code);
if (!def) return null;
const inceput = def.index + def[0].lastIndexOf('KeyGenParameterSpec');
return cu(inceput, ctx.code.slice(inceput));
}
// Sabloanele de chei Tink (KeyTemplates.get("..."), AeadKeyTemplates.X, PredefinedAeadParameters.X): numele spune algoritmul
export function sablonTink(nume) {
const t = String(nume).trim().replace(/_RAW$/, '');
let m;
const pq = pqNume(t.replace(/_/g, '-'));
if (pq) return pq;
if ((m = /^AES(128|256)_GCM(_SIV)?$/.exec(t))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: m[2] ? 'gcm-siv' : 'gcm', functii: ['encrypt', 'decrypt'] };
if ((m = /^AES(128|256)_EAX$/.exec(t))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'eax', functii: ['encrypt', 'decrypt'] };
if ((m = /^AES(128|256)_CTR_HMAC_SHA256$/.exec(t))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'ctr', functii: ['encrypt', 'decrypt'], nota: 'Tink AES-CTR with HMAC-SHA-256 (encrypt-then-MAC).' };
if ((m = /^AES(128|256)_GCM_HKDF_(\d+KB|\d+MB)$/.exec(t))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'gcm', functii: ['encrypt', 'decrypt'], nota: 'Tink streaming AEAD (segments keyed with HKDF).' };
if (t === 'AES256_SIV') return { grup: 'CIPHER', nume: 'AES', param: '256', mod: 'siv', functii: ['encrypt', 'decrypt'] };
if (t === 'XCHACHA20_POLY1305') return { grup: 'CIPHER', nume: 'XChaCha20-Poly1305', functii: ['encrypt', 'decrypt'] };
if (t === 'CHACHA20_POLY1305') return { grup: 'CIPHER', nume: 'ChaCha20-Poly1305', functii: ['encrypt', 'decrypt'] };
if (t === 'AES_CMAC') return { grup: 'MAC', nume: 'AES-CMAC', functii: ['tag'] };
if ((m = /^HMAC_SHA(256|512)_(\d+)BITTAG$/.exec(t))) return { grup: 'MAC', hash: `SHA${m[1]}`, functii: ['tag'] };
if ((m = /^HMAC_SHA(256|512)_PRF$/.exec(t)) || (m = /^HKDF_SHA(256)$/.exec(t))) return { grup: 'KDF', nume: /^HKDF/.test(t) ? 'HKDF' : 'HMAC-PRF', hash: `SHA${m[1]}`, functii: ['keyderive'] };
if ((m = /^ECDSA_P(256|384|521)(?:_IEEE_P1363|_RAW)?$/.exec(t))) return { grup: 'ECDSA', curba: `secp${m[1]}r1`, functii: ['sign', 'verify'] };
if (t === 'ED25519') return { grup: 'EDDSA', nume: 'Ed25519', functii: ['sign', 'verify'] };
if ((m = /^RSA_SSA_(PKCS1|PSS)_(\d{4})_SHA(\d{3})(?:_SHA\d{3}_\d+)?_F4$/.exec(t))) return { grup: 'RSA', param: m[2], primitiv: 'signature', padding: m[1] === 'PSS' ? 'other' : 'pkcs1v15', hash: `SHA${m[3]}`, functii: ['sign', 'verify'] };
if ((m = /^ECIES_P(256|384|521)(?:_COMPRESSED)?_HKDF_HMAC_SHA\d+_AES\d+_(?:GCM|CTR_HMAC_SHA\d+)$/.exec(t))) return { grup: 'ECDH', curba: `secp${m[1]}r1`, primitiv: 'pke', functii: ['encrypt', 'decrypt'], nota: `Tink ECIES (${t}).` };
if ((m = /^DHKEM_(X25519|P256|P384|P521)_HKDF_SHA\d+_HKDF_SHA\d+_(AES_128_GCM|AES_256_GCM|CHACHA20_POLY1305)$/.exec(t))) {
const nota = `Tink HPKE (RFC 9180) with DHKEM ${m[1]}: the key encapsulation is classical.`;
return m[1] === 'X25519' ? { grup: 'XDH', nume: 'X25519', functii: ['encapsulate', 'decapsulate'], nota } : { grup: 'ECDH', curba: `secp${m[1].slice(1)}r1`, primitiv: 'pke', functii: ['encapsulate', 'decapsulate'], nota };
}
return { grup: 'UNKNOWN', nume: `Tink template ${nume}`, motiv: `Tink key template "${nume}" is not in this tool's catalog.` };
}
// Suita de cifruri TLS (OkHttp CipherSuite.TLS_...): schimbul de cheie din nume, sau cifrul pentru suitele TLS 1.3
function suitaTls(nume) {
let m;
if ((m = /^TLS_(ECDHE|ECDH)_/.exec(nume))) return { grup: 'ECDH', nume, functii: ['keygen'], nota: 'TLS cipher suite with (EC)DHE key exchange; the curve is negotiated separately.' };
if ((m = /^TLS_(DHE|DH)_/.exec(nume))) return { grup: 'DH', nume, functii: ['keygen'] };
if (/^TLS_RSA_/.test(nume)) return { grup: 'RSA', nume, primitiv: 'pke', padding: 'pkcs1v15', functii: ['encrypt'], nota: 'TLS cipher suite with RSA key transport (no forward secrecy).' };
if ((m = /^TLS_AES_(128|256)_GCM_SHA\d+$/.exec(nume))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'gcm', functii: ['encrypt'], nota: 'TLS 1.3 cipher suite: the key exchange group is negotiated separately.' };
if (/^TLS_CHACHA20_POLY1305_SHA256$/.test(nume)) return { grup: 'CIPHER', nume: 'ChaCha20-Poly1305', functii: ['encrypt'], nota: 'TLS 1.3 cipher suite: the key exchange group is negotiated separately.' };
if ((m = /^TLS_AES_(128|256)_CCM/.exec(nume))) return { grup: 'CIPHER', nume: 'AES', param: m[1], mod: 'ccm', functii: ['encrypt'] };
return { grup: 'UNKNOWN', nume, motiv: `TLS cipher suite "${nume}" is not in this tool's catalog.` };
}
export function detecteazaJava(ctx) { detecteazaJvm(ctx, false); }
export function detecteazaJvm(ctx, kotlin) {
const c = ctx.code;
const consumate = new Set();
for (const m of ctx.potriviri(/(?<![\w.])(KeyPairGenerator|Signature|Cipher|KeyAgreement|MessageDigest|Mac|KeyGenerator|KeyFactory|SecretKeyFactory|KEM|SSLContext|AlgorithmParameterGenerator)\s*\.\s*getInstance\s*\(/g)) {
const a = argumente(ctx, m.index + m[0].length);
const p = imparteArgumente(ctx, a.start, a.end);
const api = `${m[1]}.getInstance`;
let v = valoareArgument(ctx, p[0]);
// Android Keystore: KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
const kp = v.fel === 'necunoscut' && /^KeyProperties\s*\.\s*KEY_ALGORITHM_(\w+)$/.exec(v.expresie);
if (kp && KEY_ALGORITHM[kp[1]]) v = { fel: 'rezolvat', valoare: KEY_ALGORITHM[kp[1]], din: `KeyProperties.KEY_ALGORITHM_${kp[1]}` };
if (v.fel === 'necunoscut') { ctx.adauga(m.index, api, 'call', necunoscut(v.expresie, api), { bucata: m[0] + v.expresie }); continue; }
if (v.fel !== 'literal' && v.fel !== 'rezolvat') continue;
const ex = v.fel === 'rezolvat' ? { rezolvatDin: v.din } : {};
if (m[1] === 'SSLContext') {
const ver = TLSV[v.valoare];
ctx.adauga(m.index, api, 'config', ver ? { grup: 'TLS', param: ver, rol: 'only' } : { grup: 'UNKNOWN', motiv: `SSLContext protocol "${v.valoare}" is not in this tool's catalog.` }, { ...ex, bucata: `${m[0]}"${v.valoare}"` });
continue;
}
const dupa = c.slice(a.end, a.end + 2000);
const variabila = variabilaAtribuita(c, m.index);
if (variabila && m[1] === 'KeyPairGenerator' && /^(EC|ECDSA|ECDH)$/i.test(v.valoare)) {
const s = new RegExp(`(?<![\\w.])${variabila}\\s*\\.\\s*initialize\\s*\\(\\s*((?:new\\s+)?ECGenParameterSpec\\s*\\(\\s*")`).exec(dupa);
if (s) consumate.add(a.end + s.index + s[0].indexOf(s[1]));
}
const act = activJca(m[1], v.valoare, dupa, variabila);
// Android Keystore: marimea, curba, modul si umplutura vin din KeyGenParameterSpec.Builder, nu din initialize(bits)
if (/^(KeyPairGenerator|KeyGenerator)$/.test(m[1]) && act.grup !== 'UNKNOWN') {
const sp = specAndroidPentru(ctx, a.end, dupa, variabila);
if (sp) {
for (const pz of sp.pozitii) consumate.add(pz);
if (sp.spec.bits && !act.param) act.param = sp.spec.bits;
if (sp.spec.curba && !act.curba && /^(EC|ECDH)$/.test(act.grup)) act.curba = sp.spec.curba;
if (sp.spec.mod && act.grup === 'CIPHER') act.mod = sp.spec.mod;
if (sp.spec.padding && act.grup === 'RSA') { act.padding = sp.spec.padding; act.primitiv = sp.spec.padding === 'oaep' ? 'pke' : 'signature'; }
if (sp.spec.hash && act.grup === 'RSA' && !act.hash) act.hash = sp.spec.hash;
}
}
ctx.adauga(m.index, api, 'call', act, { ...ex, bucata: `${m[0]}"${v.valoare}"` });
}
// Android Keystore: un KeyGenParameterSpec.Builder neconsumat de un getInstance din acelasi fisier (generatorul e in alta parte);
// inaintea curbelor numite, fiindca ECGenParameterSpec-ul din lantul lui e al lui, nu o a doua curba
for (const m of ctx.potriviri(/(?<![\w.])KeyGenParameterSpec\s*\.\s*Builder\s*\(/g)) {
if (consumate.has(m.index)) continue;
const sp = citesteSpecAndroid(c.slice(m.index));
if (sp.pozCurba !== undefined) consumate.add(m.index + sp.pozCurba);
const act = sp.curba ? { grup: 'EC', curba: sp.curba, functii: ['keygen'] }
: sp.mod ? { grup: 'CIPHER', nume: sp.bits === '168' ? '3DES' : 'AES', param: sp.bits, mod: sp.mod, functii: ['keygen'] }
: sp.padding || (sp.bits && Number(sp.bits) >= 1024) ? { grup: 'RSA', param: sp.bits, padding: sp.padding, primitiv: sp.padding === 'oaep' ? 'pke' : (sp.padding ? 'signature' : undefined), hash: sp.hash, functii: ['keygen'] }
: sp.bits && /^(224|256|384|521)$/.test(sp.bits) ? { grup: 'EC', curba: `secp${sp.bits}r1`, functii: ['keygen'] }
: { grup: 'UNKNOWN', nume: 'KeyGenParameterSpec', motiv: 'Android Keystore key specification: the algorithm is given to the generator (KeyProperties.KEY_ALGORITHM_*), which is not in this file; the spec alone does not say it.' };
ctx.adauga(m.index, 'KeyGenParameterSpec.Builder', 'config', act, { bucata: m[0] });
}
// curbe numite in afara unui KeyPairGenerator (ECGenParameterSpec, ECNamedCurveTable); Kotlin: fara new
for (const m of ctx.potriviri(/(?<![\w.])(?:(?:new\s+)?ECGenParameterSpec|ECNamedCurveTable\s*\.\s*getParameterSpec|SECNamedCurves\s*\.\s*getByName|CustomNamedCurves\s*\.\s*getByName)\s*\(\s*"([^"]+)"/g)) {
if (consumate.has(m.index)) continue;
ctx.adauga(m.index, m[0].replace(/\s*\(.*$/, ''), 'call', { grup: 'EC', curba: m[1] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])NamedParameterSpec\s*\.\s*(X25519|X448|ED25519|ED448|ML_DSA_44|ML_DSA_65|ML_DSA_87|ML_KEM_512|ML_KEM_768|ML_KEM_1024)\b/g)) {
const t = m[1].replace(/_/g, '-');
const act = pqNume(t) || (/^X/.test(t) ? { grup: 'XDH', nume: t } : { grup: 'EDDSA', nume: t === 'ED448' ? 'Ed448' : 'Ed25519' });
ctx.adauga(m.index, `NamedParameterSpec.${m[1]}`, 'call', act, { bucata: m[0] });
}
// protocoale TLS activate explicit: Java new String[]{...}, Kotlin arrayOf(...) / listOf(...)
for (const m of ctx.potriviri(/(?<![\w.])set(?:Enabled)?Protocols\s*\(\s*(?:new\s+String\s*\[\s*\]\s*\{|arrayOf\s*\(|listOf\s*\()/g)) {
const a = argumente(ctx, m.index + m[0].length);
const vers = [...a.text.matchAll(/"([^"]+)"/g)];
const lista = vers.map((x) => TLSV[x[1]]).filter(Boolean);
for (const x of vers) {
const ver = TLSV[x[1]];
if (!ver) continue;
const rol = ver === '1.2' && lista.includes('1.3') ? 'min' : 'only';
ctx.adauga(a.start + x.index, 'setEnabledProtocols', 'config', { grup: 'TLS', param: ver, rol }, { bucata: `"${x[1]}"` });
}
}
// OkHttp (ancorat pe importul okhttp3): ConnectionSpec, versiunile si suitele cerute, pinning
if (/^[ \t]*import\s+okhttp3\./m.test(c)) {
for (const m of ctx.potriviri(/(?<![\w.])ConnectionSpec\s*\.\s*Builder\s*\(\s*ConnectionSpec\s*\.\s*(MODERN_TLS|COMPATIBLE_TLS|RESTRICTED_TLS|CLEARTEXT)\s*\)/g)) {
if (m[1] === 'CLEARTEXT') { ctx.adauga(m.index, 'ConnectionSpec.CLEARTEXT', 'config', { grup: 'UNKNOWN', nume: 'OkHttp CLEARTEXT', motiv: 'OkHttp ConnectionSpec.CLEARTEXT: plain HTTP, no TLS at all on this connection spec.' }, { bucata: m[0] }); continue; }
ctx.adauga(m.index, `ConnectionSpec.${m[1]}`, 'config', { grup: 'TLS', param: 'negotiated', nume: `TLS (negotiated, OkHttp ${m[1]})` }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/\.\s*tlsVersions\s*\(/g)) {
const a = argumente(ctx, m.index + m[0].length);
const vers = [...a.text.matchAll(/TlsVersion\s*\.\s*(TLS_1_0|TLS_1_1|TLS_1_2|TLS_1_3|SSL_3_0)\b/g)];
const map = { TLS_1_0: '1.0', TLS_1_1: '1.1', TLS_1_2: '1.2', TLS_1_3: '1.3', SSL_3_0: 'ssl3' };
const lista = vers.map((x) => map[x[1]]);
for (const x of vers) ctx.adauga(a.start + x.index, 'ConnectionSpec.tlsVersions', 'config', { grup: 'TLS', param: map[x[1]], rol: map[x[1]] === '1.2' && lista.includes('1.3') ? 'min' : 'only' }, { bucata: x[0] });
}
for (const m of ctx.potriviri(/\.\s*cipherSuites\s*\(/g)) {
const a = argumente(ctx, m.index + m[0].length);
for (const x of a.text.matchAll(/CipherSuite\s*\.\s*(TLS_[A-Z0-9_]+)\b/g)) ctx.adauga(a.start + x.index, 'ConnectionSpec.cipherSuites', 'config', suitaTls(x[1]), { bucata: x[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])CertificatePinner\s*\.\s*Builder\s*\(/g)) ctx.adauga(m.index, 'CertificatePinner.Builder', 'config', { grup: 'PKI-VERIFY', nume: 'certificate pinning (SPKI)', nota: 'OkHttp CertificatePinner: the pinned SPKI hashes name classical (RSA or EC) keys.' }, { bucata: m[0] });
}
// verificarea lanturilor de certificate (lantul de incredere TLS al clientului): autentificare, nu schimb de cheie
for (const m of ctx.potriviri(/(?<![\w.])(CertPathValidator|TrustManagerFactory)\s*\.\s*getInstance\s*\(/g)) ctx.adauga(m.index, `${m[1]}.getInstance`, 'call', { grup: 'PKI-VERIFY', nume: NUME_PKIX, functii: ['verify'] }, { bucata: m[0] });
for (const m of ctx.potriviri(/(?<![\w.])KeyStore\s*\.\s*getInstance\s*\(\s*"AndroidCAStore"\s*\)/g)) ctx.adauga(m.index, 'KeyStore.getInstance("AndroidCAStore")', 'call', { grup: 'PKI-VERIFY', nume: NUME_PKIX, nota: 'The Android system CA store (the client\'s TLS trust anchors).' }, { bucata: m[0] });
for (const m of ctx.potriviri(/(?<![\w.])X509TrustManager\b(?!\s*\.)/g)) ctx.adauga(m.index, 'X509TrustManager', 'config', { grup: 'PKI-VERIFY', nume: NUME_PKIX, functii: ['verify'] }, { bucata: c.slice(Math.max(0, m.index - 12), m.index + 16) });
for (const m of ctx.potriviri(/\.\s*checkServerTrusted\s*\(/g)) ctx.adauga(m.index, 'checkServerTrusted', 'call', { grup: 'PKI-VERIFY', nume: NUME_PKIX, functii: ['verify'] }, { bucata: m[0].trim() });
// Tink (ancorat pe importul com.google.crypto.tink): sablonul de cheie spune algoritmul; inregistrarea primitivelor e dovada slaba
const tink = ctx.potriviri(/^[ \t]*import\s+com\.google\.crypto\.tink\.[\w.*]+/gm);
if (tink.length) {
const TINK_SUPRIMA = ['CIPHER', 'MAC', 'KDF', 'ECDSA', 'EDDSA', 'RSA', 'XDH', 'ECDH', 'MLDSA', 'MLKEM', 'SLHDSA', 'UNKNOWN'];
for (const m of ctx.potriviri(/(?<![\w.])KeyTemplates\s*\.\s*get\s*\(\s*"([^"]+)"\s*\)/g)) ctx.adauga(m.index, 'KeyTemplates.get', 'call', sablonTink(m[1]), { bucata: m[0] });
for (const m of ctx.potriviri(/(?<![\w.])(?:(?:Aead|Mac|Signature|Hybrid|Daead|StreamingAead|Prf)KeyTemplates|Predefined(?:Aead|Mac|Signature|Hybrid|Daead|StreamingAead|Prf)Parameters)\s*\.\s*([A-Z][A-Z0-9_]+)\b/g)) ctx.adauga(m.index, m[0].replace(/\s+/g, ''), 'call', sablonTink(m[1]), { bucata: m[0] });
for (const m of ctx.potriviri(/(?<![\w.])(Aead|Mac|Signature|Hybrid|Daead|StreamingAead|Prf|Tink)Config\s*\.\s*register\s*\(/g)) ctx.adauga(m.index, `${m[1]}Config.register`, 'import', { grup: 'LIB-MULTI', nume: `Tink ${m[1]} primitives`, motiv: `Tink ${m[1]}Config.register(): registers the primitive family; the algorithm is chosen by the key template, not visible in this file.` }, { suprimaDe: TINK_SUPRIMA, bucata: m[0] });
ctx.adauga(tink[0].index + tink[0][0].indexOf('import'), 'import com.google.crypto.tink', 'import', { grup: 'LIB-MULTI', nume: 'Tink (com.google.crypto.tink)', motiv: 'Imports Tink (AEAD, MAC, signatures, hybrid encryption, HPKE); no key template recognized in this file.' }, { suprimaDe: [...TINK_SUPRIMA, 'LIB-MULTI'], bucata: tink[0][0].trim() });
}
// proprietati de sistem ale JSSE
for (const m of ctx.potriviri(/(?<![\w.])System\s*\.\s*setProperty\s*\(\s*"(jdk\.tls\.(?:client|server)\.protocols|jdk\.tls\.namedGroups|https\.protocols)"\s*,\s*"([^"]*)"/g)) {
const off = m[0].lastIndexOf('"' + m[2] + '"') + 1;
let k = 0;
for (const bucata of m[2].split(',')) {
const t = bucata.trim();
const pos = m.index + off + k + bucata.indexOf(t);
k += bucata.length + 1;
if (!t) continue;
if (/namedGroups/.test(m[1])) ctx.adauga(pos, m[1], 'config', { ...grupTls(t), functii: ['keygen'] }, { bucata: `${m[1]}=${t}` });
else if (TLSV[t]) ctx.adauga(pos, m[1], 'config', { grup: 'TLS', param: TLSV[t], rol: 'only' }, { bucata: `${m[1]}=${t}` });
}
}
// Bouncy Castle: importurile (Java cu `;`, Kotlin fara) si parametrii post-cuantici
for (const m of ctx.potriviri(/^[ \t]*import\s+(?:static\s+)?(org\.bouncycastle\.[\w.]+)\s*(?:;|$)/gm)) {
const act = bcActiv(m[1]);
if (act) ctx.adauga(m.index + m[0].indexOf('import'), `import ${m[1]}`, 'import', act, { suprimaDe: act.grup === 'EC' ? ['EC', 'ECDSA', 'ECDH', 'SECP256K1'] : 'AUTO', bucata: `import ${m[1]}` });
}
for (const m of ctx.potriviri(/(?<![\w.])(MLKEM|MLDSA|SLHDSA|Falcon|Kyber|Dilithium|SPHINCSPlus)Parameter(?:s|Spec)\s*\.\s*(\w+)/g)) {
const act = bcParam(m[1], m[2]);
if (act) ctx.adauga(m.index, `${m[1]}Parameters.${m[2]}`, 'call', act, { bucata: m[0] });
}
detecteazaLibsignalJvm(ctx, kotlin);
void curbaCanonica;
}
// libsignal, API-ul Java/Android (org.signal.libsignal; 2026-10-18, dupa rularea pe arborele libsignal: 343 de fisiere Java si zero
// constatari, fiindca biblioteca nu trece prin JCA, ci prin propriile clase peste puntea nativa). Ancora e importul sau pachetul
// org.signal.libsignal, ca numele scurte (ECKeyPair, HKDF, Aes256GcmSiv) sa nu fie luate drept libsignal in alt cod.
const LIBSIGNAL_NOTA_EC = 'libsignal Curve25519 key pair: used for Diffie-Hellman (X3DH/PQXDH, Double Ratchet) and for XEdDSA signatures over the same key.';
const LIBSIGNAL_NOTA_KYBER = 'Signal PQXDH prekeys: CRYSTALS-Kyber (the pre-standard form of ML-KEM).';
const LIBSIGNAL_BUNDLE = { grup: 'LIB-MULTI', nume: 'libsignal session setup (X3DH/PQXDH)', motiv: 'libsignal prekey bundle: Curve25519 identity and signed prekey; a Kyber prekey in the bundle makes the session PQXDH (hybrid). Which prekeys the bundle carries is decided at run time.' };
export function kemLibsignal(tip) {
let k;
if ((k = /^KYBER_?(768|1024)$/i.exec(tip || ''))) return { grup: 'PREPQ', nume: `Kyber-${k[1]}`, primitiv: 'kem', functii: ['keygen'], nota: LIBSIGNAL_NOTA_KYBER };
if ((k = /^ML_?KEM_?(768|1024)$/i.exec(tip || ''))) return { grup: 'MLKEM', param: k[1], functii: ['keygen'] };
return { grup: 'UNKNOWN', nume: 'libsignal KEM key pair', motiv: 'libsignal KEMKeyPair: the key type (Kyber-1024 or ML-KEM) is the KEMKeyType argument, not visible on this line.' };
}
export const LIBSIGNAL_NOTA_KYBER_PK = 'libsignal KEMPublicKey: a Kyber-1024 public key (a PQXDH prekey), built from bytes or from a native handle.';
export const LIBSIGNAL_NOTA_SEALED = 'libsignal sealed sender: the envelope key is derived with X25519 (an ephemeral key against the recipient\'s identity key) and HKDF-SHA-256, then AES-256-CTR with HMAC-SHA-256 (v1) or AES-256-GCM-SIV (v2); the session inside is X3DH/PQXDH.';
// 0.4.0 (2026-10-18): Kotlin are aceleasi clase fara `new` (Aes256GcmSiv(key), PreKeyBundle(...)); forma fara `new` nu se admite in Java,
// unde ar prinde declaratii. In Kotlin, `class X(` / `fun X(` / `constructor(` sunt declaratii, nu apeluri. Si puntea nativa a bibliotecii
// insesi (Native.ECPrivateKey_Generate(), Native.KyberKeyPair_Generate(), Native.HKDF_DeriveSecrets(...)): asa isi fac cheile sursele
// Kotlin si Java din org.signal.libsignal (ECKeyPair.kt, KEMKeyPair.kt, HKDF.kt), unde ancora e pachetul.
function detecteazaLibsignalJvm(ctx, kotlin) {
const ancore = ctx.potriviri(/(?<![\w.])(?:import\s+(?:static\s+)?org\.signal\.libsignal\.(?:protocol|crypto|metadata|zkgroup|svr|keytrans)\b|package\s+org\.signal\.libsignal\b)/g);
if (!ancore.length) return;
const ctor = kotlin ? '(?<![\\w.])(?<!class\\s)(?<!fun\\s)(?<!constructor\\s)(?<!object\\s)' : '(?<![\\w.])new\\s+';
const api = (nume) => (kotlin ? nume : `new ${nume}`);
for (const m of ctx.potriviri(/(?<![\w.])(?:(ECKeyPair|IdentityKeyPair|ECPrivateKey)\s*\.\s*generate|(Curve)\s*\.\s*generateKeyPair|Native\s*\.\s*(ECPrivateKey_Generate|IdentityKeyPair_Generate))\s*\(/g)) {
const nume = m[3] ? `Native.${m[3]}` : `${m[1] || m[2]}.${m[1] ? 'generate' : 'generateKeyPair'}`;
ctx.adauga(m.index, nume, 'call', { grup: 'XDH', nume: 'X25519', functii: ['keygen'], nota: LIBSIGNAL_NOTA_EC }, { bucata: m[0] });
}
for (const m of ctx.potriviri(/(?<![\w.])KEMKeyPair\s*\.\s*generate\s*\(\s*(?:KEMKeyType\s*\.\s*([A-Za-z_0-9]+))?/g)) {
ctx.adauga(m.index, 'KEMKeyPair.generate', 'call', kemLibsignal(m[1]), { bucata: m[0].trim() });
}
for (const m of ctx.potriviri(/(?<![\w.])Native\s*\.\s*KyberKeyPair_Generate\s*\(/g)) ctx.adauga(m.index, 'Native.KyberKeyPair_Generate', 'call', kemLibsignal('KYBER_1024'), { bucata: m[0] });
// puntea cheii Curve25519 (ECPrivateKey.kt / ECPrivateKey.java): acordul X25519, semnatura XEdDSA pe aceeasi cheie, HPKE (RFC 9180, DHKEM X25519)
for (const m of ctx.potriviri(/(?<![\w.])Native\s*\.\s*(ECPrivateKey_Agree|ECPrivateKey_Sign|ECPublicKey_Verify|ECPrivateKey_HpkeOpen|ECPublicKey_HpkeSeal)\s*\(/g)) {
const act = m[1] === 'ECPrivateKey_Agree' ? { grup: 'XDH', nume: 'X25519', functii: ['other'], nota: 'libsignal Diffie-Hellman agreement over Curve25519.' }
: /Hpke/.test(m[1]) ? { grup: 'XDH', nume: 'X25519', primitiv: 'pke', functii: [m[1] === 'ECPublicKey_HpkeSeal' ? 'encrypt' : 'decrypt'], nota: 'libsignal HPKE (RFC 9180) with DHKEM X25519: the key encapsulation is classical.' }
: { grup: 'EDDSA', nume: 'XEdDSA (Curve25519)', functii: [m[1] === 'ECPublicKey_Verify' ? 'verify' : 'sign'], nota: 'libsignal XEdDSA: an Ed25519-style signature made with the Curve25519 (X25519) identity key.' };
ctx.adauga(m.index, `Native.${m[1]}`, 'call', act, { bucata: m[0] });
}
for (const m of ctx.potriviri(new RegExp(`${ctor}KEMPublicKey\\s*\\(`, 'g'))) ctx.adauga(m.index, api('KEMPublicKey'), 'call', { grup: 'PREPQ', nume: 'Kyber-1024', primitiv: 'kem', functii: ['other'], nota: LIBSIGNAL_NOTA_KYBER_PK }, { bucata: m[0] });
for (const m of ctx.potriviri(/(?<![\w.])(?:HKDF\s*\.\s*deriveSecrets|Native\s*\.\s*HKDF_DeriveSecrets)\s*\(/g)) {
ctx.adauga(m.index, /Native/.test(m[0]) ? 'Native.HKDF_DeriveSecrets' : 'HKDF.deriveSecrets', 'call', { grup: 'KDF', nume: 'HKDF', hash: 'SHA256', functii: ['keyderive'], nota: 'libsignal HKDF is HKDF-SHA-256.' }, { bucata: m[0] });
}
for (const m of ctx.potriviri(new RegExp(`${ctor}(Aes256GcmSiv|Aes256GcmEncryption|Aes256GcmDecryption|Aes256Ctr32)\\s*\\(|(?<![\\w.])Native\\s*\\.\\s*(Aes256GcmSiv|Aes256GcmEncryption|Aes256GcmDecryption|Aes256Ctr32)_New\\s*\\(`, 'g'))) {
const tip = m[1] || m[2];
const mod = tip === 'Aes256GcmSiv' ? 'gcm-siv' : tip === 'Aes256Ctr32' ? 'ctr' : 'gcm';
ctx.adauga(m.index, m[2] ? `Native.${m[2]}_New` : api(tip), 'call', { grup: 'CIPHER', nume: 'AES', param: '256', mod, functii: [tip === 'Aes256GcmDecryption' ? 'decrypt' : 'encrypt'] }, { bucata: m[0] });
}
for (const m of ctx.potriviri(new RegExp(`${ctor}PreKeyBundle\\s*\\(|(?<![\\w.])Native\\s*\\.\\s*(?:PreKeyBundle_New|SessionBuilder_ProcessPreKeyBundle)\\s*\\(`, 'g'))) ctx.adauga(m.index, /Native/.test(m[0]) ? m[0].replace(/\s+|\($/g, '') : api('PreKeyBundle'), 'call', LIBSIGNAL_BUNDLE, { bucata: m[0] });
for (const m of ctx.potriviri(new RegExp(`${ctor}SealedSessionCipher\\s*\\(`, 'g'))) ctx.adauga(m.index, api('SealedSessionCipher'), 'call', { grup: 'XDH', nume: 'X25519', functii: ['keygen'], nota: LIBSIGNAL_NOTA_SEALED }, { bucata: m[0] });
// importul: dovada slaba, o singura data pe fisier, suprimata de orice apel recunoscut de mai sus
const imp = ancore.find((a) => /^import/.test(a[0]));
if (imp) ctx.adauga(imp.index, 'import org.signal.libsignal', 'import', { grup: 'LIB-MULTI', nume: 'libsignal (org.signal.libsignal)', motiv: 'Imports the Signal protocol library (Curve25519 identity and prekeys, Kyber prekeys (PQXDH), Double Ratchet, SPQR, sealed sender); no specific call recognized in this file.' }, { suprimaDe: ['XDH', 'PREPQ', 'MLKEM', 'KDF', 'CIPHER', 'LIB-MULTI'], bucata: imp[0].trim() });
}
function bcParam(fam, p) {
let m;
if (fam === 'MLKEM' && (m = /(512|768|1024)/.exec(p))) return { grup: 'MLKEM', param: m[1] };
if (fam === 'MLDSA' && (m = /(44|65|87)/.exec(p))) return { grup: 'MLDSA', param: m[1] };
if (fam === 'SLHDSA' && (m = /(sha2|shake)_(128|192|256)([sf])/i.exec(p))) return { grup: 'SLHDSA', param: `${m[1].toUpperCase()}-${m[2]}${m[3].toLowerCase()}` };
if (fam === 'Falcon' && (m = /(512|1024)/.exec(p))) return { grup: 'FALCON', param: m[1] };
if (fam === 'Kyber' || fam === 'Dilithium' || fam === 'SPHINCSPlus') return { grup: 'PREPQ', nume: `${fam} ${p}`, primitiv: fam === 'Kyber' ? 'kem' : 'signature' };
return null;
}