cryptoserve 0.1.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 +183 -0
- package/bin/cryptoserve.mjs +812 -0
- package/lib/cli-style.mjs +217 -0
- package/lib/client.mjs +138 -0
- package/lib/context-resolver.mjs +339 -0
- package/lib/credentials.mjs +67 -0
- package/lib/init.mjs +241 -0
- package/lib/keychain.mjs +303 -0
- package/lib/local-crypto.mjs +218 -0
- package/lib/pqc-engine.mjs +636 -0
- package/lib/scanner.mjs +323 -0
- package/lib/vault.mjs +242 -0
- package/package.json +36 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline PQC analysis engine.
|
|
3
|
+
*
|
|
4
|
+
* Port of sdk/python/cryptoserve/_pqc_engine.py — same data, same logic.
|
|
5
|
+
* Provides air-gapped quantum readiness analysis with zero dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Embedded intelligence data
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export const QUANTUM_THREAT_TIMELINE = {
|
|
13
|
+
rsa_2048: { min: 10, median: 15, max: 25 },
|
|
14
|
+
rsa_4096: { min: 15, median: 20, max: 30 },
|
|
15
|
+
ecdsa_p256: { min: 10, median: 15, max: 25 },
|
|
16
|
+
ecdsa_p384: { min: 12, median: 17, max: 27 },
|
|
17
|
+
ed25519: { min: 10, median: 15, max: 25 },
|
|
18
|
+
x25519: { min: 10, median: 15, max: 25 },
|
|
19
|
+
dh_2048: { min: 10, median: 15, max: 25 },
|
|
20
|
+
aes_128: { min: 15, median: 25, max: 50 },
|
|
21
|
+
aes_256: { min: 30, median: 50, max: 100 },
|
|
22
|
+
sha_256: { min: 30, median: 50, max: 100 },
|
|
23
|
+
chacha20: { min: 30, median: 50, max: 100 },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const DATA_PROFILES = {
|
|
27
|
+
national_security: {
|
|
28
|
+
name: 'National Security Data',
|
|
29
|
+
lifespanYears: 75,
|
|
30
|
+
urgency: 'critical',
|
|
31
|
+
cryptoNeeds: ['kem', 'signature'],
|
|
32
|
+
},
|
|
33
|
+
healthcare: {
|
|
34
|
+
name: 'Healthcare Records',
|
|
35
|
+
lifespanYears: 100,
|
|
36
|
+
urgency: 'critical',
|
|
37
|
+
cryptoNeeds: ['kem'],
|
|
38
|
+
},
|
|
39
|
+
financial: {
|
|
40
|
+
name: 'Long-term Financial Data',
|
|
41
|
+
lifespanYears: 25,
|
|
42
|
+
urgency: 'high',
|
|
43
|
+
cryptoNeeds: ['kem', 'signature'],
|
|
44
|
+
},
|
|
45
|
+
intellectual_property: {
|
|
46
|
+
name: 'Intellectual Property',
|
|
47
|
+
lifespanYears: 20,
|
|
48
|
+
urgency: 'high',
|
|
49
|
+
cryptoNeeds: ['kem'],
|
|
50
|
+
},
|
|
51
|
+
legal: {
|
|
52
|
+
name: 'Legal Documents',
|
|
53
|
+
lifespanYears: 30,
|
|
54
|
+
urgency: 'high',
|
|
55
|
+
cryptoNeeds: ['kem', 'signature'],
|
|
56
|
+
},
|
|
57
|
+
general: {
|
|
58
|
+
name: 'Personal Data / General',
|
|
59
|
+
lifespanYears: 10,
|
|
60
|
+
urgency: 'medium',
|
|
61
|
+
cryptoNeeds: ['kem'],
|
|
62
|
+
},
|
|
63
|
+
authentication: {
|
|
64
|
+
name: 'Authentication Credentials',
|
|
65
|
+
lifespanYears: 1,
|
|
66
|
+
urgency: 'medium',
|
|
67
|
+
cryptoNeeds: ['kem', 'signature'],
|
|
68
|
+
},
|
|
69
|
+
session_tokens: {
|
|
70
|
+
name: 'Session Tokens',
|
|
71
|
+
lifespanYears: 0,
|
|
72
|
+
urgency: 'low',
|
|
73
|
+
cryptoNeeds: ['signature'],
|
|
74
|
+
},
|
|
75
|
+
ephemeral: {
|
|
76
|
+
name: 'Ephemeral Communications',
|
|
77
|
+
lifespanYears: 1,
|
|
78
|
+
urgency: 'low',
|
|
79
|
+
cryptoNeeds: ['kem'],
|
|
80
|
+
},
|
|
81
|
+
// Backward-compat alias
|
|
82
|
+
short_lived: {
|
|
83
|
+
name: 'Session Tokens',
|
|
84
|
+
lifespanYears: 0,
|
|
85
|
+
urgency: 'low',
|
|
86
|
+
cryptoNeeds: ['signature'],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const PQC_ALGORITHMS = {
|
|
91
|
+
kem: [
|
|
92
|
+
{
|
|
93
|
+
id: 'ml-kem-768',
|
|
94
|
+
name: 'ML-KEM-768',
|
|
95
|
+
fips: 'FIPS 203',
|
|
96
|
+
securityLevel: 3,
|
|
97
|
+
status: 'standardized',
|
|
98
|
+
description: 'Primary NIST KEM standard, balanced security/performance',
|
|
99
|
+
hybridWith: 'X25519Kyber768',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'ml-kem-1024',
|
|
103
|
+
name: 'ML-KEM-1024',
|
|
104
|
+
fips: 'FIPS 203',
|
|
105
|
+
securityLevel: 5,
|
|
106
|
+
status: 'standardized',
|
|
107
|
+
description: 'Highest security KEM for long-term protection',
|
|
108
|
+
hybridWith: 'X25519Kyber1024',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'ml-kem-512',
|
|
112
|
+
name: 'ML-KEM-512',
|
|
113
|
+
fips: 'FIPS 203',
|
|
114
|
+
securityLevel: 1,
|
|
115
|
+
status: 'standardized',
|
|
116
|
+
description: 'Smallest/fastest KEM for constrained environments',
|
|
117
|
+
hybridWith: 'X25519Kyber512',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
signature: [
|
|
121
|
+
{
|
|
122
|
+
id: 'ml-dsa-65',
|
|
123
|
+
name: 'ML-DSA-65',
|
|
124
|
+
fips: 'FIPS 204',
|
|
125
|
+
securityLevel: 3,
|
|
126
|
+
status: 'standardized',
|
|
127
|
+
description: 'Primary NIST signature standard, balanced approach',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'ml-dsa-87',
|
|
131
|
+
name: 'ML-DSA-87',
|
|
132
|
+
fips: 'FIPS 204',
|
|
133
|
+
securityLevel: 5,
|
|
134
|
+
status: 'standardized',
|
|
135
|
+
description: 'Highest security signatures for critical applications',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'slh-dsa-128f',
|
|
139
|
+
name: 'SLH-DSA-128f',
|
|
140
|
+
fips: 'FIPS 205',
|
|
141
|
+
securityLevel: 1,
|
|
142
|
+
status: 'standardized',
|
|
143
|
+
description: 'Hash-based signatures, conservative security assumptions',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const COMPLIANCE_FRAMEWORKS = {
|
|
149
|
+
cnsa_2_0: {
|
|
150
|
+
name: 'CNSA 2.0',
|
|
151
|
+
authority: 'NSA',
|
|
152
|
+
kem: 'ML-KEM-1024 required by 2030',
|
|
153
|
+
sig: 'ML-DSA-87 required by 2033',
|
|
154
|
+
},
|
|
155
|
+
nist_sp_800_208: {
|
|
156
|
+
name: 'NIST SP 800-208',
|
|
157
|
+
authority: 'NIST',
|
|
158
|
+
sig: 'LMS/XMSS/SLH-DSA for firmware signing',
|
|
159
|
+
},
|
|
160
|
+
bsi: {
|
|
161
|
+
name: 'BSI TR-02102',
|
|
162
|
+
authority: 'BSI (Germany)',
|
|
163
|
+
note: 'Hybrid mode recommended until 2030',
|
|
164
|
+
},
|
|
165
|
+
anssi: {
|
|
166
|
+
name: 'ANSSI Guidelines',
|
|
167
|
+
authority: 'ANSSI (France)',
|
|
168
|
+
note: 'Hybrid classical+PQC mandated through 2030',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Algorithm classification rules
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
// [pattern, timelineKey, category] — first match wins
|
|
177
|
+
const ALGO_CLASSIFICATION_RULES = [
|
|
178
|
+
// PQC (safe)
|
|
179
|
+
['Kyber', 'pqc', 'pqc'],
|
|
180
|
+
['ML-KEM', 'pqc', 'pqc'],
|
|
181
|
+
['Dilithium', 'pqc', 'pqc'],
|
|
182
|
+
['ML-DSA', 'pqc', 'pqc'],
|
|
183
|
+
['Falcon', 'pqc', 'pqc'],
|
|
184
|
+
['SPHINCS', 'pqc', 'pqc'],
|
|
185
|
+
['SLH-DSA', 'pqc', 'pqc'],
|
|
186
|
+
// Asymmetric (quantum-vulnerable via Shor's)
|
|
187
|
+
['RSA', 'rsa_2048', 'asymmetric'],
|
|
188
|
+
['ECDSA', 'ecdsa_p256', 'asymmetric'],
|
|
189
|
+
['ECDHE', 'ecdsa_p256', 'asymmetric'],
|
|
190
|
+
['ECC', 'ecdsa_p256', 'asymmetric'],
|
|
191
|
+
['Ed25519', 'ed25519', 'asymmetric'],
|
|
192
|
+
['EdDSA', 'ed25519', 'asymmetric'],
|
|
193
|
+
['Curve25519','x25519', 'asymmetric'],
|
|
194
|
+
['X25519', 'x25519', 'asymmetric'],
|
|
195
|
+
['DH', 'dh_2048', 'asymmetric'],
|
|
196
|
+
// Symmetric (Grover's — key-doubling sufficient)
|
|
197
|
+
['AES', 'aes_256', 'symmetric'],
|
|
198
|
+
['ChaCha20', 'chacha20', 'symmetric'],
|
|
199
|
+
['3DES', 'aes_128', 'symmetric'],
|
|
200
|
+
['DES', 'aes_128', 'symmetric'],
|
|
201
|
+
['XSalsa20', 'chacha20', 'symmetric'],
|
|
202
|
+
// Hashing
|
|
203
|
+
['SHA-256', 'sha_256', 'hash'],
|
|
204
|
+
['SHA-512', 'sha_256', 'hash'],
|
|
205
|
+
['SHA-1', 'sha_256', 'hash'],
|
|
206
|
+
['SHA3', 'sha_256', 'hash'],
|
|
207
|
+
['Blake2', 'sha_256', 'hash'],
|
|
208
|
+
['MD5', 'sha_256', 'hash'],
|
|
209
|
+
// KDF / MAC / CSPRNG
|
|
210
|
+
['HMAC', 'sha_256', 'hash'],
|
|
211
|
+
['bcrypt', null, 'kdf'],
|
|
212
|
+
['Argon2', null, 'kdf'],
|
|
213
|
+
['PBKDF2', null, 'kdf'],
|
|
214
|
+
['scrypt', null, 'kdf'],
|
|
215
|
+
['CSPRNG', null, 'random'],
|
|
216
|
+
['Poly1305', null, 'mac'],
|
|
217
|
+
// Token / TLS wrappers
|
|
218
|
+
['TLS', 'rsa_2048', 'asymmetric'],
|
|
219
|
+
['JWS', 'rsa_2048', 'asymmetric'],
|
|
220
|
+
['JWE', 'rsa_2048', 'asymmetric'],
|
|
221
|
+
['JWK', 'rsa_2048', 'asymmetric'],
|
|
222
|
+
['RS256', 'rsa_2048', 'asymmetric'],
|
|
223
|
+
['ES256', 'ecdsa_p256', 'asymmetric'],
|
|
224
|
+
['HS256', 'sha_256', 'hash'],
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Classification
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
function classifyAlgorithms(libraries) {
|
|
232
|
+
const seen = new Set();
|
|
233
|
+
const results = [];
|
|
234
|
+
|
|
235
|
+
for (const lib of libraries) {
|
|
236
|
+
for (const algoName of (lib.algorithms || [])) {
|
|
237
|
+
if (seen.has(algoName)) continue;
|
|
238
|
+
seen.add(algoName);
|
|
239
|
+
|
|
240
|
+
let matched = false;
|
|
241
|
+
const upper = algoName.toUpperCase();
|
|
242
|
+
|
|
243
|
+
for (const [pattern, timelineKey, category] of ALGO_CLASSIFICATION_RULES) {
|
|
244
|
+
if (upper.includes(pattern.toUpperCase())) {
|
|
245
|
+
results.push({ algo: algoName, timelineKey, category });
|
|
246
|
+
matched = true;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!matched) {
|
|
252
|
+
results.push({ algo: algoName, timelineKey: null, category: 'unknown' });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Analysis helpers
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
function assessSndl(classifications, profile, libraries) {
|
|
265
|
+
const lifespan = profile.lifespanYears;
|
|
266
|
+
const migrationYears = 2;
|
|
267
|
+
|
|
268
|
+
const asymmetricTimelines = classifications
|
|
269
|
+
.filter(c => c.category === 'asymmetric' && c.timelineKey in QUANTUM_THREAT_TIMELINE)
|
|
270
|
+
.map(c => QUANTUM_THREAT_TIMELINE[c.timelineKey]);
|
|
271
|
+
|
|
272
|
+
let minMedian, minMin, minMax;
|
|
273
|
+
if (asymmetricTimelines.length > 0) {
|
|
274
|
+
minMedian = Math.min(...asymmetricTimelines.map(t => t.median));
|
|
275
|
+
minMin = Math.min(...asymmetricTimelines.map(t => t.min));
|
|
276
|
+
minMax = Math.min(...asymmetricTimelines.map(t => t.max));
|
|
277
|
+
} else {
|
|
278
|
+
minMedian = 50;
|
|
279
|
+
minMin = 30;
|
|
280
|
+
minMax = 100;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const riskWindow = minMedian - (lifespan + migrationYears);
|
|
284
|
+
const isVulnerable = riskWindow < 0;
|
|
285
|
+
|
|
286
|
+
const hasDeprecated = libraries.some(lib => lib.isDeprecated);
|
|
287
|
+
const hasPqc = classifications.some(c => c.category === 'pqc');
|
|
288
|
+
const hasAsymmetric = classifications.some(c => c.category === 'asymmetric');
|
|
289
|
+
|
|
290
|
+
let riskLevel;
|
|
291
|
+
if (hasDeprecated) {
|
|
292
|
+
riskLevel = 'critical';
|
|
293
|
+
} else if (isVulnerable && hasAsymmetric && !hasPqc) {
|
|
294
|
+
riskLevel = 'critical';
|
|
295
|
+
} else if (isVulnerable) {
|
|
296
|
+
riskLevel = 'high';
|
|
297
|
+
} else if (riskWindow < 5 && hasAsymmetric) {
|
|
298
|
+
riskLevel = 'medium';
|
|
299
|
+
} else {
|
|
300
|
+
riskLevel = 'low';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let explanation;
|
|
304
|
+
if (isVulnerable) {
|
|
305
|
+
explanation =
|
|
306
|
+
`Your data (${profile.name}) with ${lifespan}-year lifespan ` +
|
|
307
|
+
`is at risk. Quantum computers (est. ${minMin}-${minMax} years) may ` +
|
|
308
|
+
`decrypt this data before its confidentiality period expires.`;
|
|
309
|
+
} else if (riskWindow < 5) {
|
|
310
|
+
explanation =
|
|
311
|
+
`Only ${riskWindow} years margin before SNDL risk. ` +
|
|
312
|
+
`Data encrypted today may be vulnerable before expiration.`;
|
|
313
|
+
} else if (riskWindow < 10) {
|
|
314
|
+
explanation = `${riskWindow} years margin. Time to plan migration.`;
|
|
315
|
+
} else {
|
|
316
|
+
explanation = `${riskWindow} years margin. Monitor quantum developments.`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
vulnerable: isVulnerable,
|
|
321
|
+
protectionYearsRequired: lifespan,
|
|
322
|
+
estimatedQuantumYearsMin: minMin,
|
|
323
|
+
estimatedQuantumYearsMedian: minMedian,
|
|
324
|
+
estimatedQuantumYearsMax: minMax,
|
|
325
|
+
riskWindowYears: riskWindow,
|
|
326
|
+
riskLevel,
|
|
327
|
+
explanation,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function scoreAlgorithm(algo, hasCritical) {
|
|
332
|
+
let score = 50.0;
|
|
333
|
+
if (algo.status === 'standardized') score += 25;
|
|
334
|
+
if (hasCritical && algo.securityLevel >= 3) {
|
|
335
|
+
score += 15;
|
|
336
|
+
} else if (algo.securityLevel === 3) {
|
|
337
|
+
score += 10;
|
|
338
|
+
}
|
|
339
|
+
if (algo.id === 'ml-kem-768' || algo.id === 'ml-dsa-65') score += 10;
|
|
340
|
+
return Math.min(100.0, score);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function recommendKem(classifications, profile, libraries) {
|
|
344
|
+
const hasAsymmetric = classifications.some(c => c.category === 'asymmetric');
|
|
345
|
+
if (!hasAsymmetric && !(profile.cryptoNeeds || []).includes('kem')) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const hasCritical = libraries.some(
|
|
350
|
+
lib => lib.quantumRisk === 'high' || lib.quantumRisk === 'critical'
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const currentAlgos = [...new Set(
|
|
354
|
+
classifications.filter(c => c.category === 'asymmetric').map(c => c.algo)
|
|
355
|
+
)].sort();
|
|
356
|
+
const currentDisplay = currentAlgos.length > 0 ? currentAlgos.join(', ') : 'classical algorithms';
|
|
357
|
+
|
|
358
|
+
const results = PQC_ALGORITHMS.kem.map(algo => ({
|
|
359
|
+
currentAlgorithm: currentDisplay,
|
|
360
|
+
recommendedAlgorithm: algo.name,
|
|
361
|
+
fipsStandard: algo.fips,
|
|
362
|
+
securityLevel: `NIST Level ${algo.securityLevel}`,
|
|
363
|
+
description: algo.description,
|
|
364
|
+
hybridOption: algo.hybridWith || null,
|
|
365
|
+
score: scoreAlgorithm(algo, hasCritical),
|
|
366
|
+
rationale: `Replaces quantum-vulnerable key exchange with ${algo.fips}-standardized KEM`,
|
|
367
|
+
migrationComplexity: 'medium',
|
|
368
|
+
}));
|
|
369
|
+
|
|
370
|
+
results.sort((a, b) => b.score - a.score);
|
|
371
|
+
return results;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function recommendSignatures(classifications, profile, libraries) {
|
|
375
|
+
const sigAlgos = new Set(['RSA', 'ECDSA', 'Ed25519', 'EdDSA', 'RS256', 'ES256']);
|
|
376
|
+
const hasSigningAlgo = classifications.some(
|
|
377
|
+
c => sigAlgos.has(c.algo) || c.category === 'asymmetric'
|
|
378
|
+
);
|
|
379
|
+
if (!hasSigningAlgo && !(profile.cryptoNeeds || []).includes('signature')) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const hasCritical = libraries.some(
|
|
384
|
+
lib => lib.quantumRisk === 'high' || lib.quantumRisk === 'critical'
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const currentAlgos = [...new Set(
|
|
388
|
+
classifications.filter(c => c.category === 'asymmetric').map(c => c.algo)
|
|
389
|
+
)].sort();
|
|
390
|
+
const currentDisplay = currentAlgos.length > 0 ? currentAlgos.join(', ') : 'classical signatures';
|
|
391
|
+
|
|
392
|
+
const results = PQC_ALGORITHMS.signature.map(algo => ({
|
|
393
|
+
currentAlgorithm: currentDisplay,
|
|
394
|
+
recommendedAlgorithm: algo.name,
|
|
395
|
+
fipsStandard: algo.fips,
|
|
396
|
+
securityLevel: `NIST Level ${algo.securityLevel}`,
|
|
397
|
+
description: algo.description,
|
|
398
|
+
score: scoreAlgorithm(algo, hasCritical),
|
|
399
|
+
rationale: `Replaces quantum-vulnerable signatures with ${algo.fips}-standardized scheme`,
|
|
400
|
+
migrationComplexity: 'medium',
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
results.sort((a, b) => b.score - a.score);
|
|
404
|
+
return results;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function generateMigrationPlan(libraries, classifications, sndl) {
|
|
408
|
+
const steps = [];
|
|
409
|
+
let stepOrder = 1;
|
|
410
|
+
|
|
411
|
+
const deprecated = libraries.filter(lib => lib.isDeprecated);
|
|
412
|
+
if (deprecated.length > 0) {
|
|
413
|
+
steps.push({
|
|
414
|
+
step: stepOrder++,
|
|
415
|
+
action: 'Replace deprecated libraries',
|
|
416
|
+
description: `Remove ${deprecated.map(l => l.name).join(', ')} — known vulnerabilities`,
|
|
417
|
+
priority: 'CRITICAL',
|
|
418
|
+
effort: 'medium',
|
|
419
|
+
affected: deprecated.map(l => l.name),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const hasPqc = classifications.some(c => c.category === 'pqc');
|
|
424
|
+
if (!hasPqc) {
|
|
425
|
+
steps.push({
|
|
426
|
+
step: stepOrder++,
|
|
427
|
+
action: 'Enable cryptographic agility',
|
|
428
|
+
description: 'Refactor to support algorithm negotiation and easy swapping',
|
|
429
|
+
priority: ['critical', 'high'].includes(sndl.riskLevel) ? 'HIGH' : 'MEDIUM',
|
|
430
|
+
effort: 'high',
|
|
431
|
+
affected: [],
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const hasAsymmetric = classifications.some(c => c.category === 'asymmetric');
|
|
436
|
+
if (hasAsymmetric) {
|
|
437
|
+
steps.push({
|
|
438
|
+
step: stepOrder++,
|
|
439
|
+
action: 'Deploy hybrid key exchange',
|
|
440
|
+
description: 'Implement X25519Kyber768 for TLS and key exchange',
|
|
441
|
+
priority: sndl.vulnerable ? 'HIGH' : 'MEDIUM',
|
|
442
|
+
effort: 'medium',
|
|
443
|
+
affected: libraries.filter(l => l.category === 'tls').map(l => l.name),
|
|
444
|
+
targetAlgorithm: 'X25519Kyber768',
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const sigAlgos = new Set(['RSA', 'ECDSA', 'Ed25519', 'EdDSA', 'RS256', 'ES256']);
|
|
449
|
+
const hasSigning = classifications.some(c => sigAlgos.has(c.algo));
|
|
450
|
+
if (hasSigning) {
|
|
451
|
+
steps.push({
|
|
452
|
+
step: stepOrder++,
|
|
453
|
+
action: 'Migrate to PQC signatures',
|
|
454
|
+
description: 'Replace RSA/ECDSA signatures with ML-DSA-65',
|
|
455
|
+
priority: 'MEDIUM',
|
|
456
|
+
effort: 'medium',
|
|
457
|
+
affected: libraries.filter(l => l.category === 'token').map(l => l.name),
|
|
458
|
+
targetAlgorithm: 'ML-DSA-65',
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
steps.push({
|
|
463
|
+
step: stepOrder,
|
|
464
|
+
action: 'Complete PQC migration',
|
|
465
|
+
description: 'Remove classical-only crypto, verify quantum resistance',
|
|
466
|
+
priority: 'LOW',
|
|
467
|
+
effort: 'low',
|
|
468
|
+
affected: [],
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
return steps;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function calculateQuantumScore(libraries, classifications) {
|
|
475
|
+
if (libraries.length === 0) return 100.0;
|
|
476
|
+
|
|
477
|
+
const safe = libraries.filter(
|
|
478
|
+
lib => ['none', 'low'].includes((lib.quantumRisk || '').toLowerCase())
|
|
479
|
+
).length;
|
|
480
|
+
const vulnerable = libraries.filter(
|
|
481
|
+
lib => ['high', 'critical'].includes((lib.quantumRisk || '').toLowerCase())
|
|
482
|
+
).length;
|
|
483
|
+
const total = safe + vulnerable;
|
|
484
|
+
|
|
485
|
+
if (total === 0) return 100.0;
|
|
486
|
+
|
|
487
|
+
let score = (safe / total) * 100;
|
|
488
|
+
if (classifications.some(c => c.category === 'pqc')) {
|
|
489
|
+
score = Math.min(100, score + 20);
|
|
490
|
+
}
|
|
491
|
+
const deprecatedCount = libraries.filter(lib => lib.isDeprecated).length;
|
|
492
|
+
if (deprecatedCount > 0) {
|
|
493
|
+
score = Math.max(0, score - deprecatedCount * 10);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return Math.round(score * 10) / 10;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getComplianceReferences(urgency) {
|
|
500
|
+
const refs = [];
|
|
501
|
+
const allKeys = ['cnsa_2_0', 'nist_sp_800_208', 'bsi', 'anssi'];
|
|
502
|
+
const mediumKeys = ['cnsa_2_0', 'bsi'];
|
|
503
|
+
|
|
504
|
+
const keys = ['critical', 'high'].includes(urgency) ? allKeys
|
|
505
|
+
: urgency === 'medium' ? mediumKeys
|
|
506
|
+
: [];
|
|
507
|
+
|
|
508
|
+
for (const key of keys) {
|
|
509
|
+
const fw = COMPLIANCE_FRAMEWORKS[key];
|
|
510
|
+
const detail = fw.kem || fw.sig || fw.note || '';
|
|
511
|
+
refs.push({ framework: fw.name, authority: fw.authority, detail });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return refs;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function generateFindings(libraries, classifications, sndl, profile) {
|
|
518
|
+
const findings = [];
|
|
519
|
+
|
|
520
|
+
const vulnerableCount = libraries.filter(
|
|
521
|
+
lib => lib.quantumRisk === 'high' || lib.quantumRisk === 'critical'
|
|
522
|
+
).length;
|
|
523
|
+
if (vulnerableCount > 0) {
|
|
524
|
+
findings.push(`Found ${vulnerableCount} quantum-vulnerable libraries`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const deprecatedCount = libraries.filter(lib => lib.isDeprecated).length;
|
|
528
|
+
if (deprecatedCount > 0) {
|
|
529
|
+
findings.push(`Found ${deprecatedCount} deprecated libraries requiring immediate attention`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
findings.push(
|
|
533
|
+
`Data profile '${profile.name}' requires ${profile.lifespanYears}-year protection`
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
if (sndl.vulnerable) {
|
|
537
|
+
findings.push('SNDL risk: Data may be decryptable before confidentiality period expires');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (classifications.some(c => c.category === 'pqc')) {
|
|
541
|
+
findings.push('Post-quantum cryptography already in use');
|
|
542
|
+
} else {
|
|
543
|
+
findings.push('No post-quantum cryptography detected');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return findings;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function generateNextSteps(urgency, migrationPlan, sndl) {
|
|
550
|
+
const steps = [];
|
|
551
|
+
|
|
552
|
+
if (migrationPlan.length > 0) {
|
|
553
|
+
steps.push(`Priority: ${migrationPlan[0].action}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (urgency === 'critical') {
|
|
557
|
+
steps.push('Deploy hybrid crypto (X25519Kyber768) within 90 days');
|
|
558
|
+
steps.push('Identify and re-encrypt sensitive long-term data');
|
|
559
|
+
} else if (urgency === 'high') {
|
|
560
|
+
steps.push('Begin PQC pilot project within 6 months');
|
|
561
|
+
steps.push('Evaluate liboqs or @noble/post-quantum for Node.js integration');
|
|
562
|
+
} else if (urgency === 'medium') {
|
|
563
|
+
steps.push('Include PQC migration in next architecture review');
|
|
564
|
+
steps.push('Train development team on PQC concepts');
|
|
565
|
+
} else {
|
|
566
|
+
steps.push('Monitor NIST PQC standardization updates');
|
|
567
|
+
steps.push('Evaluate crypto agility improvements');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return steps;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function buildThreatTimelines(classifications) {
|
|
574
|
+
const timelines = {};
|
|
575
|
+
for (const c of classifications) {
|
|
576
|
+
const key = c.timelineKey;
|
|
577
|
+
if (key && key in QUANTUM_THREAT_TIMELINE && !(key in timelines)) {
|
|
578
|
+
const t = QUANTUM_THREAT_TIMELINE[key];
|
|
579
|
+
timelines[key] = {
|
|
580
|
+
algorithm: c.algo,
|
|
581
|
+
timelineKey: key,
|
|
582
|
+
minYears: t.min,
|
|
583
|
+
medianYears: t.median,
|
|
584
|
+
maxYears: t.max,
|
|
585
|
+
status: t.median <= 25 ? 'AT RISK' : 'SAFE',
|
|
586
|
+
category: c.category,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return timelines;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
// Main entry point
|
|
595
|
+
// ---------------------------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
export function analyzeOffline(libraries, dataProfile = null) {
|
|
598
|
+
const profileKey = dataProfile || 'general';
|
|
599
|
+
const profile = DATA_PROFILES[profileKey] || DATA_PROFILES.general;
|
|
600
|
+
|
|
601
|
+
const classifications = classifyAlgorithms(libraries);
|
|
602
|
+
const sndl = assessSndl(classifications, profile, libraries);
|
|
603
|
+
const kemRecs = recommendKem(classifications, profile, libraries);
|
|
604
|
+
const sigRecs = recommendSignatures(classifications, profile, libraries);
|
|
605
|
+
const migrationPlan = generateMigrationPlan(libraries, classifications, sndl);
|
|
606
|
+
const quantumScore = calculateQuantumScore(libraries, classifications);
|
|
607
|
+
|
|
608
|
+
const urgency = sndl.riskLevel;
|
|
609
|
+
const complianceRefs = getComplianceReferences(urgency);
|
|
610
|
+
const findings = generateFindings(libraries, classifications, sndl, profile);
|
|
611
|
+
const nextSteps = generateNextSteps(urgency, migrationPlan, sndl);
|
|
612
|
+
const threatTimelines = buildThreatTimelines(classifications);
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
generatedAt: new Date().toISOString(),
|
|
616
|
+
analysisMode: 'offline',
|
|
617
|
+
sndlAssessment: sndl,
|
|
618
|
+
kemRecommendations: kemRecs,
|
|
619
|
+
signatureRecommendations: sigRecs,
|
|
620
|
+
migrationPlan,
|
|
621
|
+
overallUrgency: urgency,
|
|
622
|
+
quantumReadinessScore: quantumScore,
|
|
623
|
+
keyFindings: findings,
|
|
624
|
+
nextSteps,
|
|
625
|
+
complianceReferences: complianceRefs,
|
|
626
|
+
threatTimelines,
|
|
627
|
+
dataProfile: {
|
|
628
|
+
key: profileKey,
|
|
629
|
+
name: profile.name,
|
|
630
|
+
lifespanYears: profile.lifespanYears,
|
|
631
|
+
urgency: profile.urgency,
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export { classifyAlgorithms, assessSndl, calculateQuantumScore };
|