104 lines
6.1 KiB
JavaScript
104 lines
6.1 KiB
JavaScript
// Material criptografic in text: blocuri PEM complete (antet, corp base64, subsol).
|
|
// Cheile private se raporteaza DOAR ca locatie si tip; valoarea nu se tipareste niciodata.
|
|
// Tipul cheii se afla parsand blocul in memorie cu node:crypto (date, nu cod executat).
|
|
import { createPublicKey, createPrivateKey, X509Certificate } from 'node:crypto';
|
|
|
|
// Cautarea blocurilor e LINIARA in marimea textului (2026-09-29, revizuirea adversariala): forma veche, un singur regex cu
|
|
// corp lenes si referinta inapoi, relua cautarea sfarsitului de la FIECARE antet BEGIN, deci un fisier de 1 MB cu antete fara
|
|
// sfarsit costa minute. Acum pentru fiecare antet se cere PRIMUL `-----END <tip>-----` de dupa el si ca intre ele sa nu fie niciun
|
|
// caracter din afara alfabetului corpului, adica exact ce potrivea regexul vechi; ambele pozitii se tin minte si se cauta numai
|
|
// inainte, deci fiecare caracter se citeste de un numar marginit de ori.
|
|
const SURSA_BEGIN = '-----BEGIN ((?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|PUBLIC KEY|RSA PUBLIC KEY|CERTIFICATE)-----';
|
|
const SURSA_AFARA_DIN_CORP = '[^A-Za-z0-9+/=\\s:,\\-\\r\\n]';
|
|
|
|
export function* blocuriPem(text) {
|
|
const reBegin = new RegExp(SURSA_BEGIN, 'g');
|
|
const reRau = new RegExp(SURSA_AFARA_DIN_CORP, 'g');
|
|
// o cautare tinuta minte e buna pentru pozitia `de` daca a pornit la sau inainte de `de` si nu a gasit ceva inainte de `de`
|
|
const valabil = (c, de) => c && c.la <= de && (c.poz < 0 || c.poz >= de);
|
|
const sfarsit = new Map(); // tip -> { la, poz }: primul END al tipului la pozitia >= la (poz -1 = niciunul)
|
|
let rau = null; // { la, poz }: primul caracter din afara alfabetului corpului, la pozitia >= la
|
|
const urmatorulSfarsit = (tip, de) => {
|
|
let c = sfarsit.get(tip);
|
|
if (!valabil(c, de)) { c = { la: de, poz: text.indexOf(`-----END ${tip}-----`, de) }; sfarsit.set(tip, c); }
|
|
return c.poz;
|
|
};
|
|
const urmatorulRau = (de) => {
|
|
if (!valabil(rau, de)) { reRau.lastIndex = de; const x = reRau.exec(text); rau = { la: de, poz: x ? x.index : -1 }; }
|
|
return rau.poz;
|
|
};
|
|
let m;
|
|
while ((m = reBegin.exec(text))) {
|
|
const tip = m[1];
|
|
const corpDe = m.index + m[0].length;
|
|
const e = urmatorulSfarsit(tip, corpDe);
|
|
const r = e < 0 ? -1 : urmatorulRau(corpDe);
|
|
// fara potrivire: cautarea continua de la caracterul urmator, ca la regexul vechi (un antet poate incepe in liniutele celui de dinainte)
|
|
if (e < 0 || (r >= 0 && r < e)) { reBegin.lastIndex = m.index + 1; continue; }
|
|
const capat = e + `-----END ${tip}-----`.length;
|
|
yield { index: m.index, tip, corp: text.slice(corpDe, e), bloc: text.slice(m.index, capat) };
|
|
reBegin.lastIndex = capat;
|
|
}
|
|
}
|
|
|
|
function activCheie(ko) {
|
|
const t = ko.asymmetricKeyType;
|
|
const d = ko.asymmetricKeyDetails || {};
|
|
if (t === 'rsa' || t === 'rsa-pss') return { grup: 'RSA', param: d.modulusLength ? String(d.modulusLength) : undefined };
|
|
if (t === 'dsa') return { grup: 'DSA', param: d.modulusLength ? String(d.modulusLength) : undefined };
|
|
if (t === 'dh') return { grup: 'DH' };
|
|
if (t === 'ec') return { grup: 'EC', curba: d.namedCurve };
|
|
if (t === 'ed25519' || t === 'ed448') return { grup: 'EDDSA', nume: t === 'ed25519' ? 'Ed25519' : 'Ed448' };
|
|
if (t === 'x25519' || t === 'x448') return { grup: 'XDH', nume: t === 'x25519' ? 'X25519' : 'X448' };
|
|
if (/^ml-dsa-(44|65|87)$/.test(t)) return { grup: 'MLDSA', param: t.slice(7) };
|
|
if (/^ml-kem-(512|768|1024)$/.test(t)) return { grup: 'MLKEM', param: t.slice(7) };
|
|
if (/^slh-dsa/.test(t)) return { grup: 'SLHDSA', param: t.slice(8) };
|
|
return { grup: 'UNKNOWN', motiv: `Key type "${t}" is not in this tool's catalog.` };
|
|
}
|
|
|
|
export function detecteazaPem(text, ctx) {
|
|
for (const b of blocuriPem(text)) {
|
|
const m = [b.bloc, b.tip, b.corp];
|
|
m.index = b.index;
|
|
const corp = m[2].replace(/[\s]/g, '').replace(/^Proc-Type:.*?DEK-Info:[^A-Za-z0-9+/]*/, '');
|
|
if (corp.replace(/[^A-Za-z0-9+/=]/g, '').length < 64) continue; // un antet singur (de ex. intr-un parser) nu e material
|
|
const tip = m[1];
|
|
let act;
|
|
let assetType;
|
|
if (tip === 'CERTIFICATE') {
|
|
assetType = 'certificate';
|
|
try {
|
|
const x = new X509Certificate(m[0]);
|
|
act = { ...activCheie(x.publicKey), nota: 'Certificate public key.' };
|
|
act.certificat = {
|
|
subjectName: x.subject.replace(/\n/g, ', '),
|
|
issuerName: x.issuer.replace(/\n/g, ', '),
|
|
notValidBefore: new Date(x.validFrom).toISOString(),
|
|
notValidAfter: new Date(x.validTo).toISOString(),
|
|
certificateFormat: 'X.509',
|
|
certificateExtension: 'pem',
|
|
fingerprintSha256: x.fingerprint256,
|
|
};
|
|
} catch {
|
|
act = { grup: 'UNKNOWN', motiv: 'PEM certificate block that could not be parsed.', certificat: { certificateFormat: 'X.509', certificateExtension: 'pem' } };
|
|
}
|
|
} else if (/PUBLIC KEY/.test(tip)) {
|
|
assetType = 'related-crypto-material';
|
|
try { act = activCheie(createPublicKey(m[0])); } catch { act = tip === 'RSA PUBLIC KEY' ? { grup: 'RSA' } : { grup: 'UNKNOWN', motiv: 'PEM public key block that could not be parsed.' }; }
|
|
act.material = { type: 'public-key', format: 'PEM' };
|
|
} else {
|
|
assetType = 'related-crypto-material';
|
|
if (/ENCRYPTED/.test(tip) || /Proc-Type:\s*4,ENCRYPTED/.test(m[2])) act = { grup: 'UNKNOWN', motiv: 'Encrypted private key: algorithm not visible without the passphrase.' };
|
|
else if (tip === 'OPENSSH PRIVATE KEY') act = { grup: 'UNKNOWN', motiv: 'OpenSSH private key: algorithm not parsed by this tool.' };
|
|
else {
|
|
try { act = activCheie(createPrivateKey(m[0])); } catch {
|
|
act = tip.startsWith('RSA') ? { grup: 'RSA' } : tip.startsWith('EC') ? { grup: 'EC' } : tip.startsWith('DSA') ? { grup: 'DSA' } : { grup: 'UNKNOWN', motiv: 'PEM private key block that could not be parsed.' };
|
|
}
|
|
}
|
|
act.material = { type: 'private-key', format: 'PEM' };
|
|
act.nota = 'Private key material stored in the scanned tree: if it is real, treat it as exposed and rotate it.';
|
|
}
|
|
ctx.adauga(m.index, `PEM ${tip}`, 'pem', { ...act, assetType }, { bucata: `-----BEGIN ${tip}-----` });
|
|
}
|
|
}
|