cryptoserve 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Multi-language crypto pattern detection.
3
+ *
4
+ * Detects crypto usage in Go, Python, Java/Kotlin, Rust, and C/C++ source files.
5
+ * Ported from backend/app/core/code_scanner.py LIBRARY_PATTERNS.
6
+ * Zero dependencies.
7
+ */
8
+
9
+ import { extname } from 'node:path';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Per-language regex patterns
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export const LANGUAGE_PATTERNS = {
16
+ go: {
17
+ extensions: ['.go'],
18
+ patterns: [
19
+ { regex: /aes\.NewCipher/g, algorithm: 'aes', category: 'encryption' },
20
+ { regex: /des\.NewCipher/g, algorithm: 'des', category: 'encryption' },
21
+ { regex: /des\.NewTripleDESCipher/g, algorithm: '3des', category: 'encryption' },
22
+ { regex: /sha256\.New\(\)|sha256\.Sum256/g, algorithm: 'sha256', category: 'hashing' },
23
+ { regex: /sha512\.New\(\)|sha512\.Sum512/g, algorithm: 'sha512', category: 'hashing' },
24
+ { regex: /sha1\.New\(\)|sha1\.Sum/g, algorithm: 'sha1', category: 'hashing' },
25
+ { regex: /md5\.New\(\)|md5\.Sum/g, algorithm: 'md5', category: 'hashing' },
26
+ { regex: /rsa\.GenerateKey|rsa\.EncryptOAEP|rsa\.SignPKCS1v15|rsa\.EncryptPKCS1v15/g, algorithm: 'rsa', category: 'encryption' },
27
+ { regex: /ecdsa\.GenerateKey|ecdsa\.Sign|ecdsa\.Verify/g, algorithm: 'ecdsa', category: 'signing' },
28
+ { regex: /ed25519\.GenerateKey|ed25519\.Sign|ed25519\.Verify/g, algorithm: 'ed25519', category: 'signing' },
29
+ { regex: /chacha20poly1305\.New/g, algorithm: 'chacha20-poly1305', category: 'encryption' },
30
+ { regex: /argon2\.IDKey|argon2\.Key/g, algorithm: 'argon2id', category: 'kdf' },
31
+ { regex: /bcrypt\.GenerateFromPassword|bcrypt\.CompareHashAndPassword/g, algorithm: 'bcrypt', category: 'kdf' },
32
+ { regex: /scrypt\.Key/g, algorithm: 'scrypt', category: 'kdf' },
33
+ { regex: /pbkdf2\.Key/g, algorithm: 'pbkdf2', category: 'kdf' },
34
+ { regex: /curve25519\.X25519|ecdh\.P256\(\)|ecdh\.X25519\(\)/g, algorithm: 'x25519', category: 'key_exchange' },
35
+ { regex: /hmac\.New/g, algorithm: 'hmac', category: 'mac' },
36
+ { regex: /hkdf\.New|hkdf\.Expand/g, algorithm: 'hkdf', category: 'kdf' },
37
+ { regex: /tls\.Config\{|tls\.Listen|tls\.Dial/g, algorithm: 'tls', category: 'protocol' },
38
+ { regex: /circl\/.*kyber|circl\/.*dilithium/g, algorithm: 'kyber', category: 'key_exchange' },
39
+ ],
40
+ importPatterns: [
41
+ { regex: /["']crypto\/aes["']/g, library: 'crypto/aes' },
42
+ { regex: /["']crypto\/des["']/g, library: 'crypto/des' },
43
+ { regex: /["']crypto\/rsa["']/g, library: 'crypto/rsa' },
44
+ { regex: /["']crypto\/ecdsa["']/g, library: 'crypto/ecdsa' },
45
+ { regex: /["']crypto\/ed25519["']/g, library: 'crypto/ed25519' },
46
+ { regex: /["']crypto\/sha256["']/g, library: 'crypto/sha256' },
47
+ { regex: /["']crypto\/sha512["']/g, library: 'crypto/sha512' },
48
+ { regex: /["']crypto\/sha1["']/g, library: 'crypto/sha1' },
49
+ { regex: /["']crypto\/md5["']/g, library: 'crypto/md5' },
50
+ { regex: /["']crypto\/hmac["']/g, library: 'crypto/hmac' },
51
+ { regex: /["']crypto\/tls["']/g, library: 'crypto/tls' },
52
+ { regex: /["']crypto\/ecdh["']/g, library: 'crypto/ecdh' },
53
+ { regex: /["']golang\.org\/x\/crypto/g, library: 'golang.org/x/crypto' },
54
+ { regex: /["']github\.com\/cloudflare\/circl/g, library: 'circl' },
55
+ ],
56
+ },
57
+ python: {
58
+ extensions: ['.py'],
59
+ patterns: [
60
+ { regex: /hashlib\.sha256|hashlib\.new\s*\(\s*['"]sha256/g, algorithm: 'sha256', category: 'hashing' },
61
+ { regex: /hashlib\.sha384|hashlib\.new\s*\(\s*['"]sha384/g, algorithm: 'sha384', category: 'hashing' },
62
+ { regex: /hashlib\.sha512|hashlib\.new\s*\(\s*['"]sha512/g, algorithm: 'sha512', category: 'hashing' },
63
+ { regex: /hashlib\.md5|hashlib\.new\s*\(\s*['"]md5/g, algorithm: 'md5', category: 'hashing' },
64
+ { regex: /hashlib\.sha1|hashlib\.new\s*\(\s*['"]sha1/g, algorithm: 'sha1', category: 'hashing' },
65
+ { regex: /hashlib\.sha3_256|hashlib\.new\s*\(\s*['"]sha3_256/g, algorithm: 'sha3-256', category: 'hashing' },
66
+ { regex: /hashlib\.blake2b/g, algorithm: 'blake2b', category: 'hashing' },
67
+ { regex: /from\s+Crypto\.Cipher\s+import\s+AES|AES\.new/g, algorithm: 'aes', category: 'encryption' },
68
+ { regex: /from\s+Crypto\.Cipher\s+import\s+DES\b|DES\.new/g, algorithm: 'des', category: 'encryption' },
69
+ { regex: /from\s+Crypto\.Cipher\s+import\s+DES3|DES3\.new/g, algorithm: '3des', category: 'encryption' },
70
+ { regex: /RSA\.generate|PKCS1_OAEP|RSA\.import_key/g, algorithm: 'rsa', category: 'encryption' },
71
+ { regex: /from\s+cryptography.*import.*Fernet/g, algorithm: 'aes-128-cbc', category: 'encryption' },
72
+ { regex: /AESGCM|algorithms\.AES/g, algorithm: 'aes-gcm', category: 'encryption' },
73
+ { regex: /Ed25519PrivateKey|Ed25519PublicKey/g, algorithm: 'ed25519', category: 'signing' },
74
+ { regex: /X25519PrivateKey|X25519PublicKey/g, algorithm: 'x25519', category: 'key_exchange' },
75
+ { regex: /ECDSA|ec\.SECP256R1|ec\.SECP384R1/g, algorithm: 'ecdsa', category: 'signing' },
76
+ { regex: /bcrypt\.hashpw|bcrypt\.gensalt/g, algorithm: 'bcrypt', category: 'kdf' },
77
+ { regex: /argon2\.PasswordHasher|argon2\.hash_password/g, algorithm: 'argon2', category: 'kdf' },
78
+ { regex: /PBKDF2HMAC/g, algorithm: 'pbkdf2', category: 'kdf' },
79
+ { regex: /Scrypt/g, algorithm: 'scrypt', category: 'kdf' },
80
+ { regex: /ChaCha20Poly1305/g, algorithm: 'chacha20-poly1305', category: 'encryption' },
81
+ { regex: /from\s+hmac\s+import|hmac\.new/g, algorithm: 'hmac', category: 'mac' },
82
+ ],
83
+ importPatterns: [
84
+ { regex: /import\s+hashlib/g, library: 'hashlib' },
85
+ { regex: /from\s+Crypto\b/g, library: 'pycryptodome' },
86
+ { regex: /from\s+cryptography\b/g, library: 'cryptography' },
87
+ { regex: /import\s+bcrypt/g, library: 'bcrypt' },
88
+ { regex: /import\s+argon2/g, library: 'argon2-cffi' },
89
+ { regex: /from\s+nacl\b|import\s+nacl/g, library: 'pynacl' },
90
+ { regex: /import\s+jwt\b|from\s+jwt\b/g, library: 'pyjwt' },
91
+ ],
92
+ },
93
+ java: {
94
+ extensions: ['.java', '.kt', '.scala'],
95
+ patterns: [
96
+ { regex: /Cipher\.getInstance\s*\(\s*["']AES/g, algorithm: 'aes', category: 'encryption' },
97
+ { regex: /Cipher\.getInstance\s*\(\s*["']DES\b/g, algorithm: 'des', category: 'encryption' },
98
+ { regex: /Cipher\.getInstance\s*\(\s*["']DESede/g, algorithm: '3des', category: 'encryption' },
99
+ { regex: /Cipher\.getInstance\s*\(\s*["']RSA/g, algorithm: 'rsa', category: 'encryption' },
100
+ { regex: /Cipher\.getInstance\s*\(\s*["']ChaCha20/g, algorithm: 'chacha20-poly1305', category: 'encryption' },
101
+ { regex: /Cipher\.getInstance\s*\(\s*["']Blowfish/g, algorithm: 'blowfish', category: 'encryption' },
102
+ { regex: /Cipher\.getInstance\s*\(\s*["']RC4/g, algorithm: 'rc4', category: 'encryption' },
103
+ { regex: /MessageDigest\.getInstance\s*\(\s*["']SHA-256/g, algorithm: 'sha256', category: 'hashing' },
104
+ { regex: /MessageDigest\.getInstance\s*\(\s*["']SHA-384/g, algorithm: 'sha384', category: 'hashing' },
105
+ { regex: /MessageDigest\.getInstance\s*\(\s*["']SHA-512/g, algorithm: 'sha512', category: 'hashing' },
106
+ { regex: /MessageDigest\.getInstance\s*\(\s*["']SHA-1/g, algorithm: 'sha1', category: 'hashing' },
107
+ { regex: /MessageDigest\.getInstance\s*\(\s*["']MD5/g, algorithm: 'md5', category: 'hashing' },
108
+ { regex: /KeyPairGenerator\.getInstance\s*\(\s*["']RSA/g, algorithm: 'rsa', category: 'encryption' },
109
+ { regex: /KeyPairGenerator\.getInstance\s*\(\s*["']EC/g, algorithm: 'ecdsa', category: 'signing' },
110
+ { regex: /KeyPairGenerator\.getInstance\s*\(\s*["']DSA/g, algorithm: 'dsa', category: 'signing' },
111
+ { regex: /Signature\.getInstance\s*\(\s*["']SHA256withRSA/g, algorithm: 'rsa', category: 'signing' },
112
+ { regex: /Signature\.getInstance\s*\(\s*["']SHA256withECDSA/g, algorithm: 'ecdsa', category: 'signing' },
113
+ { regex: /SecretKeyFactory\.getInstance\s*\(\s*["']PBKDF2/g, algorithm: 'pbkdf2', category: 'kdf' },
114
+ { regex: /KeyAgreement\.getInstance\s*\(\s*["']ECDH/g, algorithm: 'ecdh', category: 'key_exchange' },
115
+ { regex: /KeyAgreement\.getInstance\s*\(\s*["']DH/g, algorithm: 'dh', category: 'key_exchange' },
116
+ { regex: /SSLContext\.getInstance\s*\(\s*["']TLS/g, algorithm: 'tls', category: 'protocol' },
117
+ { regex: /Mac\.getInstance\s*\(\s*["']HmacSHA/g, algorithm: 'hmac', category: 'mac' },
118
+ ],
119
+ importPatterns: [
120
+ { regex: /import\s+javax\.crypto/g, library: 'javax.crypto' },
121
+ { regex: /import\s+java\.security/g, library: 'java.security' },
122
+ { regex: /import\s+org\.bouncycastle/g, library: 'bouncycastle' },
123
+ ],
124
+ },
125
+ rust: {
126
+ extensions: ['.rs'],
127
+ patterns: [
128
+ { regex: /use\s+aes::|Aes256Gcm|Aes128Gcm|Aes256/g, algorithm: 'aes-gcm', category: 'encryption' },
129
+ { regex: /use\s+chacha20poly1305::|ChaCha20Poly1305/g, algorithm: 'chacha20-poly1305', category: 'encryption' },
130
+ { regex: /use\s+rsa::|RsaPrivateKey|RsaPublicKey/g, algorithm: 'rsa', category: 'encryption' },
131
+ { regex: /use\s+ed25519::|Ed25519/g, algorithm: 'ed25519', category: 'signing' },
132
+ { regex: /use\s+ed25519_dalek::/g, algorithm: 'ed25519', category: 'signing' },
133
+ { regex: /use\s+sha2::|Sha256::new|Sha512::new/g, algorithm: 'sha256', category: 'hashing' },
134
+ { regex: /use\s+sha1::|Sha1::new/g, algorithm: 'sha1', category: 'hashing' },
135
+ { regex: /use\s+md5::|Md5::new/g, algorithm: 'md5', category: 'hashing' },
136
+ { regex: /use\s+blake2::|Blake2b/g, algorithm: 'blake2b', category: 'hashing' },
137
+ { regex: /use\s+argon2::|Argon2/g, algorithm: 'argon2', category: 'kdf' },
138
+ { regex: /use\s+bcrypt::/g, algorithm: 'bcrypt', category: 'kdf' },
139
+ { regex: /use\s+scrypt::/g, algorithm: 'scrypt', category: 'kdf' },
140
+ { regex: /use\s+pbkdf2::/g, algorithm: 'pbkdf2', category: 'kdf' },
141
+ { regex: /use\s+x25519_dalek::|X25519/g, algorithm: 'x25519', category: 'key_exchange' },
142
+ { regex: /use\s+p256::|use\s+p384::/g, algorithm: 'ecdsa', category: 'signing' },
143
+ { regex: /use\s+ring::/g, algorithm: 'aes-gcm', category: 'encryption' },
144
+ { regex: /use\s+pqcrypto::|use\s+oqs::/g, algorithm: 'ml-kem', category: 'key_exchange' },
145
+ { regex: /use\s+hmac::|Hmac::new/g, algorithm: 'hmac', category: 'mac' },
146
+ { regex: /use\s+hkdf::/g, algorithm: 'hkdf', category: 'kdf' },
147
+ ],
148
+ importPatterns: [
149
+ { regex: /\[dependencies\][\s\S]*?(?:aes-gcm|aes)\s*=/g, library: 'aes-gcm' },
150
+ { regex: /\[dependencies\][\s\S]*?ring\s*=/g, library: 'ring' },
151
+ { regex: /\[dependencies\][\s\S]*?pqcrypto\s*=/g, library: 'pqcrypto' },
152
+ ],
153
+ },
154
+ c: {
155
+ extensions: ['.c', '.h', '.cpp', '.hpp', '.cc', '.cxx'],
156
+ patterns: [
157
+ { regex: /EVP_aes_256_gcm|EVP_aes_128_gcm|EVP_aes_256_cbc|AES_encrypt|AES_set_encrypt_key/g, algorithm: 'aes', category: 'encryption' },
158
+ { regex: /EVP_des_|DES_ecb_encrypt|DES_set_key/g, algorithm: 'des', category: 'encryption' },
159
+ { regex: /EVP_des_ede3|DES_ede3_cbc_encrypt/g, algorithm: '3des', category: 'encryption' },
160
+ { regex: /RSA_generate_key|EVP_PKEY_RSA|RSA_public_encrypt/g, algorithm: 'rsa', category: 'encryption' },
161
+ { regex: /EVP_sha256|SHA256_Init|SHA256_Update/g, algorithm: 'sha256', category: 'hashing' },
162
+ { regex: /EVP_sha1|SHA1_Init|SHA1_Update/g, algorithm: 'sha1', category: 'hashing' },
163
+ { regex: /EVP_md5|MD5_Init|MD5_Update/g, algorithm: 'md5', category: 'hashing' },
164
+ { regex: /EVP_sha512|SHA512_Init/g, algorithm: 'sha512', category: 'hashing' },
165
+ { regex: /EVP_sha3_256/g, algorithm: 'sha3-256', category: 'hashing' },
166
+ { regex: /EC_KEY_generate|ECDSA_sign|ECDSA_verify/g, algorithm: 'ecdsa', category: 'signing' },
167
+ { regex: /EVP_chacha20_poly1305/g, algorithm: 'chacha20-poly1305', category: 'encryption' },
168
+ { regex: /PKCS5_PBKDF2_HMAC/g, algorithm: 'pbkdf2', category: 'kdf' },
169
+ { regex: /EVP_PKEY_X25519/g, algorithm: 'x25519', category: 'key_exchange' },
170
+ { regex: /HMAC_Init|HMAC_Update|HMAC_CTX/g, algorithm: 'hmac', category: 'mac' },
171
+ { regex: /EVP_PKEY_EC|EC_GROUP_new/g, algorithm: 'ecdh', category: 'key_exchange' },
172
+ { regex: /EVP_bf_cbc|BF_encrypt/g, algorithm: 'blowfish', category: 'encryption' },
173
+ { regex: /EVP_rc4|RC4_set_key/g, algorithm: 'rc4', category: 'encryption' },
174
+ { regex: /SSL_CTX_new|TLS_method|SSLv23_method/g, algorithm: 'tls', category: 'protocol' },
175
+ ],
176
+ importPatterns: [
177
+ { regex: /#include\s*<openssl\/evp\.h>/g, library: 'openssl' },
178
+ { regex: /#include\s*<openssl\/aes\.h>/g, library: 'openssl' },
179
+ { regex: /#include\s*<openssl\/rsa\.h>/g, library: 'openssl' },
180
+ { regex: /#include\s*<openssl\/ssl\.h>/g, library: 'openssl' },
181
+ { regex: /#include\s*<sodium\.h>/g, library: 'libsodium' },
182
+ { regex: /#include\s*<oqs\/oqs\.h>/g, library: 'liboqs' },
183
+ ],
184
+ },
185
+ };
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Extension → language mapping
189
+ // ---------------------------------------------------------------------------
190
+
191
+ const EXT_MAP = {};
192
+ for (const [lang, def] of Object.entries(LANGUAGE_PATTERNS)) {
193
+ for (const ext of def.extensions) {
194
+ EXT_MAP[ext] = lang;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Detect programming language from file extension.
200
+ * Returns language key (go, python, java, rust, c) or null.
201
+ */
202
+ export function detectLanguage(filePath) {
203
+ const ext = extname(filePath).toLowerCase();
204
+ return EXT_MAP[ext] || null;
205
+ }
206
+
207
+ /**
208
+ * Scan a source file for crypto patterns in the specified language.
209
+ * Returns { algorithms: [{ algorithm, category, line? }], imports: [{ library }] }.
210
+ */
211
+ export function scanSourceFile(filePath, content, language) {
212
+ const langDef = LANGUAGE_PATTERNS[language];
213
+ if (!langDef) return { algorithms: [], imports: [] };
214
+
215
+ const algorithms = [];
216
+ const imports = [];
217
+ const seenAlgos = new Set();
218
+ const seenImports = new Set();
219
+
220
+ for (const { regex, algorithm, category } of langDef.patterns) {
221
+ regex.lastIndex = 0;
222
+ if (regex.test(content) && !seenAlgos.has(algorithm)) {
223
+ seenAlgos.add(algorithm);
224
+ algorithms.push({ algorithm, category });
225
+ }
226
+ }
227
+
228
+ for (const { regex, library } of langDef.importPatterns) {
229
+ regex.lastIndex = 0;
230
+ if (regex.test(content) && !seenImports.has(library)) {
231
+ seenImports.add(library);
232
+ imports.push({ library });
233
+ }
234
+ }
235
+
236
+ return { algorithms, imports };
237
+ }
238
+
239
+ /**
240
+ * All supported non-JS source extensions for the file walker.
241
+ */
242
+ export const MULTI_LANG_EXTENSIONS = new Set(Object.values(LANGUAGE_PATTERNS).flatMap(l => l.extensions));
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Multi-ecosystem manifest parser and crypto package detector.
3
+ *
4
+ * Parses go.mod, requirements.txt, pyproject.toml, Cargo.toml, pom.xml
5
+ * and identifies known crypto packages.
6
+ * Ported from backend/app/core/dependency_scanner.py.
7
+ * Zero dependencies.
8
+ */
9
+
10
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
11
+ import { join, basename } from 'node:path';
12
+ import { CRYPTO_PACKAGES, lookupPackage } from './crypto-registry.mjs';
13
+
14
+ // Re-export for backward compatibility
15
+ export { CRYPTO_PACKAGES };
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Manifest parsers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Parse go.mod — extract module dependencies.
23
+ */
24
+ export function parseGoMod(content) {
25
+ const deps = [];
26
+ // Match require block
27
+ const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
28
+ const lines = requireBlock ? requireBlock[1].split('\n') : content.split('\n');
29
+
30
+ for (const line of lines) {
31
+ const trimmed = line.trim();
32
+ if (!trimmed || trimmed.startsWith('//') || trimmed === 'require') continue;
33
+ const match = trimmed.match(/^([a-zA-Z0-9.\/_-]+)\s+(v[\d.]+[a-zA-Z0-9._-]*)/);
34
+ if (match) {
35
+ deps.push({ name: match[1], version: match[2] });
36
+ }
37
+ }
38
+
39
+ // Also match single-line require directives
40
+ const singleReqs = content.matchAll(/^require\s+([a-zA-Z0-9.\/_-]+)\s+(v[\d.]+[a-zA-Z0-9._-]*)/gm);
41
+ for (const m of singleReqs) {
42
+ if (!deps.some(d => d.name === m[1])) {
43
+ deps.push({ name: m[1], version: m[2] });
44
+ }
45
+ }
46
+
47
+ return deps;
48
+ }
49
+
50
+ /**
51
+ * Parse requirements.txt — extract package==version lines.
52
+ */
53
+ export function parseRequirementsTxt(content) {
54
+ const deps = [];
55
+ for (const line of content.split('\n')) {
56
+ const trimmed = line.trim();
57
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue;
58
+ const match = trimmed.match(/^([a-zA-Z0-9._-]+)(?:\[.*?\])?\s*(?:([<>=!~]+)\s*(.*))?$/);
59
+ if (match) {
60
+ deps.push({
61
+ name: match[1].toLowerCase(),
62
+ version: match[3] || null,
63
+ });
64
+ }
65
+ }
66
+ return deps;
67
+ }
68
+
69
+ /**
70
+ * Parse pyproject.toml — extract [project.dependencies] section.
71
+ */
72
+ export function parsePyprojectToml(content) {
73
+ const deps = [];
74
+ // Match dependencies array
75
+ const depsMatch = content.match(/\[project\]\s*[\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
76
+ if (depsMatch) {
77
+ const lines = depsMatch[1].split('\n');
78
+ for (const line of lines) {
79
+ const match = line.match(/["']([a-zA-Z0-9._-]+)(?:\[.*?\])?(?:([<>=!~]+)([\d.]+))?/);
80
+ if (match) {
81
+ deps.push({ name: match[1].toLowerCase(), version: match[3] || null });
82
+ }
83
+ }
84
+ }
85
+
86
+ // Also check [tool.poetry.dependencies]
87
+ const poetryMatch = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?:\[|$)/);
88
+ if (poetryMatch) {
89
+ const lines = poetryMatch[1].split('\n');
90
+ for (const line of lines) {
91
+ const match = line.match(/^([a-zA-Z0-9._-]+)\s*=\s*(?:"([^"]+)"|{.*?version\s*=\s*"([^"]+)")/);
92
+ if (match && match[1] !== 'python') {
93
+ const name = match[1].toLowerCase();
94
+ if (!deps.some(d => d.name === name)) {
95
+ deps.push({ name, version: match[2] || match[3] || null });
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ return deps;
102
+ }
103
+
104
+ /**
105
+ * Parse Cargo.toml — extract [dependencies] section.
106
+ */
107
+ export function parseCargoToml(content) {
108
+ const deps = [];
109
+ const depsMatch = content.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/);
110
+ if (!depsMatch) return deps;
111
+
112
+ for (const line of depsMatch[1].split('\n')) {
113
+ const trimmed = line.trim();
114
+ if (!trimmed || trimmed.startsWith('#')) continue;
115
+ // name = "version" or name = { version = "x.y.z" }
116
+ const simpleMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"/);
117
+ if (simpleMatch) {
118
+ deps.push({ name: simpleMatch[1], version: simpleMatch[2] });
119
+ continue;
120
+ }
121
+ const complexMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*\{.*?version\s*=\s*"([^"]+)"/);
122
+ if (complexMatch) {
123
+ deps.push({ name: complexMatch[1], version: complexMatch[2] });
124
+ }
125
+ }
126
+ return deps;
127
+ }
128
+
129
+ /**
130
+ * Parse pom.xml — extract <dependency> blocks.
131
+ */
132
+ export function parsePomXml(content) {
133
+ const deps = [];
134
+ const depRegex = /<dependency>\s*<groupId>(.*?)<\/groupId>\s*<artifactId>(.*?)<\/artifactId>(?:\s*<version>(.*?)<\/version>)?/g;
135
+ let match;
136
+ while ((match = depRegex.exec(content)) !== null) {
137
+ deps.push({
138
+ name: `${match[1]}:${match[2]}`,
139
+ version: match[3] || null,
140
+ groupId: match[1],
141
+ artifactId: match[2],
142
+ });
143
+ }
144
+ return deps;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Manifest detection and scanning
149
+ // ---------------------------------------------------------------------------
150
+
151
+ const MANIFEST_FILES = [
152
+ { file: 'package.json', ecosystem: 'npm', parser: null }, // npm handled by main scanner
153
+ { file: 'go.mod', ecosystem: 'go', parser: parseGoMod },
154
+ { file: 'go.sum', ecosystem: 'go', parser: null },
155
+ { file: 'requirements.txt', ecosystem: 'pypi', parser: parseRequirementsTxt },
156
+ { file: 'pyproject.toml', ecosystem: 'pypi', parser: parsePyprojectToml },
157
+ { file: 'Cargo.toml', ecosystem: 'cargo', parser: parseCargoToml },
158
+ { file: 'pom.xml', ecosystem: 'maven', parser: parsePomXml },
159
+ ];
160
+
161
+ /**
162
+ * Scan a project directory for manifests and identify crypto dependencies.
163
+ * Returns array of library entries compatible with scanner.mjs output.
164
+ */
165
+ export function scanManifests(projectDir) {
166
+ const results = [];
167
+ const seen = new Set();
168
+
169
+ for (const { file, ecosystem, parser } of MANIFEST_FILES) {
170
+ if (!parser) continue; // npm handled by main scanner
171
+
172
+ const manifestPath = join(projectDir, file);
173
+ if (!existsSync(manifestPath)) continue;
174
+
175
+ let content;
176
+ try { content = readFileSync(manifestPath, 'utf-8'); }
177
+ catch { continue; }
178
+
179
+ const deps = parser(content);
180
+
181
+ for (const dep of deps) {
182
+ const pkg = lookupPackage(dep.name, ecosystem);
183
+ if (pkg && !seen.has(`${ecosystem}:${pkg.name}`)) {
184
+ seen.add(`${ecosystem}:${pkg.name}`);
185
+ results.push({
186
+ name: pkg.name,
187
+ version: dep.version || 'unknown',
188
+ algorithms: pkg.algorithms,
189
+ quantumRisk: pkg.quantumRisk,
190
+ category: pkg.category,
191
+ ecosystem,
192
+ source: file,
193
+ isDeprecated: pkg.isDeprecated || false,
194
+ });
195
+ }
196
+ }
197
+ }
198
+
199
+ return results;
200
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * TLS version scanner — detects deprecated TLS/SSL in config and source files.
3
+ *
4
+ * Scans nginx, Apache, Node.js, Go, Java configs and source files for
5
+ * deprecated TLS versions (SSLv2, SSLv3, TLSv1.0, TLSv1.1).
6
+ * Zero dependencies.
7
+ */
8
+
9
+ import { readFileSync } from 'node:fs';
10
+ import { extname, relative } from 'node:path';
11
+ import { walkProject } from './walker.mjs';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // TLS version risk levels
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const TLS_RISK = {
18
+ 'SSLv2': { risk: 'critical', recommendation: 'Remove immediately — completely broken' },
19
+ 'SSLv3': { risk: 'critical', recommendation: 'Remove immediately — POODLE attack' },
20
+ 'TLSv1': { risk: 'critical', recommendation: 'Remove — deprecated by RFC 8996' },
21
+ 'TLSv1.0': { risk: 'critical', recommendation: 'Remove — deprecated by RFC 8996' },
22
+ 'TLSv1.1': { risk: 'high', recommendation: 'Remove — deprecated by RFC 8996' },
23
+ 'TLSv1.2': { risk: 'none', recommendation: 'Current standard, safe to use' },
24
+ 'TLSv1.3': { risk: 'none', recommendation: 'Best available, recommended' },
25
+ };
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Detection patterns by file type
29
+ // ---------------------------------------------------------------------------
30
+
31
+ const PATTERNS = [
32
+ // Nginx: ssl_protocols directive
33
+ {
34
+ filePatterns: ['nginx.conf', '*.nginx', '*.nginx.conf'],
35
+ extensions: ['.conf'],
36
+ regex: /ssl_protocols\s+([^;]+);/g,
37
+ extract: (match) => {
38
+ const protocols = match[1].split(/\s+/);
39
+ return protocols
40
+ .filter(p => p.match(/^(SSLv[23]|TLSv1(\.[0-3])?)$/))
41
+ .map(p => ({ protocol: p, context: 'nginx ssl_protocols' }));
42
+ },
43
+ },
44
+ // Apache: SSLProtocol directive
45
+ {
46
+ filePatterns: ['httpd.conf', 'apache2.conf', 'ssl.conf', '*.apache.conf'],
47
+ extensions: ['.conf'],
48
+ regex: /SSLProtocol\s+([^\n]+)/g,
49
+ extract: (match) => {
50
+ const parts = match[1].split(/\s+/);
51
+ return parts
52
+ .filter(p => p.match(/^[+-]?(SSLv[23]|TLSv1(\.[0-3])?)$/))
53
+ .filter(p => !p.startsWith('-'))
54
+ .map(p => ({ protocol: p.replace(/^\+/, ''), context: 'Apache SSLProtocol' }));
55
+ },
56
+ },
57
+ // Node.js: tls.createServer minVersion
58
+ {
59
+ extensions: ['.js', '.ts', '.mjs', '.cjs'],
60
+ regex: /minVersion:\s*['"]([^'"]+)['"]/g,
61
+ extract: (match) => {
62
+ const version = match[1];
63
+ return [{ protocol: version, context: 'Node.js TLS minVersion' }];
64
+ },
65
+ },
66
+ // Go: tls.Config MinVersion
67
+ {
68
+ extensions: ['.go'],
69
+ regex: /MinVersion:\s*tls\.(VersionSSL30|VersionTLS1[0-3])/g,
70
+ extract: (match) => {
71
+ const versionMap = {
72
+ 'VersionSSL30': 'SSLv3',
73
+ 'VersionTLS10': 'TLSv1.0',
74
+ 'VersionTLS11': 'TLSv1.1',
75
+ 'VersionTLS12': 'TLSv1.2',
76
+ 'VersionTLS13': 'TLSv1.3',
77
+ };
78
+ const protocol = versionMap[match[1]] || match[1];
79
+ return [{ protocol, context: 'Go tls.Config MinVersion' }];
80
+ },
81
+ },
82
+ // Java: SSLContext.getInstance
83
+ {
84
+ extensions: ['.java', '.kt', '.scala'],
85
+ regex: /SSLContext\.getInstance\s*\(\s*["'](TLS(?:v1(?:\.[0-3])?)?|SSL(?:v[23])?)["']\s*\)/g,
86
+ extract: (match) => {
87
+ return [{ protocol: match[1], context: 'Java SSLContext' }];
88
+ },
89
+ },
90
+ // Docker / env: SSL_MIN_VERSION or similar
91
+ {
92
+ filePatterns: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.env'],
93
+ regex: /(?:SSL_MIN_VERSION|TLS_MIN_VERSION|MIN_TLS_VERSION)\s*[=:]\s*['"]?([^\s'"]+)/gi,
94
+ extract: (match) => {
95
+ return [{ protocol: match[1], context: 'Environment variable' }];
96
+ },
97
+ },
98
+ // Generic: TLS version strings in any config
99
+ {
100
+ extensions: ['.conf', '.cfg', '.ini', '.toml', '.yaml', '.yml', '.json', '.xml'],
101
+ regex: /\b(SSLv[23]|TLSv1\.0|TLSv1\.1)\b/g,
102
+ extract: (match) => {
103
+ return [{ protocol: match[1], context: 'Config file reference' }];
104
+ },
105
+ },
106
+ ];
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Scanner
110
+ // ---------------------------------------------------------------------------
111
+
112
+ /**
113
+ * Scan project for deprecated TLS/SSL versions.
114
+ *
115
+ * @param {string} projectDir - Root directory for relative path calculation
116
+ * @param {string[]|undefined} preFilteredFiles - Pre-walked file list (source+config).
117
+ * If omitted, walks the directory internally for backward compat.
118
+ * @returns {Array<{file: string, line: number, protocol: string, risk: string, recommendation: string}>}
119
+ */
120
+ export function scanTlsConfigs(projectDir, preFilteredFiles) {
121
+ const findings = [];
122
+ let files;
123
+ if (Array.isArray(preFilteredFiles)) {
124
+ files = preFilteredFiles;
125
+ } else {
126
+ const walked = walkProject(projectDir);
127
+ files = [...walked.sourceFiles, ...walked.configFiles];
128
+ }
129
+
130
+ for (const filePath of files) {
131
+ let content;
132
+ try { content = readFileSync(filePath, 'utf-8'); }
133
+ catch { continue; }
134
+
135
+ const ext = extname(filePath).toLowerCase();
136
+ const name = filePath.split('/').pop();
137
+ const relPath = relative(projectDir, filePath);
138
+
139
+ for (const pattern of PATTERNS) {
140
+ // Check if pattern applies to this file
141
+ const extMatch = pattern.extensions && pattern.extensions.includes(ext);
142
+ const nameMatch = pattern.filePatterns && pattern.filePatterns.some(p => {
143
+ if (p.startsWith('*')) return name.endsWith(p.slice(1));
144
+ return name === p;
145
+ });
146
+
147
+ if (!extMatch && !nameMatch) continue;
148
+
149
+ pattern.regex.lastIndex = 0;
150
+ let match;
151
+ while ((match = pattern.regex.exec(content)) !== null) {
152
+ const detected = pattern.extract(match);
153
+ for (const { protocol, context } of detected) {
154
+ const riskInfo = TLS_RISK[protocol];
155
+ if (riskInfo && riskInfo.risk !== 'none') {
156
+ // Find line number
157
+ const lineNum = content.substring(0, match.index).split('\n').length;
158
+ findings.push({
159
+ file: relPath,
160
+ line: lineNum,
161
+ protocol,
162
+ risk: riskInfo.risk,
163
+ recommendation: riskInfo.recommendation,
164
+ context,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return findings;
173
+ }