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.
- package/README.md +97 -10
- package/bin/cryptoserve.mjs +259 -11
- package/lib/algorithm-db.mjs +260 -0
- package/lib/cbom.mjs +298 -0
- package/lib/config.mjs +87 -0
- package/lib/crypto-registry.mjs +161 -0
- package/lib/pqc-engine.mjs +220 -59
- package/lib/scanner-binary.mjs +151 -0
- package/lib/scanner-languages.mjs +242 -0
- package/lib/scanner-manifests.mjs +200 -0
- package/lib/scanner-tls.mjs +173 -0
- package/lib/scanner.mjs +133 -104
- package/lib/walker.mjs +166 -0
- package/package.json +7 -2
|
@@ -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
|
+
}
|