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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized crypto package registry — single source of truth for all ecosystems.
|
|
3
|
+
*
|
|
4
|
+
* Consolidates package metadata previously duplicated across scanner.mjs (npm only,
|
|
5
|
+
* uppercase algorithms) and scanner-manifests.mjs (all ecosystems, lowercase).
|
|
6
|
+
* Algorithms are stored in lowercase, matching algorithm-db.mjs canonical form.
|
|
7
|
+
* Zero dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Known crypto packages per ecosystem
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export const CRYPTO_PACKAGES = {
|
|
15
|
+
// npm uses display-name algorithms for backward compat with scanner.mjs output.
|
|
16
|
+
// The manifest scanner skips npm (parser is null), so only lookupNpmPackage reads this.
|
|
17
|
+
npm: {
|
|
18
|
+
'crypto-js': { category: 'symmetric', algorithms: ['AES', 'DES', '3DES', 'MD5', 'SHA-256', 'SHA-512', 'HMAC'], quantumRisk: 'low' },
|
|
19
|
+
'jsonwebtoken': { category: 'token', algorithms: ['RS256', 'HS256', 'ES256'], quantumRisk: 'high' },
|
|
20
|
+
'jose': { category: 'token', algorithms: ['RS256', 'ES256', 'EdDSA', 'AES-GCM'], quantumRisk: 'high' },
|
|
21
|
+
'node-forge': { category: 'tls', algorithms: ['RSA', 'AES', 'SHA-256', 'HMAC', 'TLS'], quantumRisk: 'high' },
|
|
22
|
+
'tweetnacl': { category: 'asymmetric', algorithms: ['X25519', 'Ed25519', 'XSalsa20'], quantumRisk: 'high' },
|
|
23
|
+
'libsodium-wrappers': { category: 'asymmetric', algorithms: ['X25519', 'Ed25519', 'ChaCha20', 'AES-256-GCM'], quantumRisk: 'high' },
|
|
24
|
+
'@noble/curves': { category: 'asymmetric', algorithms: ['ECDSA', 'Ed25519', 'X25519'], quantumRisk: 'high' },
|
|
25
|
+
'@noble/hashes': { category: 'hash', algorithms: ['SHA-256', 'SHA-512', 'SHA3', 'Blake2'], quantumRisk: 'low' },
|
|
26
|
+
'@noble/post-quantum': { category: 'pqc', algorithms: ['ML-KEM', 'ML-DSA', 'SLH-DSA'], quantumRisk: 'none' },
|
|
27
|
+
'openpgp': { category: 'asymmetric', algorithms: ['RSA', 'ECDSA', 'AES', 'SHA-256'], quantumRisk: 'high' },
|
|
28
|
+
'elliptic': { category: 'asymmetric', algorithms: ['ECDSA', 'ECDHE', 'Ed25519'], quantumRisk: 'high' },
|
|
29
|
+
'secp256k1': { category: 'asymmetric', algorithms: ['ECDSA'], quantumRisk: 'high' },
|
|
30
|
+
'argon2': { category: 'kdf', algorithms: ['Argon2'], quantumRisk: 'none' },
|
|
31
|
+
'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
|
|
32
|
+
'pbkdf2': { category: 'kdf', algorithms: ['PBKDF2'], quantumRisk: 'none' },
|
|
33
|
+
'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
|
|
34
|
+
'bcryptjs': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
|
|
35
|
+
'tls': { category: 'tls', algorithms: ['TLS', 'RSA', 'ECDSA'], quantumRisk: 'high' },
|
|
36
|
+
'ssh2': { category: 'asymmetric', algorithms: ['RSA', 'Ed25519', 'ECDSA', 'AES'], quantumRisk: 'high' },
|
|
37
|
+
'node-rsa': { category: 'asymmetric', algorithms: ['RSA'], quantumRisk: 'high' },
|
|
38
|
+
'jsrsasign': { category: 'asymmetric', algorithms: ['RSA', 'ECDSA', 'SHA-256'], quantumRisk: 'high' },
|
|
39
|
+
'ethers': { category: 'asymmetric', algorithms: ['ECDSA', 'SHA-256'], quantumRisk: 'high' },
|
|
40
|
+
},
|
|
41
|
+
go: {
|
|
42
|
+
'crypto/aes': { category: 'encryption', algorithms: ['aes'], quantumRisk: 'none' },
|
|
43
|
+
'crypto/des': { category: 'encryption', algorithms: ['des', '3des'], quantumRisk: 'critical', isDeprecated: true },
|
|
44
|
+
'crypto/rsa': { category: 'encryption', algorithms: ['rsa'], quantumRisk: 'high' },
|
|
45
|
+
'crypto/ecdsa': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
|
|
46
|
+
'crypto/ed25519': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
|
|
47
|
+
'crypto/ecdh': { category: 'key_exchange', algorithms: ['ecdh', 'x25519'], quantumRisk: 'high' },
|
|
48
|
+
'crypto/sha256': { category: 'hashing', algorithms: ['sha256'], quantumRisk: 'low' },
|
|
49
|
+
'crypto/sha512': { category: 'hashing', algorithms: ['sha512'], quantumRisk: 'low' },
|
|
50
|
+
'crypto/sha1': { category: 'hashing', algorithms: ['sha1'], quantumRisk: 'critical', isDeprecated: true },
|
|
51
|
+
'crypto/md5': { category: 'hashing', algorithms: ['md5'], quantumRisk: 'critical', isDeprecated: true },
|
|
52
|
+
'crypto/hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
|
|
53
|
+
'crypto/tls': { category: 'protocol', algorithms: ['tls'], quantumRisk: 'high' },
|
|
54
|
+
'golang.org/x/crypto': { category: 'general', algorithms: ['chacha20-poly1305', 'argon2', 'bcrypt', 'scrypt', 'hkdf'], quantumRisk: 'none' },
|
|
55
|
+
'golang.org/x/crypto/chacha20poly1305': { category: 'encryption', algorithms: ['chacha20-poly1305'], quantumRisk: 'none' },
|
|
56
|
+
'golang.org/x/crypto/argon2': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
|
|
57
|
+
'golang.org/x/crypto/bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
|
|
58
|
+
'golang.org/x/crypto/scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
|
|
59
|
+
'github.com/cloudflare/circl': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'x25519'], quantumRisk: 'none' },
|
|
60
|
+
},
|
|
61
|
+
pypi: {
|
|
62
|
+
'cryptography': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa', 'ed25519', 'x25519', 'sha256'], quantumRisk: 'high' },
|
|
63
|
+
'pycryptodome': { category: 'general', algorithms: ['aes', 'des', 'rsa', 'sha256', 'md5'], quantumRisk: 'high' },
|
|
64
|
+
'pycryptodomex': { category: 'general', algorithms: ['aes', 'des', 'rsa', 'sha256', 'md5'], quantumRisk: 'high' },
|
|
65
|
+
'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
|
|
66
|
+
'argon2-cffi': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
|
|
67
|
+
'pynacl': { category: 'general', algorithms: ['xchacha20', 'ed25519', 'x25519'], quantumRisk: 'high' },
|
|
68
|
+
'pyopenssl': { category: 'general', algorithms: ['rsa', 'aes', 'sha256', 'tls'], quantumRisk: 'high' },
|
|
69
|
+
'paramiko': { category: 'asymmetric', algorithms: ['rsa', 'ed25519', 'ecdsa', 'aes'], quantumRisk: 'high' },
|
|
70
|
+
'pyjwt': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'hmac'], quantumRisk: 'high' },
|
|
71
|
+
'hashlib': { category: 'hashing', algorithms: ['sha256', 'sha512', 'md5', 'sha1'], quantumRisk: 'low' },
|
|
72
|
+
'hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
|
|
73
|
+
'passlib': { category: 'kdf', algorithms: ['bcrypt', 'argon2', 'pbkdf2', 'scrypt'], quantumRisk: 'none' },
|
|
74
|
+
'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
|
|
75
|
+
'ecdsa': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
|
|
76
|
+
'rsa': { category: 'asymmetric', algorithms: ['rsa'], quantumRisk: 'high' },
|
|
77
|
+
'liboqs-python': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'sphincs'], quantumRisk: 'none' },
|
|
78
|
+
'pqcrypto': { category: 'pqc', algorithms: ['kyber', 'dilithium'], quantumRisk: 'none' },
|
|
79
|
+
'pyca-cryptography': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa'], quantumRisk: 'high' },
|
|
80
|
+
},
|
|
81
|
+
cargo: {
|
|
82
|
+
'aes-gcm': { category: 'encryption', algorithms: ['aes-gcm'], quantumRisk: 'none' },
|
|
83
|
+
'aes': { category: 'encryption', algorithms: ['aes'], quantumRisk: 'none' },
|
|
84
|
+
'chacha20poly1305': { category: 'encryption', algorithms: ['chacha20-poly1305'], quantumRisk: 'none' },
|
|
85
|
+
'chacha20': { category: 'encryption', algorithms: ['chacha20'], quantumRisk: 'none' },
|
|
86
|
+
'rsa': { category: 'encryption', algorithms: ['rsa'], quantumRisk: 'high' },
|
|
87
|
+
'ed25519-dalek': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
|
|
88
|
+
'ed25519': { category: 'signing', algorithms: ['ed25519'], quantumRisk: 'high' },
|
|
89
|
+
'ring': { category: 'general', algorithms: ['aes-gcm', 'sha256', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
|
|
90
|
+
'pqcrypto': { category: 'pqc', algorithms: ['kyber', 'dilithium', 'sphincs'], quantumRisk: 'none' },
|
|
91
|
+
'sha2': { category: 'hashing', algorithms: ['sha256', 'sha512'], quantumRisk: 'low' },
|
|
92
|
+
'sha1': { category: 'hashing', algorithms: ['sha1'], quantumRisk: 'critical', isDeprecated: true },
|
|
93
|
+
'md-5': { category: 'hashing', algorithms: ['md5'], quantumRisk: 'critical', isDeprecated: true },
|
|
94
|
+
'blake2': { category: 'hashing', algorithms: ['blake2b'], quantumRisk: 'low' },
|
|
95
|
+
'argon2': { category: 'kdf', algorithms: ['argon2'], quantumRisk: 'none' },
|
|
96
|
+
'bcrypt': { category: 'kdf', algorithms: ['bcrypt'], quantumRisk: 'none' },
|
|
97
|
+
'scrypt': { category: 'kdf', algorithms: ['scrypt'], quantumRisk: 'none' },
|
|
98
|
+
'pbkdf2': { category: 'kdf', algorithms: ['pbkdf2'], quantumRisk: 'none' },
|
|
99
|
+
'x25519-dalek': { category: 'key_exchange', algorithms: ['x25519'], quantumRisk: 'high' },
|
|
100
|
+
'p256': { category: 'signing', algorithms: ['ecdsa'], quantumRisk: 'high' },
|
|
101
|
+
'hmac': { category: 'mac', algorithms: ['hmac'], quantumRisk: 'low' },
|
|
102
|
+
'hkdf': { category: 'kdf', algorithms: ['hkdf'], quantumRisk: 'none' },
|
|
103
|
+
'rustls': { category: 'protocol', algorithms: ['tls', 'aes-gcm', 'chacha20-poly1305'], quantumRisk: 'high' },
|
|
104
|
+
},
|
|
105
|
+
maven: {
|
|
106
|
+
'org.bouncycastle': { category: 'general', algorithms: ['aes', 'rsa', 'ecdsa', 'ed25519', 'sha256'], quantumRisk: 'high' },
|
|
107
|
+
'com.google.crypto.tink': { category: 'general', algorithms: ['aes-gcm', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
|
|
108
|
+
'io.jsonwebtoken': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'hmac'], quantumRisk: 'high' },
|
|
109
|
+
'com.nimbusds': { category: 'signing', algorithms: ['rsa', 'ecdsa', 'ed25519'], quantumRisk: 'high' },
|
|
110
|
+
'org.signal': { category: 'asymmetric', algorithms: ['x25519', 'ed25519', 'aes-gcm'], quantumRisk: 'high' },
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Lookup helpers
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Look up a dependency in the crypto packages database.
|
|
120
|
+
* For Go imports, also checks subpackage paths.
|
|
121
|
+
* For Maven, checks groupId prefix.
|
|
122
|
+
*/
|
|
123
|
+
export function lookupPackage(name, ecosystem) {
|
|
124
|
+
const db = CRYPTO_PACKAGES[ecosystem];
|
|
125
|
+
if (!db) return null;
|
|
126
|
+
|
|
127
|
+
// Direct match
|
|
128
|
+
if (db[name]) return { ...db[name], name };
|
|
129
|
+
|
|
130
|
+
// For Go, check if it's a subpackage of a known package
|
|
131
|
+
if (ecosystem === 'go') {
|
|
132
|
+
for (const [pkgName, info] of Object.entries(db)) {
|
|
133
|
+
if (name.startsWith(pkgName + '/') || name === pkgName) {
|
|
134
|
+
return { ...info, name: pkgName };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// For Maven, check groupId
|
|
140
|
+
if (ecosystem === 'maven') {
|
|
141
|
+
const groupId = name.split(':')[0];
|
|
142
|
+
for (const [pkgName, info] of Object.entries(db)) {
|
|
143
|
+
if (groupId.startsWith(pkgName)) {
|
|
144
|
+
return { ...info, name: pkgName };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Shorthand for npm package lookup.
|
|
154
|
+
* Returns the package info directly — npm section already uses display-name
|
|
155
|
+
* algorithms and scanner.mjs-compatible categories.
|
|
156
|
+
*/
|
|
157
|
+
export function lookupNpmPackage(name) {
|
|
158
|
+
const pkg = CRYPTO_PACKAGES.npm[name];
|
|
159
|
+
if (!pkg) return null;
|
|
160
|
+
return { algorithms: pkg.algorithms, quantumRisk: pkg.quantumRisk, category: pkg.category };
|
|
161
|
+
}
|
package/lib/init.mjs
CHANGED
|
@@ -142,13 +142,13 @@ function protectAider(projectDir, result) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
function addInstructions(filePath, result) {
|
|
145
|
-
|
|
145
|
+
const existed = existsSync(filePath);
|
|
146
|
+
let content = existed ? readFileSync(filePath, 'utf-8') : '';
|
|
146
147
|
if (content.includes(MARKER)) return;
|
|
147
148
|
|
|
148
149
|
content += buildSecretlessInstructions();
|
|
149
150
|
writeFileSync(filePath, content);
|
|
150
151
|
|
|
151
|
-
const existed = existsSync(filePath);
|
|
152
152
|
if (existed) {
|
|
153
153
|
result.filesModified.push(filePath);
|
|
154
154
|
} else {
|
package/lib/pqc-engine.mjs
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Provides air-gapped quantum readiness analysis with zero dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { lookupAlgorithm, classifyFromDb } from './algorithm-db.mjs';
|
|
9
|
+
|
|
8
10
|
// ---------------------------------------------------------------------------
|
|
9
11
|
// Embedded intelligence data
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
@@ -170,63 +172,101 @@ export const COMPLIANCE_FRAMEWORKS = {
|
|
|
170
172
|
};
|
|
171
173
|
|
|
172
174
|
// ---------------------------------------------------------------------------
|
|
173
|
-
// Algorithm classification
|
|
175
|
+
// Algorithm classification
|
|
174
176
|
// ---------------------------------------------------------------------------
|
|
175
177
|
|
|
176
|
-
// [pattern, timelineKey
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
['
|
|
181
|
-
['
|
|
182
|
-
['
|
|
183
|
-
['
|
|
184
|
-
['
|
|
185
|
-
['
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
['
|
|
189
|
-
['
|
|
190
|
-
['
|
|
191
|
-
['
|
|
192
|
-
['
|
|
193
|
-
['
|
|
194
|
-
['
|
|
195
|
-
['
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
['
|
|
199
|
-
['
|
|
200
|
-
['
|
|
201
|
-
['
|
|
178
|
+
// Timeline mapping: [pattern, timelineKey] — first match wins.
|
|
179
|
+
// Category is now derived from algorithm-db.mjs's classifyFromDb().
|
|
180
|
+
const TIMELINE_RULES = [
|
|
181
|
+
// PQC
|
|
182
|
+
['Kyber', 'pqc'],
|
|
183
|
+
['ML-KEM', 'pqc'],
|
|
184
|
+
['Dilithium', 'pqc'],
|
|
185
|
+
['ML-DSA', 'pqc'],
|
|
186
|
+
['Falcon', 'pqc'],
|
|
187
|
+
['SPHINCS', 'pqc'],
|
|
188
|
+
['SLH-DSA', 'pqc'],
|
|
189
|
+
// Asymmetric
|
|
190
|
+
['RSA', 'rsa_2048'],
|
|
191
|
+
['ECDSA', 'ecdsa_p256'],
|
|
192
|
+
['ECDHE', 'ecdsa_p256'],
|
|
193
|
+
['ECC', 'ecdsa_p256'],
|
|
194
|
+
['Ed25519', 'ed25519'],
|
|
195
|
+
['EdDSA', 'ed25519'],
|
|
196
|
+
['Curve25519','x25519'],
|
|
197
|
+
['X25519', 'x25519'],
|
|
198
|
+
['DH', 'dh_2048'],
|
|
199
|
+
// Symmetric
|
|
200
|
+
['AES', 'aes_256'],
|
|
201
|
+
['ChaCha20', 'chacha20'],
|
|
202
|
+
['3DES', 'aes_128'],
|
|
203
|
+
['DES', 'aes_128'],
|
|
204
|
+
['XSalsa20', 'chacha20'],
|
|
202
205
|
// Hashing
|
|
203
|
-
['SHA-256', 'sha_256'
|
|
204
|
-
['SHA-512', 'sha_256'
|
|
205
|
-
['SHA-1', 'sha_256'
|
|
206
|
-
['SHA3', 'sha_256'
|
|
207
|
-
['Blake2', 'sha_256'
|
|
208
|
-
['MD5', 'sha_256'
|
|
209
|
-
//
|
|
210
|
-
['HMAC', 'sha_256'
|
|
211
|
-
['bcrypt', null
|
|
212
|
-
['Argon2', null
|
|
213
|
-
['PBKDF2', null
|
|
214
|
-
['scrypt', null
|
|
215
|
-
['CSPRNG', null
|
|
216
|
-
['Poly1305', null
|
|
206
|
+
['SHA-256', 'sha_256'],
|
|
207
|
+
['SHA-512', 'sha_256'],
|
|
208
|
+
['SHA-1', 'sha_256'],
|
|
209
|
+
['SHA3', 'sha_256'],
|
|
210
|
+
['Blake2', 'sha_256'],
|
|
211
|
+
['MD5', 'sha_256'],
|
|
212
|
+
// MAC / KDF
|
|
213
|
+
['HMAC', 'sha_256'],
|
|
214
|
+
['bcrypt', null],
|
|
215
|
+
['Argon2', null],
|
|
216
|
+
['PBKDF2', null],
|
|
217
|
+
['scrypt', null],
|
|
218
|
+
['CSPRNG', null],
|
|
219
|
+
['Poly1305', null],
|
|
217
220
|
// Token / TLS wrappers
|
|
218
|
-
['TLS', 'rsa_2048'
|
|
219
|
-
['JWS', 'rsa_2048'
|
|
220
|
-
['JWE', 'rsa_2048'
|
|
221
|
-
['JWK', 'rsa_2048'
|
|
222
|
-
['RS256', 'rsa_2048'
|
|
223
|
-
['ES256', 'ecdsa_p256'
|
|
224
|
-
['HS256', 'sha_256'
|
|
221
|
+
['TLS', 'rsa_2048'],
|
|
222
|
+
['JWS', 'rsa_2048'],
|
|
223
|
+
['JWE', 'rsa_2048'],
|
|
224
|
+
['JWK', 'rsa_2048'],
|
|
225
|
+
['RS256', 'rsa_2048'],
|
|
226
|
+
['ES256', 'ecdsa_p256'],
|
|
227
|
+
['HS256', 'sha_256'],
|
|
225
228
|
];
|
|
226
229
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Map algorithm-db category + quantumRisk to PQC engine classification category.
|
|
232
|
+
* This derives the quantum vulnerability class from algorithm-db metadata.
|
|
233
|
+
*/
|
|
234
|
+
function dbCategoryToPqcCategory(dbEntry) {
|
|
235
|
+
if (!dbEntry) return null;
|
|
236
|
+
const { category, quantumRisk } = dbEntry;
|
|
237
|
+
|
|
238
|
+
// PQC algorithms: signing/key_exchange with no quantum risk
|
|
239
|
+
// (classical asymmetric has 'high' risk, PQC has 'none')
|
|
240
|
+
if (quantumRisk === 'none' && (category === 'signing' || category === 'key_exchange')) {
|
|
241
|
+
return 'pqc';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Signing and key exchange → asymmetric (quantum-vulnerable via Shor's)
|
|
245
|
+
if (category === 'signing' || category === 'key_exchange') return 'asymmetric';
|
|
246
|
+
|
|
247
|
+
// Encryption with high quantum risk → asymmetric (RSA encryption)
|
|
248
|
+
if (category === 'encryption' && quantumRisk === 'high') return 'asymmetric';
|
|
249
|
+
|
|
250
|
+
// Encryption with none/low risk → symmetric
|
|
251
|
+
if (category === 'encryption') return 'symmetric';
|
|
252
|
+
|
|
253
|
+
// Hashing
|
|
254
|
+
if (category === 'hashing') return 'hash';
|
|
255
|
+
|
|
256
|
+
// MAC
|
|
257
|
+
if (category === 'mac') return 'hash';
|
|
258
|
+
|
|
259
|
+
// KDF
|
|
260
|
+
if (category === 'kdf') return 'kdf';
|
|
261
|
+
|
|
262
|
+
// Protocol (TLS) → asymmetric (involves key exchange)
|
|
263
|
+
if (category === 'protocol') return 'asymmetric';
|
|
264
|
+
|
|
265
|
+
// Random
|
|
266
|
+
if (category === 'random') return 'random';
|
|
267
|
+
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
230
270
|
|
|
231
271
|
function classifyAlgorithms(libraries) {
|
|
232
272
|
const seen = new Set();
|
|
@@ -237,20 +277,33 @@ function classifyAlgorithms(libraries) {
|
|
|
237
277
|
if (seen.has(algoName)) continue;
|
|
238
278
|
seen.add(algoName);
|
|
239
279
|
|
|
240
|
-
|
|
280
|
+
// Look up timeline key via pattern matching
|
|
281
|
+
let timelineKey = null;
|
|
241
282
|
const upper = algoName.toUpperCase();
|
|
242
|
-
|
|
243
|
-
for (const [pattern, timelineKey, category] of ALGO_CLASSIFICATION_RULES) {
|
|
283
|
+
for (const [pattern, tKey] of TIMELINE_RULES) {
|
|
244
284
|
if (upper.includes(pattern.toUpperCase())) {
|
|
245
|
-
|
|
246
|
-
matched = true;
|
|
285
|
+
timelineKey = tKey;
|
|
247
286
|
break;
|
|
248
287
|
}
|
|
249
288
|
}
|
|
250
289
|
|
|
251
|
-
|
|
252
|
-
|
|
290
|
+
// Derive category from algorithm-db
|
|
291
|
+
const dbEntry = classifyFromDb(algoName);
|
|
292
|
+
let category = dbEntry ? dbCategoryToPqcCategory(dbEntry) : null;
|
|
293
|
+
|
|
294
|
+
// Fallback: if algorithm-db doesn't know this algo, infer from timeline
|
|
295
|
+
if (!category) {
|
|
296
|
+
if (timelineKey === 'pqc') category = 'pqc';
|
|
297
|
+
else if (timelineKey && timelineKey in QUANTUM_THREAT_TIMELINE) {
|
|
298
|
+
// Timeline keys like rsa_2048, ecdsa_p256 → asymmetric
|
|
299
|
+
const t = QUANTUM_THREAT_TIMELINE[timelineKey];
|
|
300
|
+
category = t.median <= 30 ? 'asymmetric' : 'symmetric';
|
|
301
|
+
} else {
|
|
302
|
+
category = 'unknown';
|
|
303
|
+
}
|
|
253
304
|
}
|
|
305
|
+
|
|
306
|
+
results.push({ algo: algoName, timelineKey, category });
|
|
254
307
|
}
|
|
255
308
|
}
|
|
256
309
|
|
|
@@ -478,7 +531,7 @@ function calculateQuantumScore(libraries, classifications) {
|
|
|
478
531
|
// Score by individual algorithm classifications, not library count.
|
|
479
532
|
// A project with 5 symmetric + 1 asymmetric algorithm is mostly ready, not 0%.
|
|
480
533
|
const safe = classifications.filter(
|
|
481
|
-
c => c.category !== 'asymmetric'
|
|
534
|
+
c => c.category !== 'asymmetric'
|
|
482
535
|
).length;
|
|
483
536
|
const vulnerable = classifications.filter(
|
|
484
537
|
c => c.category === 'asymmetric'
|
|
@@ -605,7 +658,107 @@ function buildThreatTimelines(classifications) {
|
|
|
605
658
|
// Main entry point
|
|
606
659
|
// ---------------------------------------------------------------------------
|
|
607
660
|
|
|
608
|
-
|
|
661
|
+
// ---------------------------------------------------------------------------
|
|
662
|
+
// Confidence calculation
|
|
663
|
+
// ---------------------------------------------------------------------------
|
|
664
|
+
|
|
665
|
+
function calculateConfidence(libraries, classifications, scanMeta = {}) {
|
|
666
|
+
const algorithmsFound = classifications.length;
|
|
667
|
+
const languagesScanned = scanMeta.languagesDetected?.length || 0;
|
|
668
|
+
const filesScanned = scanMeta.filesScanned || 0;
|
|
669
|
+
const manifestsFound = scanMeta.manifestsFound?.length || 0;
|
|
670
|
+
|
|
671
|
+
let score = 0;
|
|
672
|
+
|
|
673
|
+
// Algorithms contribute up to 40 points
|
|
674
|
+
if (algorithmsFound >= 10) score += 40;
|
|
675
|
+
else if (algorithmsFound >= 5) score += 30;
|
|
676
|
+
else if (algorithmsFound >= 3) score += 20;
|
|
677
|
+
else if (algorithmsFound >= 1) score += 10;
|
|
678
|
+
|
|
679
|
+
// Manifests contribute up to 25 points
|
|
680
|
+
if (manifestsFound >= 3) score += 25;
|
|
681
|
+
else if (manifestsFound >= 2) score += 20;
|
|
682
|
+
else if (manifestsFound >= 1) score += 15;
|
|
683
|
+
|
|
684
|
+
// Files scanned contribute up to 20 points
|
|
685
|
+
if (filesScanned >= 100) score += 20;
|
|
686
|
+
else if (filesScanned >= 50) score += 15;
|
|
687
|
+
else if (filesScanned >= 20) score += 10;
|
|
688
|
+
else if (filesScanned >= 5) score += 5;
|
|
689
|
+
|
|
690
|
+
// Languages contribute up to 15 points
|
|
691
|
+
if (languagesScanned >= 3) score += 15;
|
|
692
|
+
else if (languagesScanned >= 2) score += 10;
|
|
693
|
+
else if (languagesScanned >= 1) score += 5;
|
|
694
|
+
|
|
695
|
+
let level;
|
|
696
|
+
if (score >= 70) level = 'high';
|
|
697
|
+
else if (score >= 40) level = 'medium';
|
|
698
|
+
else level = 'low';
|
|
699
|
+
|
|
700
|
+
const parts = [];
|
|
701
|
+
if (algorithmsFound > 0) parts.push(`${algorithmsFound} algorithms found`);
|
|
702
|
+
if (languagesScanned > 0) parts.push(`${languagesScanned} languages`);
|
|
703
|
+
if (filesScanned > 0) parts.push(`${filesScanned} files`);
|
|
704
|
+
if (manifestsFound > 0) parts.push(`${manifestsFound} manifests`);
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
level,
|
|
708
|
+
score,
|
|
709
|
+
reason: parts.join(', ') || 'no data scanned',
|
|
710
|
+
algorithmsFound,
|
|
711
|
+
languagesScanned,
|
|
712
|
+
filesScanned,
|
|
713
|
+
manifestsFound,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ---------------------------------------------------------------------------
|
|
718
|
+
// Migration urgency mapping
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
|
|
721
|
+
function getMigrationUrgency(riskLevel) {
|
|
722
|
+
switch (riskLevel) {
|
|
723
|
+
case 'critical': return 'immediate';
|
|
724
|
+
case 'high': return 'high';
|
|
725
|
+
case 'medium': return 'medium';
|
|
726
|
+
case 'low': return 'low';
|
|
727
|
+
default: return 'none';
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
// Risk breakdown
|
|
733
|
+
// ---------------------------------------------------------------------------
|
|
734
|
+
|
|
735
|
+
function getRiskBreakdown(classifications) {
|
|
736
|
+
const breakdown = { critical: 0, high: 0, medium: 0, low: 0, none: 0 };
|
|
737
|
+
|
|
738
|
+
for (const c of classifications) {
|
|
739
|
+
// Use algorithm-db for per-algorithm risk if available
|
|
740
|
+
const dbEntry = lookupAlgorithm(c.algo);
|
|
741
|
+
if (dbEntry) {
|
|
742
|
+
const risk = dbEntry.quantumRisk || 'none';
|
|
743
|
+
if (risk in breakdown) breakdown[risk]++;
|
|
744
|
+
else breakdown.none++;
|
|
745
|
+
} else if (c.category === 'pqc') {
|
|
746
|
+
breakdown.none++;
|
|
747
|
+
} else if (c.category === 'asymmetric') {
|
|
748
|
+
breakdown.high++;
|
|
749
|
+
} else {
|
|
750
|
+
breakdown.low++;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return breakdown;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ---------------------------------------------------------------------------
|
|
758
|
+
// Main entry point
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
|
|
761
|
+
export function analyzeOffline(libraries, dataProfile = null, scanMeta = {}) {
|
|
609
762
|
const profileKey = dataProfile || 'general';
|
|
610
763
|
const profile = DATA_PROFILES[profileKey] || DATA_PROFILES.general;
|
|
611
764
|
|
|
@@ -622,6 +775,11 @@ export function analyzeOffline(libraries, dataProfile = null) {
|
|
|
622
775
|
const nextSteps = generateNextSteps(urgency, migrationPlan, sndl);
|
|
623
776
|
const threatTimelines = buildThreatTimelines(classifications);
|
|
624
777
|
|
|
778
|
+
// New in v0.2.0
|
|
779
|
+
const confidence = calculateConfidence(libraries, classifications, scanMeta);
|
|
780
|
+
const migrationUrgency = getMigrationUrgency(urgency);
|
|
781
|
+
const riskBreakdown = getRiskBreakdown(classifications);
|
|
782
|
+
|
|
625
783
|
return {
|
|
626
784
|
generatedAt: new Date().toISOString(),
|
|
627
785
|
analysisMode: 'offline',
|
|
@@ -630,7 +788,10 @@ export function analyzeOffline(libraries, dataProfile = null) {
|
|
|
630
788
|
signatureRecommendations: sigRecs,
|
|
631
789
|
migrationPlan,
|
|
632
790
|
overallUrgency: urgency,
|
|
791
|
+
migrationUrgency,
|
|
633
792
|
quantumReadinessScore: quantumScore,
|
|
793
|
+
confidence,
|
|
794
|
+
riskBreakdown,
|
|
634
795
|
keyFindings: findings,
|
|
635
796
|
nextSteps,
|
|
636
797
|
complianceReferences: complianceRefs,
|