cryptoserve 0.1.3 → 0.2.1
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/client.mjs +3 -3
- package/lib/config.mjs +87 -0
- package/lib/crypto-registry.mjs +161 -0
- package/lib/init.mjs +2 -2
- package/lib/pqc-engine.mjs +221 -60
- 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 +134 -104
- package/lib/walker.mjs +166 -0
- package/package.json +7 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary crypto signature scanner.
|
|
3
|
+
*
|
|
4
|
+
* Detects compiled crypto by searching for known byte patterns
|
|
5
|
+
* (S-boxes, round constants, initial hash values) in binary files.
|
|
6
|
+
* Ported from backend/app/core/binary_scanner.py.
|
|
7
|
+
* Zero dependencies — uses only node:fs and node:path.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
11
|
+
import { join, extname, basename } from 'node:path';
|
|
12
|
+
import { walkProject } from './walker.mjs';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Binary signatures — byte patterns for compiled crypto
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export const BINARY_SIGNATURES = [
|
|
19
|
+
{
|
|
20
|
+
name: 'AES S-box',
|
|
21
|
+
algorithm: 'aes',
|
|
22
|
+
severity: 'info',
|
|
23
|
+
bytes: Buffer.from([0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76]),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'AES round constant',
|
|
27
|
+
algorithm: 'aes',
|
|
28
|
+
severity: 'info',
|
|
29
|
+
bytes: Buffer.from([0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'DES initial permutation',
|
|
33
|
+
algorithm: 'des',
|
|
34
|
+
severity: 'high',
|
|
35
|
+
bytes: Buffer.from([58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4]),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'DES S-box 1',
|
|
39
|
+
algorithm: 'des',
|
|
40
|
+
severity: 'high',
|
|
41
|
+
bytes: Buffer.from([14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7]),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'SHA-256 initial values',
|
|
45
|
+
algorithm: 'sha256',
|
|
46
|
+
severity: 'info',
|
|
47
|
+
bytes: Buffer.from([0x6A, 0x09, 0xE6, 0x67, 0xBB, 0x67, 0xAE, 0x85, 0x3C, 0x6E, 0xF3, 0x72, 0xA5, 0x4F, 0xF5, 0x3A]),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'SHA-1 initial values',
|
|
51
|
+
algorithm: 'sha1',
|
|
52
|
+
severity: 'high',
|
|
53
|
+
bytes: Buffer.from([0x67, 0x45, 0x23, 0x01, 0xEF, 0xCD, 0xAB, 0x89, 0x98, 0xBA, 0xDC, 0xFE, 0x10, 0x32, 0x54, 0x76]),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'MD5 initial values',
|
|
57
|
+
algorithm: 'md5',
|
|
58
|
+
severity: 'critical',
|
|
59
|
+
bytes: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10]),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'ChaCha20 sigma constant',
|
|
63
|
+
algorithm: 'chacha20',
|
|
64
|
+
severity: 'info',
|
|
65
|
+
bytes: Buffer.from('expand 32-byte k'),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Blowfish P-array',
|
|
69
|
+
algorithm: 'blowfish',
|
|
70
|
+
severity: 'high',
|
|
71
|
+
bytes: Buffer.from([0x24, 0x3F, 0x6A, 0x88, 0x85, 0xA3, 0x08, 0xD3, 0x13, 0x19, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x44]),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'RSA public exponent 65537',
|
|
75
|
+
algorithm: 'rsa',
|
|
76
|
+
severity: 'info',
|
|
77
|
+
bytes: Buffer.from([0x01, 0x00, 0x01]),
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Scanner functions
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Scan a single binary file for crypto byte patterns.
|
|
89
|
+
* Returns array of matches: [{ name, algorithm, severity, offset }]
|
|
90
|
+
*/
|
|
91
|
+
export function scanBinary(filePath) {
|
|
92
|
+
let buf;
|
|
93
|
+
try {
|
|
94
|
+
const size = statSync(filePath).size;
|
|
95
|
+
if (size > MAX_FILE_SIZE) {
|
|
96
|
+
const fd = openSync(filePath, 'r');
|
|
97
|
+
buf = Buffer.alloc(MAX_FILE_SIZE);
|
|
98
|
+
readSync(fd, buf, 0, MAX_FILE_SIZE, 0);
|
|
99
|
+
closeSync(fd);
|
|
100
|
+
} else {
|
|
101
|
+
buf = readFileSync(filePath);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const matches = [];
|
|
108
|
+
for (const sig of BINARY_SIGNATURES) {
|
|
109
|
+
const offset = buf.indexOf(sig.bytes);
|
|
110
|
+
if (offset !== -1) {
|
|
111
|
+
matches.push({
|
|
112
|
+
name: sig.name,
|
|
113
|
+
algorithm: sig.algorithm,
|
|
114
|
+
severity: sig.severity,
|
|
115
|
+
offset,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return matches;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Scan binary files for crypto byte signatures.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} projectDir - Root directory for relative path calculation
|
|
126
|
+
* @param {string[]|undefined} preFilteredFiles - Pre-walked binary file list.
|
|
127
|
+
* If omitted, walks the directory internally for backward compat.
|
|
128
|
+
* @returns {Array<{name: string, algorithm: string, severity: string, offset: number, file: string}>}
|
|
129
|
+
*/
|
|
130
|
+
export function scanBinaries(projectDir, preFilteredFiles) {
|
|
131
|
+
const results = [];
|
|
132
|
+
let files;
|
|
133
|
+
if (Array.isArray(preFilteredFiles)) {
|
|
134
|
+
files = preFilteredFiles;
|
|
135
|
+
} else {
|
|
136
|
+
const walked = walkProject(projectDir);
|
|
137
|
+
files = walked.binaryFiles;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const filePath of files) {
|
|
141
|
+
const matches = scanBinary(filePath);
|
|
142
|
+
for (const match of matches) {
|
|
143
|
+
results.push({
|
|
144
|
+
...match,
|
|
145
|
+
file: filePath.replace(projectDir + '/', ''),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
@@ -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
|
+
}
|