// 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(`(? (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 = ` 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(`(? { 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(`(?= 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(/(? 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(/(? 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(/(? (kotlin ? nume : `new ${nume}`); for (const m of ctx.potriviri(/(? /^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; }