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

419 lines
29 KiB
JavaScript

// Catalogul de clasificare. Fiecare gasire primeste o clasa, un motiv scris si, cand e
// vulnerabila, o recomandare de migrare. Textele pentru utilizator sunt in engleza.
//
// Reguli: o clasa nu se ghiceste. "unknown" inseamna ca valoarea nu se poate afla static.
export const CLS = Object.freeze({
V: 'quantum-vulnerable',
W: 'weak-now',
S: 'quantum-safe',
U: 'unknown',
});
export const REC = Object.freeze({
SIG: 'Migrate signatures to ML-DSA-65 (FIPS 204), or to a hybrid (composite) classical+ML-DSA signature during the transition so that both would have to be broken. For long-lived roots of trust consider SLH-DSA (FIPS 205). Keys and signatures grow (ML-DSA-65: 1,952-byte public key, 3,309-byte signature): check protocol, storage and column limits. Composite signatures for X.509 were still an IETF draft when this rule was written.',
KEX: 'For TLS, offer the hybrid group X25519MLKEM768 in TLS 1.3 (built into Go 1.24+ and OpenSSL 3.5+). For application-level key establishment use ML-KEM-768 (FIPS 203), ideally combined with X25519 during the transition. Traffic protected by a classical key exchange can be recorded now and decrypted later, so start with data that must stay confidential for years.',
PKE: 'Replace RSA encryption and key transport with ML-KEM-768 (FIPS 203) used as a KEM in front of an AEAD such as AES-256-GCM, ideally hybrid with X25519 during the transition. Ciphertexts recorded today become readable once a cryptographically relevant quantum computer exists; re-encrypt data that must stay confidential for years.',
SECP256K1: 'secp256k1 ECDSA is fixed by the account and transaction format of Ethereum-style and Bitcoin-style chains, so this is not a library swap. Plan the migration at the account level: move control of funds and roles to an account whose owner is a post-quantum key (for example an ML-DSA or Falcon key checked by a smart-contract account or by the chain itself), and stop reusing addresses whose public key is already exposed on chain.',
JWT: 'There is no final standard for post-quantum JWS/JWT yet: ML-DSA for JOSE and COSE was an IETF draft when this rule was written, and mainstream JWT libraries do not ship it by default. What you can do now: keep token lifetimes short, use HS256/HS512 where issuer and verifier can share a secret, inventory where verification keys live so they can be rotated, and plan a hybrid (classical + ML-DSA) signature once the specification is final and your library supports it.',
JWT_NONE: 'An unsigned JWT ("none") must never be accepted: remove it from the accepted algorithms.',
HASH_WEAK: 'Replace with SHA-256, SHA-384 or SHA3-256. If this is a non-security checksum (cache key, deduplication), document that; for passwords use a password hash (Argon2id, scrypt, bcrypt, or PBKDF2-HMAC-SHA-256 with a high iteration count).',
CIPHER_WEAK: 'Replace with AES-256-GCM or ChaCha20-Poly1305, with a unique nonce per key.',
ECB: 'ECB leaks plaintext patterns. Use an authenticated mode: AES-256-GCM (unique nonce per key) or ChaCha20-Poly1305.',
TLS_OLD: 'Disable SSL, TLS 1.0 and TLS 1.1; require at least TLS 1.2 and prefer TLS 1.3 with the hybrid group X25519MLKEM768.',
TLS12: 'TLS 1.2 has no standardized post-quantum key exchange. Prefer TLS 1.3 and offer X25519MLKEM768; keep TLS 1.2 only for clients that require it, and measure how many still negotiate it.',
TLS13: 'Check the negotiated key-exchange groups: offer X25519MLKEM768 first. Go 1.24+ does this by default when CurvePreferences is unset, and OpenSSL 3.5+ includes it in its default groups; older runtimes negotiate a classical group.',
AES128: 'Grover\'s algorithm gives at most a quadratic speed-up, which NIST treats as security category 1 for AES-128. Frameworks such as CNSA 2.0 require AES-256; prefer AES-256 for data that must stay confidential for decades.',
AES_SIZE: 'Key size is set at run time by the key length. AES-128 is NIST category 1 against a quantum attacker, AES-256 category 5; prefer AES-256 for long-lived data.',
PREPQ: 'Pre-standard variant: migrate to the final NIST standard (ML-KEM FIPS 203, ML-DSA FIPS 204, SLH-DSA FIPS 205, or FN-DSA for Falcon once published). Keys and outputs are not interoperable with the final versions.',
BROKEN_PQ: 'Broken by classical attacks in 2022. Remove it; use ML-KEM (FIPS 203) or ML-DSA (FIPS 204).',
UNKNOWN: 'Find where the value comes from (configuration, environment, caller) and review it there; this tool does not guess.',
LIB_MULTI: 'The library offers both classical and post-quantum-safe primitives; no specific call was recognized in this file. Review how it is used.',
});
// numele canonice ale curbelor
const ALIAS_CURBE = [
[/^(p-?256|prime256v1|secp256r1|nist256p|curvep256|p256)$/i, 'secp256r1'],
[/^(p-?384|secp384r1|nist384p|curvep384|p384)$/i, 'secp384r1'],
[/^(p-?521|secp521r1|nist521p|curvep521|p521)$/i, 'secp521r1'],
[/^(p-?224|secp224r1|nist224p|p224)$/i, 'secp224r1'],
[/^(p-?192|prime192v1|secp192r1|nist192p|p192)$/i, 'secp192r1'],
[/^(secp256k1|k256|k-256)$/i, 'secp256k1'],
[/^brainpoolp256r1$/i, 'brainpoolP256r1'],
[/^brainpoolp384r1$/i, 'brainpoolP384r1'],
[/^brainpoolp512r1$/i, 'brainpoolP512r1'],
[/^(curve25519|x25519)$/i, 'Curve25519'],
[/^(ed25519|edwards25519)$/i, 'Edwards25519'],
[/^(curve448|x448)$/i, 'Curve448'],
[/^(ed448|edwards448)$/i, 'Edwards448'],
];
const NIVEL_CURBA = {
secp256r1: 128, secp384r1: 192, secp521r1: 256, secp224r1: 112, secp192r1: 96, secp256k1: 128,
brainpoolP256r1: 128, brainpoolP384r1: 192, brainpoolP512r1: 256,
};
export function curbaCanonica(s) {
if (!s) return null;
const t = String(s).trim();
for (const [re, nume] of ALIAS_CURBE) if (re.test(t)) return nume;
return t;
}
// hash-uri: nume canonic, clasa, nivel NIST (categoria de coliziune), OID
const HASH = {
MD2: { n: 'MD2', c: CLS.W }, MD4: { n: 'MD4', c: CLS.W },
MD5: { n: 'MD5', c: CLS.W, oid: '1.2.840.113549.2.5' },
SHA1: { n: 'SHA-1', c: CLS.W, oid: '1.3.14.3.2.26' },
RIPEMD160: { n: 'RIPEMD-160', c: CLS.W },
SHA224: { n: 'SHA-224', c: CLS.S },
SHA256: { n: 'SHA-256', c: CLS.S, q: 2, cl: 128, oid: '2.16.840.1.101.3.4.2.1' },
SHA384: { n: 'SHA-384', c: CLS.S, q: 4, cl: 192, oid: '2.16.840.1.101.3.4.2.2' },
SHA512: { n: 'SHA-512', c: CLS.S, q: 4, cl: 256, oid: '2.16.840.1.101.3.4.2.3' },
'SHA512/256': { n: 'SHA-512/256', c: CLS.S, q: 2 },
'SHA512/224': { n: 'SHA-512/224', c: CLS.S },
'SHA3-224': { n: 'SHA3-224', c: CLS.S },
'SHA3-256': { n: 'SHA3-256', c: CLS.S, q: 2 },
'SHA3-384': { n: 'SHA3-384', c: CLS.S, q: 4 },
'SHA3-512': { n: 'SHA3-512', c: CLS.S, q: 4 },
SHAKE128: { n: 'SHAKE128', c: CLS.S, xof: true },
SHAKE256: { n: 'SHAKE256', c: CLS.S, xof: true },
BLAKE2B: { n: 'BLAKE2b', c: CLS.S }, BLAKE2S: { n: 'BLAKE2s', c: CLS.S },
BLAKE2B512: { n: 'BLAKE2b-512', c: CLS.S }, BLAKE2S256: { n: 'BLAKE2s-256', c: CLS.S },
SM3: { n: 'SM3', c: CLS.S },
KECCAK256: { n: 'Keccak-256', c: CLS.S },
};
export function hashCanonic(s) {
if (!s) return null;
let t = String(s).trim().toUpperCase().replace(/^RSA-/, '');
t = t.replace(/_/g, '-');
if (/^SHA-?1$|^SHA$/.test(t)) return 'SHA1';
if (/^SHA-?(224|256|384|512)$/.test(t)) return 'SHA' + t.replace(/\D/g, '');
if (/^SHA-?512[/-](224|256)$/.test(t)) return 'SHA512/' + t.slice(-3);
if (/^SHA3-?(224|256|384|512)$/.test(t)) return 'SHA3-' + t.slice(-3);
if (/^SHAKE-?(128|256)$/.test(t)) return 'SHAKE' + t.slice(-3);
if (/^MD[245]$/.test(t)) return t;
if (/^RIPEMD-?160$|^RMD160$/.test(t)) return 'RIPEMD160';
if (/^BLAKE2B-?512$/.test(t)) return 'BLAKE2B512';
if (/^BLAKE2S-?256$/.test(t)) return 'BLAKE2S256';
if (/^BLAKE2[BS]$/.test(t)) return t;
if (/^SM3$/.test(t)) return 'SM3';
if (/^KECCAK-?256$/.test(t)) return 'KECCAK256';
return null;
}
function rez(o) {
return {
classification: o.c,
quantumVulnerable: !!o.qv,
reason: o.motiv,
recommendation: o.rec || null,
nistQuantumSecurityLevel: o.q,
classicalSecurityLevel: o.cl,
oid: o.oid,
};
}
const SHOR = 'Shor\'s algorithm on a cryptographically relevant quantum computer recovers the private key from the public key';
// intrarea: { grup, nume?, param?, curba?, hash?, mod?, primitiv?, context? }
// iesirea: descrierea completa (nume canonic, primitiv, clasa, motiv, recomandare, niveluri)
export function evalueaza(a) {
const g = a.grup;
const f = (x) => Object.assign({ grup: g, nume: a.nume, primitiv: a.primitiv || 'unknown', param: a.param, curba: a.curba, mod: a.mod, padding: a.padding }, x);
if (g === 'UNKNOWN') {
return f(rez({ c: CLS.U, motiv: a.motiv || 'Algorithm could not be determined statically.', rec: REC.UNKNOWN }));
}
if (g === 'RSA') {
const bits = a.param ? Number(a.param) : null;
const nume = a.nume || (bits ? `RSA-${bits}` : 'RSA');
const prim = a.primitiv || 'unknown';
const rec = prim === 'pke' ? REC.PKE : (a.context === 'jwt' ? REC.JWT : (prim === 'signature' ? REC.SIG : `${REC.SIG} If the key is used for encryption: ${REC.PKE}`));
const hashSlab = a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W;
if (bits && bits < 2048) {
return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `RSA with a ${bits}-bit modulus is below the 2048-bit minimum (NIST SP 800-131A); also quantum-vulnerable: ${SHOR}.`, rec, q: 0, oid: '1.2.840.113549.1.1.1' }) });
}
if (hashSlab) {
return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `RSA signature over ${HASH[a.hash].n}, which has practical collision attacks; also quantum-vulnerable: ${SHOR}.`, rec: `${REC.HASH_WEAK} ${rec}`, q: 0 }) });
}
const cl = bits === 2048 ? 112 : bits === 3072 ? 128 : bits === 7680 ? 192 : bits === 15360 ? 256 : undefined;
return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `RSA: ${SHOR}.`, rec, q: 0, cl, oid: '1.2.840.113549.1.1.1' }) });
}
if (g === 'DSA') {
const bits = a.param ? Number(a.param) : null;
const nume = a.nume || (bits ? `DSA-${bits}` : 'DSA');
if ((bits && bits < 2048) || (a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W)) {
return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `DSA with ${bits ? bits + '-bit parameters' : 'a weak hash'} is below current minimums; FIPS 186-5 no longer approves DSA for generating signatures; also ${SHOR}.`, rec: REC.SIG, q: 0 }) });
}
return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `DSA: ${SHOR} (discrete logarithm). FIPS 186-5 no longer approves DSA for generating signatures.`, rec: REC.SIG, q: 0 }) });
}
if (g === 'DH') {
const bits = a.param && /^\d+$/.test(a.param) ? Number(a.param) : null;
const nume = a.nume || (a.param ? `DH-${a.param}` : 'DH');
if (bits && bits < 2048) {
return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `Finite-field Diffie-Hellman with a ${bits}-bit group is below the 2048-bit minimum; also ${SHOR} (discrete logarithm).`, rec: REC.KEX, q: 0 }) });
}
return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `Finite-field Diffie-Hellman: ${SHOR} (discrete logarithm), so recorded key exchanges can be decrypted later.`, rec: REC.KEX, q: 0 }) });
}
if (g === 'EC' || g === 'ECDSA' || g === 'ECDH' || g === 'SECP256K1') {
const curba = curbaCanonica(a.curba) || (g === 'SECP256K1' ? 'secp256k1' : null);
const e256k1 = curba === 'secp256k1';
const eticheta = g === 'SECP256K1' ? 'ECDSA' : g;
const nume = a.nume || (curba ? `${eticheta}-${curba}` : eticheta);
const cl = curba ? NIVEL_CURBA[curba] : undefined;
const prim = a.primitiv || (g === 'ECDH' ? 'key-agree' : (g === 'EC' ? 'other' : 'signature'));
let rec = g === 'ECDH' ? REC.KEX : (g === 'EC' ? `Signatures: ${REC.SIG} Key agreement: ${REC.KEX}` : REC.SIG);
if (e256k1 && g !== 'ECDH') rec = REC.SECP256K1;
if (a.context === 'jwt') rec = REC.JWT;
const baza = { nume, primitiv: prim, curba: curba || undefined, grup: e256k1 ? 'SECP256K1' : g };
if (cl !== undefined && cl < 112) {
return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `Elliptic curve ${curba} gives about ${cl}-bit classical security, below the 112-bit minimum; also ${SHOR} (elliptic-curve discrete logarithm).`, rec, q: 0, cl }) });
}
if (a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W) {
return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `ECDSA over ${HASH[a.hash].n}, which has practical collision attacks; also ${SHOR}.`, rec: `${REC.HASH_WEAK} ${rec}`, q: 0 }) });
}
const pe = curba ? ` on ${curba}` : '';
const motiv = e256k1
? `ECDSA on secp256k1: ${SHOR} (elliptic-curve discrete logarithm). On public blockchains the public key is visible on chain after the first signed transaction.`
: `${eticheta}${pe}: ${SHOR} (elliptic-curve discrete logarithm).`;
return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv, rec, q: 0, cl, oid: '1.2.840.10045.2.1' }) });
}
if (g === 'EDDSA') {
const nume = a.nume || 'Ed25519';
const e448 = /448/.test(nume);
const rec = a.context === 'jwt' ? REC.JWT : REC.SIG;
return f({ nume, primitiv: 'signature', curba: e448 ? 'Edwards448' : 'Edwards25519', ...rez({ c: CLS.V, qv: true, motiv: `${nume}: ${SHOR} (elliptic-curve discrete logarithm).`, rec, q: 0, cl: e448 ? 224 : 128, oid: e448 ? '1.3.101.113' : '1.3.101.112' }) });
}
if (g === 'XDH') {
const nume = a.nume || 'X25519';
const e448 = /448/.test(nume);
return f({ nume, primitiv: 'key-agree', curba: e448 ? 'Curve448' : 'Curve25519', ...rez({ c: CLS.V, qv: true, motiv: `${nume} key agreement: ${SHOR} (elliptic-curve discrete logarithm), so recorded key exchanges can be decrypted later.`, rec: REC.KEX, q: 0, cl: e448 ? 224 : 128, oid: e448 ? '1.3.101.111' : '1.3.101.110' }) });
}
if (g === 'PAIRING') {
return f({ nume: a.nume || 'BLS12-381', primitiv: a.primitiv || 'signature', ...rez({ c: CLS.V, qv: true, motiv: `${a.nume || 'Pairing-based cryptography'}: ${SHOR} (discrete logarithm in the pairing groups).`, rec: REC.SIG, q: 0 }) });
}
if (g === 'CLASSIC-SIG') {
// semnatura clasica al carei tip de cheie nu se vede (RSA, DSA sau ECDSA)
const h = a.hash && HASH[a.hash];
const nume = a.nume || `signature-with-${h ? h.n : 'unknown-hash'}`;
if (h && h.c === CLS.W) {
return f({ nume, primitiv: 'signature', ...rez({ c: CLS.W, qv: true, motiv: `${a.motiv || 'Classical signature'} over ${h.n}, which has practical collision attacks; the key type (RSA, DSA or ECDSA) is also quantum-vulnerable.`, rec: `${REC.HASH_WEAK} ${REC.SIG}`, q: 0 }) });
}
return f({ nume, primitiv: 'signature', ...rez({ c: CLS.V, qv: true, motiv: `${a.motiv || 'Classical signature (RSA, DSA or ECDSA key)'}: ${SHOR}.`, rec: REC.SIG, q: 0 }) });
}
if (g === 'HASH') {
const h = HASH[a.hash];
if (!h) return f({ nume: a.nume || String(a.hash), primitiv: 'hash', ...rez({ c: CLS.U, motiv: `Hash "${a.hash}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) });
const nota = a.nota ? ` ${a.nota}` : '';
if (h.c === CLS.W) {
const motiv = a.hash === 'RIPEMD160'
? `RIPEMD-160 has a 160-bit output (about 80-bit collision resistance), below the 112-bit minimum for new designs.${nota}`
: `${h.n} has practical collision attacks; unsafe for signatures, certificates and integrity against an adversary.${nota}`;
return f({ nume: h.n, primitiv: 'hash', ...rez({ c: CLS.W, motiv, rec: REC.HASH_WEAK, q: 0, oid: h.oid }) });
}
return f({ nume: h.n, primitiv: h.xof ? 'xof' : 'hash', ...rez({ c: CLS.S, motiv: `${h.n}: quantum attacks give at most a polynomial speed-up for collisions and preimages (Grover, BHT); output size keeps it within NIST categories.${nota}`, q: h.q, cl: h.cl, oid: h.oid }) });
}
if (g === 'MAC') {
if (!a.hash) return f({ nume: 'HMAC', primitiv: 'mac', ...rez({ c: CLS.U, motiv: 'HMAC whose hash function is bound to the key object (set where the key is imported or generated), not visible at this call.', rec: REC.UNKNOWN }) });
const h = HASH[a.hash];
const nume = `HMAC-${h ? h.n : String(a.hash || 'unknown')}`;
if (!h) return f({ nume, primitiv: 'mac', ...rez({ c: CLS.U, motiv: `HMAC hash "${a.hash}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) });
if (h.c === CLS.W) {
return f({ nume, primitiv: 'mac', ...rez({ c: CLS.W, motiv: `${nume}: HMAC does not depend on collision resistance and has no practical break, but ${h.n} is deprecated for new designs and should be retired.`, rec: 'Move to HMAC-SHA-256 or HMAC-SHA-384.', q: 0 }) });
}
return f({ nume, primitiv: 'mac', ...rez({ c: CLS.S, motiv: `${nume}: symmetric; a quantum attacker gains at most a quadratic speed-up (Grover) on key search.` }) });
}
if (g === 'KDF') {
const h = a.hash ? HASH[a.hash] : null;
const nume = a.nume || 'KDF';
if (h && h.c === CLS.W) {
return f({ nume, primitiv: 'kdf', ...rez({ c: CLS.W, motiv: `${nume} uses ${h.n}; not practically broken as a KDF, but deprecated for new designs.`, rec: 'Use PBKDF2-HMAC-SHA-256 (high iteration count), scrypt or Argon2id.', q: 0 }) });
}
return f({ nume, primitiv: 'kdf', ...rez({ c: CLS.S, motiv: `${nume}: symmetric key derivation; a quantum attacker gains at most a quadratic speed-up (Grover).` }) });
}
if (g === 'CIPHER') return cifru(a, f);
if (g === 'MLKEM') {
const p = String(a.param || '');
const q = p === '512' ? 1 : p === '768' ? 3 : p === '1024' ? 5 : undefined;
const oid = p === '512' ? '2.16.840.1.101.3.4.4.1' : p === '768' ? '2.16.840.1.101.3.4.4.2' : p === '1024' ? '2.16.840.1.101.3.4.4.3' : undefined;
const nume = p ? `ML-KEM-${p}` : 'ML-KEM';
return f({ nume, primitiv: 'kem', ...rez({ c: CLS.S, motiv: `${nume}: module-lattice KEM standardized in FIPS 203${q ? `, NIST category ${q}` : ' (parameter set not visible)'}.`, q, oid }) });
}
if (g === 'MLDSA') {
const p = String(a.param || '');
const q = p === '44' ? 2 : p === '65' ? 3 : p === '87' ? 5 : undefined;
const oid = p === '44' ? '2.16.840.1.101.3.4.3.17' : p === '65' ? '2.16.840.1.101.3.4.3.18' : p === '87' ? '2.16.840.1.101.3.4.3.19' : undefined;
const nume = p ? `ML-DSA-${p}` : 'ML-DSA';
return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: module-lattice signature standardized in FIPS 204${q ? `, NIST category ${q}` : ' (parameter set not visible)'}.`, q, oid }) });
}
if (g === 'SLHDSA') {
const p = String(a.param || '');
const m = /(128|192|256)/.exec(p);
const q = m ? ({ 128: 1, 192: 3, 256: 5 })[m[1]] : undefined;
const nume = p ? `SLH-DSA-${p}` : 'SLH-DSA';
return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: stateless hash-based signature standardized in FIPS 205${q ? `, NIST category ${q}` : ''}.`, q }) });
}
if (g === 'FALCON') {
const p = String(a.param || '');
const q = p === '512' ? 1 : p === '1024' ? 5 : undefined;
const nume = p ? `Falcon-${p}` : 'Falcon';
return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: NTRU-lattice signature selected by NIST (to be standardized as FN-DSA)${q ? `, NIST category ${q}` : ''}.`, q }) });
}
if (g === 'HASHSIG') {
return f({ nume: a.nume || 'XMSS/LMS', primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${a.nume || 'Stateful hash-based signature'}: approved in NIST SP 800-208; security depends on never reusing a one-time key state.` }) });
}
if (g === 'PREPQ') {
return f({ nume: a.nume, primitiv: a.primitiv, ...rez({ c: CLS.S, motiv: `${a.nume}: pre-standard version of a NIST-selected post-quantum scheme.`, rec: REC.PREPQ }) });
}
if (g === 'BROKENPQ') {
return f({ nume: a.nume, primitiv: a.primitiv, ...rez({ c: CLS.W, motiv: `${a.nume} was broken by classical attacks in 2022.`, rec: REC.BROKEN_PQ, q: 0 }) });
}
if (g === 'HQC') {
return f({ nume: a.nume || 'HQC', primitiv: 'kem', ...rez({ c: CLS.S, motiv: 'HQC: code-based KEM selected by NIST in 2025 as a second KEM; final standard pending.' }) });
}
if (g === 'HYBRID-KEX') {
const pq = /1024/.test(a.nume) ? 5 : 3;
return f({ nume: a.nume, primitiv: 'kem', ...rez({ c: CLS.S, motiv: `${a.nume}: hybrid key exchange (classical ECDH + ML-KEM); stays secure if either component holds. The NIST category given is that of the ML-KEM component.`, q: pq }) });
}
if (g === 'TLS') return tls(a, f);
if (g === 'JWT-HMAC') {
const h = HASH[a.hash];
return f({ nume: a.nume, primitiv: 'mac', ...rez({ c: CLS.S, motiv: `${a.nume} is HMAC-${h ? h.n : a.hash}: symmetric, so a quantum attacker gains at most a quadratic speed-up (Grover); the shared secret must be long and random.` }) });
}
if (g === 'JWT-NONE') {
return f({ nume: 'JWS none', primitiv: 'signature', ...rez({ c: CLS.W, motiv: 'The "none" algorithm means the token is not signed at all.', rec: REC.JWT_NONE, q: 0 }) });
}
if (g === 'LIB-MULTI') {
return f({ nume: a.nume, primitiv: 'unknown', ...rez({ c: CLS.U, motiv: a.motiv || `Imports ${a.nume}; no specific algorithm call recognized in this file.`, rec: REC.LIB_MULTI }) });
}
if (g === 'SSH') {
return f({ nume: a.nume || 'SSH', primitiv: 'unknown', ...rez({ c: CLS.U, motiv: a.motiv || 'SSH: key exchange and host-key algorithms are negotiated at run time and not visible here.', rec: 'Check the configured key-exchange algorithms: OpenSSH 9.9+ offers mlkem768x25519-sha256 and 9.0+ sntrup761x25519-sha512; host keys remain classical (Ed25519, ECDSA, RSA).' }) });
}
return f(rez({ c: CLS.U, motiv: `No classification rule for group "${g}".`, rec: REC.UNKNOWN }));
}
function cifru(a, f) {
const alg = String(a.nume || '').toUpperCase();
const mod = a.mod ? String(a.mod).toLowerCase() : undefined;
if (/^(DES|DES-?CBC|DES-?ECB|DES-?CFB|DES-?OFB)$/.test(alg) || alg === 'DES') {
return f({ nume: mod ? `DES-${mod.toUpperCase()}` : 'DES', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: 'DES has a 56-bit key and is brute-forceable today.', rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^(3DES|DESEDE|DES-?EDE3?|TRIPLEDES|TDEA|DES3)$/.test(alg)) {
return f({ nume: mod ? `3DES-${mod.toUpperCase()}` : '3DES', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: '3DES (TDEA) has a 64-bit block (Sweet32 birthday attacks) and was disallowed by NIST after 2023.', rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^(RC4|ARC4|ARCFOUR)$/.test(alg)) {
return f({ nume: 'RC4', primitiv: 'stream-cipher', ...rez({ c: CLS.W, motiv: 'RC4 has exploitable keystream biases and is prohibited in TLS (RFC 7465).', rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^(RC2|ARC2)$/.test(alg)) {
return f({ nume: 'RC2', primitiv: 'block-cipher', ...rez({ c: CLS.W, motiv: 'RC2 is an obsolete cipher with a 64-bit block.', rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^(BLOWFISH|BF)$/.test(alg)) {
return f({ nume: 'Blowfish', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: 'Blowfish has a 64-bit block (Sweet32 birthday attacks).', rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^(CAST5|CAST|IDEA|SEED)$/.test(alg)) {
return f({ nume: alg, primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: `${alg} has a 64-bit block (Sweet32 birthday attacks) or is obsolete.`, rec: REC.CIPHER_WEAK, q: 0 }) });
}
if (/^CHACHA20(-POLY1305)?$|^XCHACHA20-POLY1305$|^XSALSA20-POLY1305$|^SALSA20$/.test(alg)) {
const ae = /POLY1305/.test(alg);
return f({ nume: a.nume, primitiv: ae ? 'ae' : 'stream-cipher', ...rez({ c: CLS.S, motiv: `${a.nume}: 256-bit key; a quantum attacker gains at most a quadratic speed-up (Grover) on key search.` }) });
}
if (/^AES/.test(alg) || /^CAMELLIA/.test(alg) || /^SM4/.test(alg) || /^ARIA/.test(alg)) {
const familie = /^CAMELLIA/.test(alg) ? 'Camellia' : /^SM4/.test(alg) ? 'SM4' : /^ARIA/.test(alg) ? 'ARIA' : 'AES';
const bits = a.param ? Number(a.param) : (familie === 'SM4' ? 128 : null);
const numeMod = mod ? `-${mod.toUpperCase()}` : '';
const nume = `${familie}${bits ? '-' + bits : ''}${numeMod}`;
const ae = mod && /^(gcm|ccm|ocb|siv|gcm-siv|eax|poly1305)$/.test(mod);
const prim = ae ? 'ae' : 'block-cipher';
const modCdx = mod && ['cbc', 'ecb', 'ccm', 'gcm', 'cfb', 'ofb', 'ctr'].includes(mod) ? mod : (mod ? 'other' : undefined);
if (mod === 'ecb') {
return f({ nume, primitiv: prim, mod: modCdx, ...rez({ c: CLS.W, motiv: `${nume}: ECB mode encrypts equal blocks to equal ciphertext and leaks plaintext structure.${a.nota ? ' ' + a.nota : ''}`, rec: REC.ECB, q: 0 }) });
}
const q = bits === 128 ? 1 : bits === 192 ? 3 : bits === 256 ? 5 : undefined;
const cl = bits || undefined;
const rec = bits === 128 ? REC.AES128 : (bits ? null : REC.AES_SIZE);
const motiv = bits === 128
? `${nume}: symmetric; Grover's algorithm reduces key search at most quadratically, NIST category 1.`
: bits ? `${nume}: symmetric ${bits}-bit key; Grover's algorithm reduces key search at most quadratically, NIST category ${q}.`
: `${nume}: symmetric; key size not visible statically (AES-128 is NIST category 1, AES-256 category 5).`;
return f({ nume, primitiv: prim, mod: modCdx, param: bits ? String(bits) : undefined, ...rez({ c: CLS.S, motiv, rec, q, cl }) });
}
return f({ nume: a.nume, primitiv: 'unknown', ...rez({ c: CLS.U, motiv: `Cipher "${a.nume}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) });
}
// a.param = versiunea ('1.0','1.1','1.2','1.3','ssl3','negotiated'); a.rol = 'min'|'max'|'only'
function tls(a, f) {
const v = String(a.param || '');
const rol = a.rol || 'only';
const numeV = v === 'ssl3' ? 'SSLv3' : v === 'ssl2' ? 'SSLv2' : (v === 'negotiated' ? 'TLS (negotiated)' : `TLSv${v}`);
const nume = a.nume || numeV;
const baza = { nume, primitiv: 'other', protocolType: 'tls', protocolVersion: /^1\.[0-3]$/.test(v) ? v : (v === 'ssl3' ? '3.0' : undefined), rol };
if (v === 'ssl2' || v === 'ssl3' || v === '1.0' || v === '1.1') {
if (rol === 'max') {
return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `${numeV} as the maximum version caps the connection at a deprecated protocol (RFC 8996).`, rec: REC.TLS_OLD, q: 0 }) });
}
return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `${numeV} is deprecated (RFC 8996) and ${rol === 'min' ? 'allowed as the minimum version' : 'selected'} here.`, rec: REC.TLS_OLD, q: 0 }) });
}
if (v === '1.2') {
if (rol === 'min') {
return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv: 'TLS 1.2 is allowed; TLS 1.2 has no standardized post-quantum key exchange, so a peer that negotiates it uses classical (EC)DHE or RSA key exchange.', rec: REC.TLS12, q: 0 }) });
}
return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv: `TLS 1.2 ${rol === 'max' ? 'is the maximum version' : 'is selected'}; TLS 1.2 has no standardized post-quantum key exchange, so key exchange is classical (EC)DHE or RSA.`, rec: REC.TLS12, q: 0 }) });
}
if (v === '1.3') {
if (rol === 'max') {
return f({ ...baza, ...rez({ c: CLS.U, motiv: 'TLS 1.3 is the maximum version; the minimum and the key-exchange groups are not visible here.', rec: REC.TLS13 }) });
}
return f({ ...baza, ...rez({ c: CLS.U, motiv: 'TLS 1.3 only: whether key exchange is post-quantum depends on the negotiated group (X25519MLKEM768 or a classical group), which depends on runtime defaults and the peer.', rec: REC.TLS13 }) });
}
return f({ ...baza, ...rez({ c: CLS.U, motiv: 'Protocol version is negotiated at run time from the runtime defaults; not visible statically.', rec: REC.TLS13 }) });
}
// grupurile de schimb de chei TLS (ecdhCurve, CurvePreferences, jdk.tls.namedGroups)
export function grupTls(nume) {
const t = String(nume).trim();
if (/^(X25519MLKEM768|X25519Kyber768Draft00|SecP256r1MLKEM768|SecP384r1MLKEM1024|p256_mlkem768|p384_mlkem1024|x25519_mlkem768)$/i.test(t)) {
if (/kyber/i.test(t)) return { grup: 'PREPQ', nume: t, primitiv: 'kem' };
return { grup: 'HYBRID-KEX', nume: t };
}
if (/^(X25519|x25519)$/.test(t)) return { grup: 'XDH', nume: 'X25519' };
if (/^(X448|x448)$/.test(t)) return { grup: 'XDH', nume: 'X448' };
if (/^(MLKEM(512|768|1024)|mlkem(512|768|1024))$/i.test(t)) return { grup: 'MLKEM', param: t.replace(/\D/g, '') };
if (/^(ffdhe\d+)$/i.test(t)) return { grup: 'DH', param: t.toLowerCase() };
const c = curbaCanonica(t);
if (c && /^secp|^brainpool/.test(c)) return { grup: 'ECDH', curba: c };
if (/^auto$/i.test(t)) return { grup: 'UNKNOWN', motiv: 'ecdhCurve "auto" selects groups from the runtime defaults; not visible statically.' };
return { grup: 'UNKNOWN', motiv: `Key-exchange group "${t}" is not in this tool's catalog.` };
}
// algoritmii JWS (RFC 7518 + RFC 8037 + RFC 8812)
export function jws(alg) {
const m = /^(HS|RS|PS|ES)(256|384|512)$/.exec(alg);
if (m) {
const hash = 'SHA' + m[2];
if (m[1] === 'HS') return { grup: 'JWT-HMAC', nume: `JWS ${alg}`, hash, context: 'jwt' };
if (m[1] === 'RS') return { grup: 'RSA', nume: `JWS ${alg}`, primitiv: 'signature', padding: 'pkcs1v15', hash, context: 'jwt' };
if (m[1] === 'PS') return { grup: 'RSA', nume: `JWS ${alg}`, primitiv: 'signature', padding: 'other', hash, context: 'jwt' };
const curba = m[2] === '256' ? 'secp256r1' : m[2] === '384' ? 'secp384r1' : 'secp521r1';
return { grup: 'ECDSA', nume: `JWS ${alg}`, curba, hash, context: 'jwt' };
}
if (alg === 'ES256K') return { grup: 'ECDSA', nume: 'JWS ES256K', curba: 'secp256k1', context: 'jwt' };
if (alg === 'EdDSA' || alg === 'Ed25519') return { grup: 'EDDSA', nume: `JWS ${alg}`, context: 'jwt' };
if (alg === 'none') return { grup: 'JWT-NONE' };
return null;
}
export const JWS_RE = /^(?:(?:HS|RS|PS|ES)(?:256|384|512)|ES256K|EdDSA|Ed25519|none)$/;