aere-quantum/crypto-inventory/lib/manifeste.mjs

135 lines
8.1 KiB
JavaScript

// Bibliotecile criptografice DECLARATE in manifeste. Declararea nu e folosire: aceste intrari
// ies ca componente "library", nu ca gasiri clasificate.
import { inceputuriDeRand, randul } from './lexer.mjs';
// nume -> ce ofera (text scurt, in engleza, pentru utilizator)
const NPM = {
'node-forge': 'RSA, AES, DES, MD5, SHA-1, SHA-2, X.509 (classical)',
elliptic: 'ECDSA/ECDH/EdDSA on classical curves including secp256k1',
secp256k1: 'secp256k1 ECDSA', 'tiny-secp256k1': 'secp256k1 ECDSA', '@noble/secp256k1': 'secp256k1 ECDSA',
ethers: 'secp256k1 ECDSA accounts and signatures', web3: 'secp256k1 ECDSA accounts and signatures', viem: 'secp256k1 ECDSA accounts and signatures',
'ethereum-cryptography': 'secp256k1, keccak, AES, scrypt',
jsonwebtoken: 'JWS HS*/RS*/PS*/ES*', jose: 'JWS/JWE (classical algorithms)', 'jwt-simple': 'JWS HS*/RS*',
'@noble/curves': 'secp256k1, P-256/384/521, Ed25519/X25519, Ed448, BLS12-381 (classical)',
'@noble/post-quantum': 'ML-KEM, ML-DSA, SLH-DSA (post-quantum)',
'@noble/hashes': 'SHA-2, SHA-3, BLAKE, legacy MD5/SHA-1/RIPEMD-160',
'@noble/ed25519': 'Ed25519',
'crypto-js': 'AES, DES, 3DES, RC4, MD5, SHA-1, SHA-2', tweetnacl: 'Ed25519, X25519, XSalsa20-Poly1305',
'libsodium-wrappers': 'Ed25519, X25519, XChaCha20-Poly1305, Argon2', 'node-rsa': 'RSA', jsrsasign: 'RSA, ECDSA, X.509',
openpgp: 'OpenPGP: RSA, ECC, AES', bcrypt: 'bcrypt password hashing', bcryptjs: 'bcrypt password hashing', argon2: 'Argon2 password hashing',
sshpk: 'SSH keys: RSA, ECDSA, Ed25519', '@peculiar/x509': 'X.509 certificates', pkijs: 'X.509/CMS',
};
const PYPI = {
cryptography: 'RSA, EC, DH, Ed25519/X25519, AES, hashes (pyca/cryptography)',
pycryptodome: 'RSA, DSA, ECC, AES, DES, hashes', pycryptodomex: 'RSA, DSA, ECC, AES, DES, hashes',
pycrypto: 'RSA, DSA, AES, DES (unmaintained since 2013)', ecdsa: 'ECDSA on classical curves', pyjwt: 'JWS HS*/RS*/ES*/PS*/EdDSA',
'python-jose': 'JWS/JWE (classical algorithms)', jwcrypto: 'JWS/JWE (classical algorithms)', pynacl: 'Ed25519, X25519',
pyopenssl: 'TLS and X.509 via OpenSSL', paramiko: 'SSH: RSA, ECDSA, Ed25519, classical key exchange', 'liboqs-python': 'post-quantum KEMs and signatures (liboqs)',
oqs: 'post-quantum KEMs and signatures (liboqs)', web3: 'secp256k1 ECDSA accounts', 'eth-account': 'secp256k1 ECDSA accounts', 'eth-keys': 'secp256k1 ECDSA',
coincurve: 'secp256k1 ECDSA', bcrypt: 'bcrypt password hashing', 'argon2-cffi': 'Argon2 password hashing', passlib: 'password hashing', rsa: 'RSA (pure Python)',
};
const MAVEN_GROUP = {
'org.bouncycastle': 'Bouncy Castle: classical and post-quantum algorithms',
'io.jsonwebtoken': 'JJWT: JWS HS*/RS*/ES*/PS*/EdDSA', 'com.nimbusds': 'Nimbus JOSE+JWT', 'com.auth0': 'java-jwt (if artifact is java-jwt)',
'org.web3j': 'secp256k1 ECDSA accounts (web3j)', 'com.google.crypto.tink': 'Tink: AEAD, signatures, hybrid encryption', 'org.conscrypt': 'Conscrypt TLS provider',
};
const GO = [
[/^golang\.org\/x\/crypto$/, 'extended crypto: ssh, chacha20poly1305, curve25519, bcrypt, argon2'],
[/^github\.com\/cloudflare\/circl$/, 'CIRCL: ML-KEM, ML-DSA, SLH-DSA, hybrid KEMs, classical curves'],
[/^github\.com\/ethereum\/go-ethereum$/, 'secp256k1 ECDSA accounts and signatures'],
[/^github\.com\/golang-jwt\/jwt(\/v\d+)?$/, 'JWS HS*/RS*/ES*/PS*/EdDSA'],
[/^github\.com\/decred\/dcrd\/dcrec\/secp256k1(\/v\d+)?$/, 'secp256k1 ECDSA'],
[/^github\.com\/btcsuite\/btcd\/btcec(\/v\d+)?$/, 'secp256k1 ECDSA'],
[/^filippo\.io\/edwards25519$/, 'Edwards25519 group arithmetic'],
[/^github\.com\/open-quantum-safe\/liboqs-go$/, 'post-quantum KEMs and signatures (liboqs)'],
];
export const NUME_MANIFEST = /^(package\.json|requirements[\w.-]*\.txt|pyproject\.toml|pom\.xml|build\.gradle(\.kts)?|go\.mod)$/;
function lib(ecosistem, nume, versiune, ofera, pos, inceputuri, fisier) {
const { line } = randul(inceputuri, pos);
const purl = ecosistem === 'npm' ? `pkg:npm/${nume.startsWith('@') ? '%40' + nume.slice(1) : nume}${versiune && /^[\w.-]+$/.test(versiune) ? '@' + versiune : ''}`
: ecosistem === 'pypi' ? `pkg:pypi/${nume}${versiune && /^[\w.-]+$/.test(versiune) ? '@' + versiune : ''}`
: ecosistem === 'maven' ? `pkg:maven/${nume.replace(':', '/')}${versiune && /^[\w.-]+$/.test(versiune) ? '@' + versiune : ''}`
: `pkg:golang/${nume}${versiune ? '@' + versiune : ''}`;
return { file: fisier, line, ecosystem: ecosistem, name: nume, version: versiune || undefined, provides: ofera, purl };
}
export function citesteManifest(text, numeFisier, rel) {
const inceputuri = inceputuriDeRand(text);
const r = [];
if (numeFisier === 'package.json') {
let j;
try { j = JSON.parse(text); } catch { return { biblioteci: [], eroare: 'package.json is not valid JSON' }; }
for (const sect of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
const d = j && j[sect];
if (!d || typeof d !== 'object') continue;
const ps = text.indexOf(`"${sect}"`);
for (const [nume, ver] of Object.entries(d)) {
const ofera = NPM[nume] || (/^@ethersproject\//.test(nume) ? 'secp256k1 ECDSA (ethers v5 module)' : null);
if (!ofera) continue;
const pos = text.indexOf(`"${nume}"`, ps < 0 ? 0 : ps);
r.push(lib('npm', nume, String(ver).replace(/^[\^~>=<\s]+/, ''), ofera, pos < 0 ? 0 : pos, inceputuri, rel));
}
}
} else if (/^requirements/.test(numeFisier)) {
let pos = 0;
for (const linie of text.split('\n')) {
const t = linie.replace(/#.*/, '').trim();
const m = /^([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:[=<>!~]=?\s*([\w.*+-]+))?/.exec(t);
if (m) {
const nume = m[1].toLowerCase().replace(/_/g, '-');
if (PYPI[nume]) r.push(lib('pypi', nume, m[2], PYPI[nume], pos + linie.indexOf(m[1]), inceputuri, rel));
}
pos += linie.length + 1;
}
} else if (numeFisier === 'pyproject.toml') {
let pos = 0;
let sectiune = '';
for (const linie of text.split('\n')) {
const h = /^\s*\[([^\]]+)\]/.exec(linie);
if (h) sectiune = h[1];
for (const m of linie.matchAll(/["']([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:[=<>!~]=?\s*([\w.*+-]+))?[^"']*["']/g)) {
const nume = m[1].toLowerCase().replace(/_/g, '-');
if (PYPI[nume]) r.push(lib('pypi', nume, m[2], PYPI[nume], pos + m.index + 1, inceputuri, rel));
}
const p = /^\s*([A-Za-z0-9_.-]+)\s*=\s*(?:["']([^"']*)["']|\{)/.exec(linie);
if (p && /dependencies/.test(sectiune)) {
const nume = p[1].toLowerCase().replace(/_/g, '-');
if (PYPI[nume]) r.push(lib('pypi', nume, p[2] ? p[2].replace(/^[\^~>=<\s]+/, '') : undefined, PYPI[nume], pos + linie.indexOf(p[1]), inceputuri, rel));
}
pos += linie.length + 1;
}
} else if (numeFisier === 'pom.xml') {
for (const m of text.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g)) {
const g = /<groupId>\s*([^<\s]+)\s*<\/groupId>/.exec(m[1]);
const a = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(m[1]);
const v = /<version>\s*([^<\s]+)\s*<\/version>/.exec(m[1]);
if (!g || !a) continue;
const ofera = MAVEN_GROUP[g[1]];
if (!ofera) continue;
r.push(lib('maven', `${g[1]}:${a[1]}`, v && !/\$\{/.test(v[1]) ? v[1] : undefined, ofera, m.index + m[0].indexOf(a[0]), inceputuri, rel));
}
} else if (/^build\.gradle/.test(numeFisier)) {
for (const m of text.matchAll(/["']([\w.-]+):([\w.-]+)(?::([\w.-]+))?["']/g)) {
const ofera = MAVEN_GROUP[m[1]];
if (ofera) r.push(lib('maven', `${m[1]}:${m[2]}`, m[3], ofera, m.index + 1, inceputuri, rel));
}
} else if (numeFisier === 'go.mod') {
let pos = 0;
let inRequire = false;
for (const linie of text.split('\n')) {
const t = linie.replace(/\/\/.*/, '').trim();
if (/^require\s*\($/.test(t)) inRequire = true;
else if (inRequire && t === ')') inRequire = false;
const m = inRequire ? /^([\w.\-/]+)\s+(v[\w.\-+]+)/.exec(t) : /^require\s+([\w.\-/]+)\s+(v[\w.\-+]+)/.exec(t);
if (m) {
const intrare = GO.find(([re]) => re.test(m[1]));
if (intrare) r.push(lib('golang', m[1], m[2], intrare[1], pos + linie.indexOf(m[1]), inceputuri, rel));
}
pos += linie.length + 1;
}
}
return { biblioteci: r };
}