@quantakrypto/core 0.2.1 → 0.3.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/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +105 -0
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +26 -1
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +26 -0
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/pem.d.ts +4 -0
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +113 -91
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/source.d.ts +9 -2
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +366 -265
- package/dist/detectors/source.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +6 -3
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +11 -6
- package/dist/inventory.js.map +1 -1
- package/dist/parallel.d.ts.map +1 -1
- package/dist/parallel.js +2 -0
- package/dist/parallel.js.map +1 -1
- package/dist/registry.d.ts +16 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +31 -0
- package/dist/registry.js.map +1 -1
- package/dist/report.d.ts +9 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +56 -23
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +1 -1
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +8 -2
- package/dist/scan.js.map +1 -1
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/dependencies.ts +105 -0
- package/src/detect-utils.ts +57 -1
- package/src/detectors/pem.ts +124 -105
- package/src/detectors/source.ts +466 -343
- package/src/index.ts +2 -1
- package/src/inventory.ts +12 -6
- package/src/parallel.ts +9 -1
- package/src/registry.ts +39 -1
- package/src/report.ts +79 -23
- package/src/scan-worker.ts +12 -5
- package/src/scan.ts +19 -5
- package/src/types.ts +54 -0
- package/src/version.ts +1 -1
package/dist/detectors/source.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JS_TS_EXTENSIONS, eachMatch,
|
|
1
|
+
import { JS_TS_EXTENSIONS, eachMatch, findingFromRule, hasExtension, nearSortedCall, } from "../detect-utils.js";
|
|
2
2
|
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
3
3
|
/* -------------------------------------------------------------------------- */
|
|
4
4
|
/* Precompiled regexes (module scope — never recreated per file) */
|
|
@@ -42,16 +42,128 @@ const RE_TLS_WEAK_CIPHER = /ciphers\s*:\s*['"`][^'"`\n]{0,256}?\b(RC4|DES|3DES|M
|
|
|
42
42
|
/* -------------------------------------------------------------------------- */
|
|
43
43
|
/* Node.js `crypto` module */
|
|
44
44
|
/* -------------------------------------------------------------------------- */
|
|
45
|
+
/**
|
|
46
|
+
* Rule catalog for the Node `crypto` detector. `node-crypto-keygen` and
|
|
47
|
+
* `node-crypto-dh-modp` refine some fields per match; the rest emit findings
|
|
48
|
+
* straight from these declarations.
|
|
49
|
+
*/
|
|
50
|
+
const RULE_NODE_KEYGEN = {
|
|
51
|
+
id: "node-crypto-keygen",
|
|
52
|
+
title: "Classical key generation",
|
|
53
|
+
description: "crypto.generateKeyPair(Sync)('rsa'|'ec'|'dsa'|'dh'|…)",
|
|
54
|
+
category: "key-exchange",
|
|
55
|
+
severity: "high",
|
|
56
|
+
confidence: "high",
|
|
57
|
+
algorithm: "unknown",
|
|
58
|
+
hndl: true,
|
|
59
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
60
|
+
message: "Generates a classical asymmetric key pair, which is not quantum-safe.",
|
|
61
|
+
};
|
|
62
|
+
const RULE_NODE_SIGN = {
|
|
63
|
+
id: "node-crypto-sign",
|
|
64
|
+
title: "Classical signature (createSign/createVerify)",
|
|
65
|
+
description: "crypto.createSign / crypto.createVerify",
|
|
66
|
+
category: "signature",
|
|
67
|
+
severity: "high",
|
|
68
|
+
confidence: "medium",
|
|
69
|
+
algorithm: "unknown",
|
|
70
|
+
hndl: false,
|
|
71
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
72
|
+
message: "Uses createSign/createVerify, typically RSA, ECDSA or DSA — all forgeable by a quantum attacker.",
|
|
73
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
74
|
+
};
|
|
75
|
+
const RULE_NODE_SIGN_ONESHOT = {
|
|
76
|
+
id: "node-crypto-sign-oneshot",
|
|
77
|
+
title: "Classical one-shot signature (crypto.sign/verify)",
|
|
78
|
+
description: "one-shot crypto.sign / crypto.verify (Node ≥ 12)",
|
|
79
|
+
category: "signature",
|
|
80
|
+
severity: "high",
|
|
81
|
+
confidence: "medium",
|
|
82
|
+
algorithm: "unknown",
|
|
83
|
+
hndl: false,
|
|
84
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
85
|
+
message: "Uses the one-shot crypto.sign/crypto.verify API, typically RSA/ECDSA/EdDSA — forgeable by a quantum attacker.",
|
|
86
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
87
|
+
};
|
|
88
|
+
const RULE_NODE_DH = {
|
|
89
|
+
id: "node-crypto-dh",
|
|
90
|
+
title: "Diffie-Hellman key exchange",
|
|
91
|
+
description: "crypto.createDiffieHellman(Group)",
|
|
92
|
+
category: "key-exchange",
|
|
93
|
+
severity: "high",
|
|
94
|
+
confidence: "high",
|
|
95
|
+
algorithm: "DH",
|
|
96
|
+
hndl: true,
|
|
97
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
98
|
+
message: "Finite-field Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
99
|
+
};
|
|
100
|
+
const RULE_NODE_DH_MODP = {
|
|
101
|
+
id: "node-crypto-dh-modp",
|
|
102
|
+
title: "Diffie-Hellman MODP group",
|
|
103
|
+
description: "crypto.getDiffieHellman('modpN')",
|
|
104
|
+
category: "key-exchange",
|
|
105
|
+
severity: "high",
|
|
106
|
+
confidence: "high",
|
|
107
|
+
algorithm: "DH",
|
|
108
|
+
hndl: true,
|
|
109
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
110
|
+
message: "Named finite-field DH MODP group is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
111
|
+
};
|
|
112
|
+
const RULE_NODE_ECDH = {
|
|
113
|
+
id: "node-crypto-ecdh",
|
|
114
|
+
title: "ECDH key exchange",
|
|
115
|
+
description: "crypto.createECDH",
|
|
116
|
+
category: "key-exchange",
|
|
117
|
+
severity: "high",
|
|
118
|
+
confidence: "high",
|
|
119
|
+
algorithm: "ECDH",
|
|
120
|
+
hndl: true,
|
|
121
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
122
|
+
message: "Elliptic-curve Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
123
|
+
};
|
|
124
|
+
const RULE_NODE_RSA_ENCRYPT = {
|
|
125
|
+
id: "node-crypto-rsa-encrypt",
|
|
126
|
+
title: "RSA public-key encryption",
|
|
127
|
+
description: "crypto.publicEncrypt / crypto.privateDecrypt",
|
|
128
|
+
category: "kem",
|
|
129
|
+
severity: "high",
|
|
130
|
+
confidence: "high",
|
|
131
|
+
algorithm: "RSA",
|
|
132
|
+
hndl: true,
|
|
133
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
134
|
+
message: "RSA public-key encryption is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.",
|
|
135
|
+
};
|
|
136
|
+
const RULE_NODE_DH_KEYOBJECT = {
|
|
137
|
+
id: "node-crypto-dh-keyobject",
|
|
138
|
+
title: "Diffie-Hellman (KeyObject) key exchange",
|
|
139
|
+
description: "crypto.diffieHellman({ privateKey, publicKey })",
|
|
140
|
+
category: "key-exchange",
|
|
141
|
+
severity: "high",
|
|
142
|
+
confidence: "high",
|
|
143
|
+
algorithm: "ECDH",
|
|
144
|
+
hndl: true,
|
|
145
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
146
|
+
message: "crypto.diffieHellman() performs a classical (EC)DH agreement (harvest-now-decrypt-later).",
|
|
147
|
+
};
|
|
45
148
|
/** Detects classical asymmetric usage from Node's built-in `crypto` module. */
|
|
46
149
|
const nodeCryptoDetector = {
|
|
47
150
|
id: "node-crypto",
|
|
48
151
|
description: "Classical asymmetric crypto via the Node.js `crypto` module",
|
|
49
152
|
scope: "source",
|
|
50
153
|
language: "js",
|
|
154
|
+
rules: [
|
|
155
|
+
RULE_NODE_KEYGEN,
|
|
156
|
+
RULE_NODE_SIGN,
|
|
157
|
+
RULE_NODE_SIGN_ONESHOT,
|
|
158
|
+
RULE_NODE_DH,
|
|
159
|
+
RULE_NODE_DH_MODP,
|
|
160
|
+
RULE_NODE_ECDH,
|
|
161
|
+
RULE_NODE_RSA_ENCRYPT,
|
|
162
|
+
RULE_NODE_DH_KEYOBJECT,
|
|
163
|
+
],
|
|
51
164
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
52
165
|
detect({ file, content }) {
|
|
53
166
|
const findings = [];
|
|
54
|
-
const push = (spec, m) => findings.push(makeFinding({ ...spec, file, content, index: m.index, matchLength: m[0].length }));
|
|
55
167
|
// generateKeyPair(Sync)('rsa' | 'ec' | 'dsa' | 'dh' | 'x25519' | 'ed25519', ...)
|
|
56
168
|
eachMatch(RE_GENERATE_KEYPAIR, content, (m) => {
|
|
57
169
|
const type = m[1].toLowerCase();
|
|
@@ -78,119 +190,72 @@ const nodeCryptoDetector = {
|
|
|
78
190
|
ed448: { algo: "EdDSA", cat: "signature", sev: "low", hndl: false, label: "Ed448" },
|
|
79
191
|
};
|
|
80
192
|
const info = map[type];
|
|
81
|
-
push({
|
|
82
|
-
ruleId: "node-crypto-keygen",
|
|
193
|
+
findings.push(findingFromRule(RULE_NODE_KEYGEN, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
83
194
|
title: `${info.label} key generation`,
|
|
84
195
|
category: info.cat,
|
|
85
196
|
severity: info.sev,
|
|
86
|
-
confidence: "high",
|
|
87
197
|
algorithm: info.algo,
|
|
88
198
|
hndl: info.hndl,
|
|
89
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
90
199
|
message: info.message ??
|
|
91
200
|
`Generates a classical ${info.label} key pair, which is not quantum-safe.`,
|
|
92
201
|
...(info.remediation ? { remediation: info.remediation } : {}),
|
|
93
|
-
}
|
|
202
|
+
}));
|
|
94
203
|
});
|
|
95
204
|
// createSign / createVerify — RSA / ECDSA / DSA signatures.
|
|
96
205
|
eachMatch(RE_CREATE_SIGN_VERIFY, content, (m) => {
|
|
97
|
-
push({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
algorithm: "unknown",
|
|
104
|
-
hndl: false,
|
|
105
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
106
|
-
message: "Uses createSign/createVerify, typically RSA, ECDSA or DSA — all forgeable by a quantum attacker.",
|
|
107
|
-
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
108
|
-
}, m);
|
|
206
|
+
findings.push(findingFromRule(RULE_NODE_SIGN, {
|
|
207
|
+
file,
|
|
208
|
+
content,
|
|
209
|
+
index: m.index,
|
|
210
|
+
matchLength: m[0].length,
|
|
211
|
+
}));
|
|
109
212
|
});
|
|
110
213
|
// One-shot crypto.sign(algorithm, data, key) / crypto.verify(...) (Node ≥ 12).
|
|
111
214
|
eachMatch(RE_ONESHOT_SIGN_VERIFY, content, (m) => {
|
|
112
|
-
push({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
algorithm: "unknown",
|
|
119
|
-
hndl: false,
|
|
120
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
121
|
-
message: "Uses the one-shot crypto.sign/crypto.verify API, typically RSA/ECDSA/EdDSA — forgeable by a quantum attacker.",
|
|
122
|
-
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
123
|
-
}, m);
|
|
215
|
+
findings.push(findingFromRule(RULE_NODE_SIGN_ONESHOT, {
|
|
216
|
+
file,
|
|
217
|
+
content,
|
|
218
|
+
index: m.index,
|
|
219
|
+
matchLength: m[0].length,
|
|
220
|
+
}));
|
|
124
221
|
});
|
|
125
222
|
// createDiffieHellman / createDiffieHellmanGroup — finite-field DH key exchange.
|
|
126
223
|
eachMatch(RE_CREATE_DH, content, (m) => {
|
|
127
|
-
push({
|
|
128
|
-
ruleId: "node-crypto-dh",
|
|
129
|
-
title: "Diffie-Hellman key exchange",
|
|
130
|
-
category: "key-exchange",
|
|
131
|
-
severity: "high",
|
|
132
|
-
confidence: "high",
|
|
133
|
-
algorithm: "DH",
|
|
134
|
-
hndl: true,
|
|
135
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
136
|
-
message: "Finite-field Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
137
|
-
}, m);
|
|
224
|
+
findings.push(findingFromRule(RULE_NODE_DH, { file, content, index: m.index, matchLength: m[0].length }));
|
|
138
225
|
});
|
|
139
226
|
// getDiffieHellman('modpN') — named built-in finite-field MODP groups.
|
|
140
227
|
eachMatch(RE_GET_DH, content, (m) => {
|
|
141
|
-
push({
|
|
142
|
-
ruleId: "node-crypto-dh-modp",
|
|
228
|
+
findings.push(findingFromRule(RULE_NODE_DH_MODP, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
143
229
|
title: `Diffie-Hellman MODP group (${m[1]})`,
|
|
144
|
-
category: "key-exchange",
|
|
145
|
-
severity: "high",
|
|
146
|
-
confidence: "high",
|
|
147
|
-
algorithm: "DH",
|
|
148
|
-
hndl: true,
|
|
149
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
150
230
|
message: `Named finite-field DH MODP group "${m[1]}" is broken by Shor's algorithm (harvest-now-decrypt-later).`,
|
|
151
|
-
}
|
|
231
|
+
}));
|
|
152
232
|
});
|
|
153
233
|
// createECDH — elliptic-curve Diffie-Hellman key exchange.
|
|
154
234
|
eachMatch(RE_CREATE_ECDH, content, (m) => {
|
|
155
|
-
push({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
algorithm: "ECDH",
|
|
162
|
-
hndl: true,
|
|
163
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
164
|
-
message: "Elliptic-curve Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
165
|
-
}, m);
|
|
235
|
+
findings.push(findingFromRule(RULE_NODE_ECDH, {
|
|
236
|
+
file,
|
|
237
|
+
content,
|
|
238
|
+
index: m.index,
|
|
239
|
+
matchLength: m[0].length,
|
|
240
|
+
}));
|
|
166
241
|
});
|
|
167
242
|
// publicEncrypt / privateDecrypt — RSA encryption (KEM-like confidentiality).
|
|
168
243
|
eachMatch(RE_RSA_ENCRYPT, content, (m) => {
|
|
169
|
-
push({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
algorithm: "RSA",
|
|
176
|
-
hndl: true,
|
|
177
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
178
|
-
message: "RSA public-key encryption is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.",
|
|
179
|
-
}, m);
|
|
244
|
+
findings.push(findingFromRule(RULE_NODE_RSA_ENCRYPT, {
|
|
245
|
+
file,
|
|
246
|
+
content,
|
|
247
|
+
index: m.index,
|
|
248
|
+
matchLength: m[0].length,
|
|
249
|
+
}));
|
|
180
250
|
});
|
|
181
251
|
// diffieHellman({ privateKey, publicKey }) — KeyObject-based DH/ECDH.
|
|
182
252
|
eachMatch(RE_DH_KEYOBJECT, content, (m) => {
|
|
183
|
-
push({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
algorithm: "ECDH",
|
|
190
|
-
hndl: true,
|
|
191
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
192
|
-
message: "crypto.diffieHellman() performs a classical (EC)DH agreement (harvest-now-decrypt-later).",
|
|
193
|
-
}, m);
|
|
253
|
+
findings.push(findingFromRule(RULE_NODE_DH_KEYOBJECT, {
|
|
254
|
+
file,
|
|
255
|
+
content,
|
|
256
|
+
index: m.index,
|
|
257
|
+
matchLength: m[0].length,
|
|
258
|
+
}));
|
|
194
259
|
});
|
|
195
260
|
return findings;
|
|
196
261
|
},
|
|
@@ -198,6 +263,18 @@ const nodeCryptoDetector = {
|
|
|
198
263
|
/* -------------------------------------------------------------------------- */
|
|
199
264
|
/* WebCrypto (SubtleCrypto) */
|
|
200
265
|
/* -------------------------------------------------------------------------- */
|
|
266
|
+
const RULE_WEBCRYPTO = {
|
|
267
|
+
id: "webcrypto-classical",
|
|
268
|
+
title: "WebCrypto classical algorithm",
|
|
269
|
+
description: "classical asymmetric algorithm passed to SubtleCrypto",
|
|
270
|
+
category: "signature",
|
|
271
|
+
severity: "high",
|
|
272
|
+
confidence: "high",
|
|
273
|
+
algorithm: "unknown",
|
|
274
|
+
hndl: false,
|
|
275
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
276
|
+
message: "A classical asymmetric WebCrypto algorithm is used, which is not quantum-safe.",
|
|
277
|
+
};
|
|
201
278
|
/**
|
|
202
279
|
* Detects classical algorithms passed to WebCrypto's SubtleCrypto methods. The
|
|
203
280
|
* algorithm name can appear as a bare string ("RSA-OAEP") or as
|
|
@@ -208,6 +285,7 @@ const webCryptoDetector = {
|
|
|
208
285
|
description: "Classical asymmetric algorithms via WebCrypto SubtleCrypto",
|
|
209
286
|
scope: "source",
|
|
210
287
|
language: "js",
|
|
288
|
+
rules: [RULE_WEBCRYPTO],
|
|
211
289
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
212
290
|
detect({ file, content }) {
|
|
213
291
|
const findings = [];
|
|
@@ -231,20 +309,12 @@ const webCryptoDetector = {
|
|
|
231
309
|
: "ECDSA";
|
|
232
310
|
const category = isEcdh ? "key-exchange" : isKem ? "kem" : "signature";
|
|
233
311
|
const hndl = isKem || isEcdh;
|
|
234
|
-
findings.push(
|
|
235
|
-
ruleId: "webcrypto-classical",
|
|
312
|
+
findings.push(findingFromRule(RULE_WEBCRYPTO, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
236
313
|
title: `WebCrypto ${m[1]}`,
|
|
237
314
|
category,
|
|
238
|
-
severity: "high",
|
|
239
|
-
confidence: "high",
|
|
240
315
|
algorithm,
|
|
241
316
|
hndl,
|
|
242
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
243
317
|
message: `WebCrypto algorithm "${m[1]}" is classical asymmetric crypto and not quantum-safe.`,
|
|
244
|
-
file,
|
|
245
|
-
content,
|
|
246
|
-
index: m.index,
|
|
247
|
-
matchLength: m[0].length,
|
|
248
318
|
}));
|
|
249
319
|
});
|
|
250
320
|
return findings;
|
|
@@ -253,108 +323,151 @@ const webCryptoDetector = {
|
|
|
253
323
|
/* -------------------------------------------------------------------------- */
|
|
254
324
|
/* Popular crypto libraries */
|
|
255
325
|
/* -------------------------------------------------------------------------- */
|
|
326
|
+
const RULE_FORGE_RSA = {
|
|
327
|
+
id: "forge-rsa-keygen",
|
|
328
|
+
title: "node-forge RSA key generation",
|
|
329
|
+
description: "node-forge pki.rsa.generateKeyPair",
|
|
330
|
+
category: "kem",
|
|
331
|
+
severity: "high",
|
|
332
|
+
confidence: "high",
|
|
333
|
+
algorithm: "RSA",
|
|
334
|
+
hndl: true,
|
|
335
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
336
|
+
message: "node-forge generates a classical RSA key pair, which is not quantum-safe.",
|
|
337
|
+
};
|
|
338
|
+
const RULE_FORGE_ED25519 = {
|
|
339
|
+
id: "forge-ed25519",
|
|
340
|
+
title: "node-forge Ed25519 usage",
|
|
341
|
+
description: "node-forge forge.ed25519.*",
|
|
342
|
+
category: "signature",
|
|
343
|
+
severity: "low",
|
|
344
|
+
confidence: "high",
|
|
345
|
+
algorithm: "EdDSA",
|
|
346
|
+
hndl: false,
|
|
347
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
348
|
+
message: "node-forge Ed25519 is a modern but still classical signature scheme.",
|
|
349
|
+
};
|
|
350
|
+
const RULE_ELLIPTIC_EC = {
|
|
351
|
+
id: "elliptic-ec",
|
|
352
|
+
title: "elliptic curve instantiation",
|
|
353
|
+
description: "the `elliptic` library — new EC(...)",
|
|
354
|
+
category: "signature",
|
|
355
|
+
severity: "high",
|
|
356
|
+
confidence: "high",
|
|
357
|
+
algorithm: "ECDSA",
|
|
358
|
+
hndl: false,
|
|
359
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
360
|
+
message: "The `elliptic` library implements classical ECDSA/ECDH, both broken by Shor's algorithm.",
|
|
361
|
+
};
|
|
362
|
+
const RULE_SECP256K1 = {
|
|
363
|
+
id: "secp256k1-usage",
|
|
364
|
+
title: "secp256k1 ECDSA/ECDH usage",
|
|
365
|
+
description: "direct @noble/secp256k1-style API usage",
|
|
366
|
+
category: "signature",
|
|
367
|
+
severity: "high",
|
|
368
|
+
confidence: "medium",
|
|
369
|
+
algorithm: "ECDSA",
|
|
370
|
+
hndl: false,
|
|
371
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
372
|
+
message: "Direct secp256k1 usage (ECDSA signatures / ECDH agreement) is classical and broken by Shor's algorithm.",
|
|
373
|
+
remediation: "ML-DSA-65 (FIPS 204) for signatures; hybrid X25519MLKEM768 for key agreement.",
|
|
374
|
+
};
|
|
375
|
+
const RULE_JSRSASIGN_KEYGEN = {
|
|
376
|
+
id: "jsrsasign-keygen",
|
|
377
|
+
title: "jsrsasign key generation",
|
|
378
|
+
description: "jsrsasign KEYUTIL.generateKeypair",
|
|
379
|
+
category: "signature",
|
|
380
|
+
severity: "high",
|
|
381
|
+
confidence: "high",
|
|
382
|
+
algorithm: "unknown",
|
|
383
|
+
hndl: false,
|
|
384
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
385
|
+
message: "jsrsasign generates classical RSA/EC key pairs, which are not quantum-safe.",
|
|
386
|
+
remediation: "ML-KEM-768 (FIPS 203) / ML-DSA-65 (FIPS 204)",
|
|
387
|
+
};
|
|
388
|
+
const RULE_JSRSASIGN_SIGN = {
|
|
389
|
+
id: "jsrsasign-sign",
|
|
390
|
+
title: "jsrsasign signature",
|
|
391
|
+
description: "jsrsasign KJUR.crypto.Signature / ECDSA",
|
|
392
|
+
category: "signature",
|
|
393
|
+
severity: "high",
|
|
394
|
+
confidence: "high",
|
|
395
|
+
algorithm: "unknown",
|
|
396
|
+
hndl: false,
|
|
397
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
398
|
+
message: "jsrsasign signing uses classical RSA/ECDSA signatures, forgeable by a quantum attacker.",
|
|
399
|
+
remediation: "ML-DSA-65 (FIPS 204)",
|
|
400
|
+
};
|
|
401
|
+
const RULE_NODE_RSA_LIB = {
|
|
402
|
+
id: "node-rsa",
|
|
403
|
+
title: "node-rsa key/usage",
|
|
404
|
+
description: "the `node-rsa` library — new NodeRSA(...)",
|
|
405
|
+
category: "kem",
|
|
406
|
+
severity: "high",
|
|
407
|
+
confidence: "high",
|
|
408
|
+
algorithm: "RSA",
|
|
409
|
+
hndl: true,
|
|
410
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
411
|
+
message: "node-rsa wraps classical RSA encryption/signing, which is not quantum-safe.",
|
|
412
|
+
};
|
|
256
413
|
/** Detects classical crypto from popular npm libraries used in source. */
|
|
257
414
|
const libraryDetector = {
|
|
258
415
|
id: "crypto-libs",
|
|
259
416
|
description: "Classical asymmetric crypto via node-forge, elliptic, jsrsasign, node-rsa",
|
|
260
417
|
scope: "source",
|
|
261
418
|
language: "js",
|
|
419
|
+
rules: [
|
|
420
|
+
RULE_FORGE_RSA,
|
|
421
|
+
RULE_FORGE_ED25519,
|
|
422
|
+
RULE_ELLIPTIC_EC,
|
|
423
|
+
RULE_SECP256K1,
|
|
424
|
+
RULE_JSRSASIGN_KEYGEN,
|
|
425
|
+
RULE_JSRSASIGN_SIGN,
|
|
426
|
+
RULE_NODE_RSA_LIB,
|
|
427
|
+
],
|
|
262
428
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
263
429
|
detect({ file, content }) {
|
|
264
430
|
const findings = [];
|
|
265
|
-
const add = (re,
|
|
266
|
-
// node-forge: pki.rsa.generateKeyPair(...)
|
|
267
|
-
add(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
algorithm: "RSA",
|
|
274
|
-
hndl: true,
|
|
275
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
276
|
-
message: "node-forge generates a classical RSA key pair, which is not quantum-safe.",
|
|
277
|
-
});
|
|
278
|
-
// node-forge: forge.ed25519.* (classical EdDSA)
|
|
279
|
-
add(RE_FORGE_ED25519, {
|
|
280
|
-
ruleId: "forge-ed25519",
|
|
281
|
-
title: "node-forge Ed25519 usage",
|
|
282
|
-
category: "signature",
|
|
283
|
-
severity: "low",
|
|
284
|
-
confidence: "high",
|
|
285
|
-
algorithm: "EdDSA",
|
|
286
|
-
hndl: false,
|
|
287
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
288
|
-
message: "node-forge Ed25519 is a modern but still classical signature scheme.",
|
|
289
|
-
});
|
|
290
|
-
// elliptic: new EC('secp256k1') / new ec(...)
|
|
291
|
-
add(RE_ELLIPTIC_EC, {
|
|
292
|
-
ruleId: "elliptic-ec",
|
|
293
|
-
title: "elliptic curve instantiation",
|
|
294
|
-
category: "signature",
|
|
295
|
-
severity: "high",
|
|
296
|
-
confidence: "high",
|
|
297
|
-
algorithm: "ECDSA",
|
|
298
|
-
hndl: false,
|
|
299
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
300
|
-
message: "The `elliptic` library implements classical ECDSA/ECDH, both broken by Shor's algorithm.",
|
|
301
|
-
});
|
|
302
|
-
// Direct secp256k1 API usage: secp.sign / getPublicKey / getSharedSecret.
|
|
303
|
-
add(RE_SECP256K1, {
|
|
304
|
-
ruleId: "secp256k1-usage",
|
|
305
|
-
title: "secp256k1 ECDSA/ECDH usage",
|
|
306
|
-
category: "signature",
|
|
307
|
-
severity: "high",
|
|
308
|
-
confidence: "medium",
|
|
309
|
-
algorithm: "ECDSA",
|
|
310
|
-
hndl: false,
|
|
311
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
312
|
-
message: "Direct secp256k1 usage (ECDSA signatures / ECDH agreement) is classical and broken by Shor's algorithm.",
|
|
313
|
-
remediation: "ML-DSA-65 (FIPS 204) for signatures; hybrid X25519MLKEM768 for key agreement.",
|
|
314
|
-
});
|
|
315
|
-
// jsrsasign: KEYUTIL.generateKeypair('RSA'|'EC', ...) or KJUR.crypto.*
|
|
316
|
-
add(RE_JSRSASIGN_KEYGEN, {
|
|
317
|
-
ruleId: "jsrsasign-keygen",
|
|
318
|
-
title: "jsrsasign key generation",
|
|
319
|
-
category: "signature",
|
|
320
|
-
severity: "high",
|
|
321
|
-
confidence: "high",
|
|
322
|
-
algorithm: "unknown",
|
|
323
|
-
hndl: false,
|
|
324
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
325
|
-
message: "jsrsasign generates classical RSA/EC key pairs, which are not quantum-safe.",
|
|
326
|
-
remediation: "ML-KEM-768 (FIPS 203) / ML-DSA-65 (FIPS 204)",
|
|
327
|
-
});
|
|
328
|
-
add(RE_JSRSASIGN_SIGN, {
|
|
329
|
-
ruleId: "jsrsasign-sign",
|
|
330
|
-
title: "jsrsasign signature",
|
|
331
|
-
category: "signature",
|
|
332
|
-
severity: "high",
|
|
333
|
-
confidence: "high",
|
|
334
|
-
algorithm: "unknown",
|
|
335
|
-
hndl: false,
|
|
336
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
337
|
-
message: "jsrsasign signing uses classical RSA/ECDSA signatures, forgeable by a quantum attacker.",
|
|
338
|
-
remediation: "ML-DSA-65 (FIPS 204)",
|
|
339
|
-
});
|
|
340
|
-
// node-rsa: new NodeRSA(...)
|
|
341
|
-
add(RE_NODE_RSA, {
|
|
342
|
-
ruleId: "node-rsa",
|
|
343
|
-
title: "node-rsa key/usage",
|
|
344
|
-
category: "kem",
|
|
345
|
-
severity: "high",
|
|
346
|
-
confidence: "high",
|
|
347
|
-
algorithm: "RSA",
|
|
348
|
-
hndl: true,
|
|
349
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
350
|
-
message: "node-rsa wraps classical RSA encryption/signing, which is not quantum-safe.",
|
|
351
|
-
});
|
|
431
|
+
const add = (re, rule) => eachMatch(re, content, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
432
|
+
add(RE_FORGE_RSA, RULE_FORGE_RSA); // node-forge: pki.rsa.generateKeyPair(...)
|
|
433
|
+
add(RE_FORGE_ED25519, RULE_FORGE_ED25519); // node-forge: forge.ed25519.*
|
|
434
|
+
add(RE_ELLIPTIC_EC, RULE_ELLIPTIC_EC); // elliptic: new EC('secp256k1')
|
|
435
|
+
add(RE_SECP256K1, RULE_SECP256K1); // secp.sign / getPublicKey / getSharedSecret
|
|
436
|
+
add(RE_JSRSASIGN_KEYGEN, RULE_JSRSASIGN_KEYGEN); // jsrsasign: KEYUTIL.generateKeypair(...)
|
|
437
|
+
add(RE_JSRSASIGN_SIGN, RULE_JSRSASIGN_SIGN); // jsrsasign: KJUR.crypto.*
|
|
438
|
+
add(RE_NODE_RSA, RULE_NODE_RSA_LIB); // node-rsa: new NodeRSA(...)
|
|
352
439
|
return findings;
|
|
353
440
|
},
|
|
354
441
|
};
|
|
355
442
|
/* -------------------------------------------------------------------------- */
|
|
356
443
|
/* JWT / JOSE / COSE algorithm strings */
|
|
357
444
|
/* -------------------------------------------------------------------------- */
|
|
445
|
+
const RULE_JWT_ALG = {
|
|
446
|
+
id: "jwt-classical-alg",
|
|
447
|
+
title: "Classical JWT/JOSE algorithm",
|
|
448
|
+
description: "JWS alg tokens (RS/PS/ES/EdDSA)",
|
|
449
|
+
category: "signature",
|
|
450
|
+
severity: "high",
|
|
451
|
+
confidence: "medium",
|
|
452
|
+
algorithm: "unknown",
|
|
453
|
+
hndl: false,
|
|
454
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
455
|
+
message: "A classical JWT/JOSE signature algorithm is used, forgeable by a quantum attacker.",
|
|
456
|
+
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
457
|
+
};
|
|
458
|
+
const RULE_JOSE_ECDH = {
|
|
459
|
+
id: "jose-ecdh-es",
|
|
460
|
+
title: "JOSE ECDH-ES key agreement",
|
|
461
|
+
description: "JOSE ECDH-ES / ECDH-ES+A*KW key agreement",
|
|
462
|
+
category: "key-exchange",
|
|
463
|
+
severity: "high",
|
|
464
|
+
confidence: "medium",
|
|
465
|
+
algorithm: "ECDH",
|
|
466
|
+
hndl: true,
|
|
467
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
468
|
+
message: "JOSE ECDH-ES performs classical ECDH key agreement — harvest-now-decrypt-later exposed.",
|
|
469
|
+
remediation: "Track IETF PQC JOSE/COSE; adopt hybrid X25519MLKEM768 KEM-based encryption.",
|
|
470
|
+
};
|
|
358
471
|
/**
|
|
359
472
|
* Detects classical signature algorithm identifiers used by JWT/JOSE, plus
|
|
360
473
|
* ECDH-ES key-agreement identifiers (HNDL-exposed). These appear as string
|
|
@@ -365,6 +478,7 @@ const jwtDetector = {
|
|
|
365
478
|
description: "Classical JWT/JOSE algorithms (RS/PS/ES/EdDSA) and ECDH-ES key agreement",
|
|
366
479
|
scope: "source",
|
|
367
480
|
language: "js",
|
|
481
|
+
rules: [RULE_JWT_ALG, RULE_JOSE_ECDH],
|
|
368
482
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
369
483
|
detect({ file, content }) {
|
|
370
484
|
const findings = [];
|
|
@@ -378,40 +492,17 @@ const jwtDetector = {
|
|
|
378
492
|
algorithm = "EdDSA";
|
|
379
493
|
else
|
|
380
494
|
algorithm = "ECDSA"; // ES*
|
|
381
|
-
findings.push(
|
|
382
|
-
ruleId: "jwt-classical-alg",
|
|
495
|
+
findings.push(findingFromRule(RULE_JWT_ALG, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
383
496
|
title: `JWT/JOSE algorithm ${alg}`,
|
|
384
|
-
category: "signature",
|
|
385
|
-
severity: "high",
|
|
386
|
-
confidence: "medium",
|
|
387
497
|
algorithm,
|
|
388
|
-
hndl: false,
|
|
389
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
390
498
|
message: `JWT/JOSE algorithm "${alg}" is a classical signature, forgeable by a quantum attacker.`,
|
|
391
|
-
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
392
|
-
file,
|
|
393
|
-
content,
|
|
394
|
-
index: m.index,
|
|
395
|
-
matchLength: m[0].length,
|
|
396
499
|
}));
|
|
397
500
|
});
|
|
398
501
|
// JOSE ECDH-ES key agreement (and ECDH-ES+A*KW) — confidentiality, HNDL.
|
|
399
502
|
eachMatch(RE_JOSE_ECDH, content, (m) => {
|
|
400
|
-
findings.push(
|
|
401
|
-
ruleId: "jose-ecdh-es",
|
|
503
|
+
findings.push(findingFromRule(RULE_JOSE_ECDH, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
402
504
|
title: `JOSE key agreement ${m[1]}`,
|
|
403
|
-
category: "key-exchange",
|
|
404
|
-
severity: "high",
|
|
405
|
-
confidence: "medium",
|
|
406
|
-
algorithm: "ECDH",
|
|
407
|
-
hndl: true,
|
|
408
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
409
505
|
message: `JOSE "${m[1]}" performs classical ECDH key agreement — harvest-now-decrypt-later exposed.`,
|
|
410
|
-
remediation: "Track IETF PQC JOSE/COSE; adopt hybrid X25519MLKEM768 KEM-based encryption.",
|
|
411
|
-
file,
|
|
412
|
-
content,
|
|
413
|
-
index: m.index,
|
|
414
|
-
matchLength: m[0].length,
|
|
415
506
|
}));
|
|
416
507
|
});
|
|
417
508
|
return findings;
|
|
@@ -420,6 +511,42 @@ const jwtDetector = {
|
|
|
420
511
|
/* -------------------------------------------------------------------------- */
|
|
421
512
|
/* TLS legacy configuration */
|
|
422
513
|
/* -------------------------------------------------------------------------- */
|
|
514
|
+
const RULE_TLS_LEGACY = {
|
|
515
|
+
id: "tls-legacy-version",
|
|
516
|
+
title: "Legacy TLS version pinned",
|
|
517
|
+
description: "minVersion/maxVersion/secureProtocol pinned to TLS 1.0/1.1",
|
|
518
|
+
category: "tls",
|
|
519
|
+
severity: "medium",
|
|
520
|
+
confidence: "high",
|
|
521
|
+
hndl: false,
|
|
522
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
523
|
+
message: "TLS 1.0/1.1 are deprecated and insecure; require TLS 1.3.",
|
|
524
|
+
remediation: "Set minVersion: 'TLSv1.3' and prefer PQC-hybrid key exchange.",
|
|
525
|
+
};
|
|
526
|
+
const RULE_TLS_REJECT = {
|
|
527
|
+
id: "tls-reject-unauthorized",
|
|
528
|
+
title: "TLS certificate verification disabled",
|
|
529
|
+
description: "rejectUnauthorized: false",
|
|
530
|
+
category: "tls",
|
|
531
|
+
severity: "high",
|
|
532
|
+
confidence: "high",
|
|
533
|
+
hndl: false,
|
|
534
|
+
cwe: CWE_CERT_VALIDATION,
|
|
535
|
+
message: "rejectUnauthorized:false disables TLS certificate verification (MITM risk).",
|
|
536
|
+
remediation: "Remove rejectUnauthorized:false; verify certificates properly.",
|
|
537
|
+
};
|
|
538
|
+
const RULE_TLS_WEAK_CIPHER = {
|
|
539
|
+
id: "tls-weak-cipher",
|
|
540
|
+
title: "Weak TLS cipher configured",
|
|
541
|
+
description: "weak/export cipher in a `ciphers` string",
|
|
542
|
+
category: "tls",
|
|
543
|
+
severity: "medium",
|
|
544
|
+
confidence: "medium",
|
|
545
|
+
hndl: false,
|
|
546
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
547
|
+
message: "A weak cipher is configured in the TLS ciphers list.",
|
|
548
|
+
remediation: "Use a modern AEAD cipher suite (TLS 1.3 defaults).",
|
|
549
|
+
};
|
|
423
550
|
/**
|
|
424
551
|
* Detects legacy / insecure TLS configuration expressed as JS object literals:
|
|
425
552
|
* forced TLS 1.0/1.1, disabled certificate verification, and weak ciphers.
|
|
@@ -431,21 +558,13 @@ const tlsDetector = {
|
|
|
431
558
|
description: "Legacy / insecure TLS configuration in JS objects",
|
|
432
559
|
scope: "config",
|
|
433
560
|
language: "js",
|
|
561
|
+
rules: [RULE_TLS_LEGACY, RULE_TLS_REJECT, RULE_TLS_WEAK_CIPHER],
|
|
434
562
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
435
563
|
detect({ file, content }) {
|
|
436
564
|
const findings = [];
|
|
437
565
|
// minVersion / maxVersion / secureProtocol pinned to TLS 1.0 or 1.1.
|
|
438
566
|
eachMatch(RE_TLS_LEGACY_VERSION, content, (m) => {
|
|
439
|
-
findings.push(
|
|
440
|
-
ruleId: "tls-legacy-version",
|
|
441
|
-
title: "Legacy TLS version pinned",
|
|
442
|
-
category: "tls",
|
|
443
|
-
severity: "medium",
|
|
444
|
-
confidence: "high",
|
|
445
|
-
hndl: false,
|
|
446
|
-
cwe: CWE_WEAK_STRENGTH,
|
|
447
|
-
message: "TLS 1.0/1.1 are deprecated and insecure; require TLS 1.3.",
|
|
448
|
-
remediation: "Set minVersion: 'TLSv1.3' and prefer PQC-hybrid key exchange.",
|
|
567
|
+
findings.push(findingFromRule(RULE_TLS_LEGACY, {
|
|
449
568
|
file,
|
|
450
569
|
content,
|
|
451
570
|
index: m.index,
|
|
@@ -454,16 +573,7 @@ const tlsDetector = {
|
|
|
454
573
|
});
|
|
455
574
|
// rejectUnauthorized: false — disables certificate verification.
|
|
456
575
|
eachMatch(RE_TLS_REJECT, content, (m) => {
|
|
457
|
-
findings.push(
|
|
458
|
-
ruleId: "tls-reject-unauthorized",
|
|
459
|
-
title: "TLS certificate verification disabled",
|
|
460
|
-
category: "tls",
|
|
461
|
-
severity: "high",
|
|
462
|
-
confidence: "high",
|
|
463
|
-
hndl: false,
|
|
464
|
-
cwe: CWE_CERT_VALIDATION,
|
|
465
|
-
message: "rejectUnauthorized:false disables TLS certificate verification (MITM risk).",
|
|
466
|
-
remediation: "Remove rejectUnauthorized:false; verify certificates properly.",
|
|
576
|
+
findings.push(findingFromRule(RULE_TLS_REJECT, {
|
|
467
577
|
file,
|
|
468
578
|
content,
|
|
469
579
|
index: m.index,
|
|
@@ -472,21 +582,7 @@ const tlsDetector = {
|
|
|
472
582
|
});
|
|
473
583
|
// Weak / export ciphers referenced in a ciphers string (bounded regex).
|
|
474
584
|
eachMatch(RE_TLS_WEAK_CIPHER, content, (m) => {
|
|
475
|
-
findings.push(
|
|
476
|
-
ruleId: "tls-weak-cipher",
|
|
477
|
-
title: "Weak TLS cipher configured",
|
|
478
|
-
category: "tls",
|
|
479
|
-
severity: "medium",
|
|
480
|
-
confidence: "medium",
|
|
481
|
-
hndl: false,
|
|
482
|
-
cwe: CWE_WEAK_STRENGTH,
|
|
483
|
-
message: `Weak cipher (${m[1]}) configured in the TLS ciphers list.`,
|
|
484
|
-
remediation: "Use a modern AEAD cipher suite (TLS 1.3 defaults).",
|
|
485
|
-
file,
|
|
486
|
-
content,
|
|
487
|
-
index: m.index,
|
|
488
|
-
matchLength: m[0].length,
|
|
489
|
-
}));
|
|
585
|
+
findings.push(findingFromRule(RULE_TLS_WEAK_CIPHER, { file, content, index: m.index, matchLength: m[0].length }, { message: `Weak cipher (${m[1]}) configured in the TLS ciphers list.` }));
|
|
490
586
|
});
|
|
491
587
|
return findings;
|
|
492
588
|
},
|
|
@@ -496,6 +592,33 @@ const tlsDetector = {
|
|
|
496
592
|
/* -------------------------------------------------------------------------- */
|
|
497
593
|
const RE_SSH_PUBKEY = /\b(ssh-rsa|ssh-ed25519|ssh-dss|ecdsa-sha2-nistp(?:256|384|521))\b/g;
|
|
498
594
|
const RE_CERT_SIG_ALG = /\b(sha(?:1|256|384|512)WithRSAEncryption|ecdsa-with-SHA(?:1|256|384|512)|rsassaPss|dsaWithSHA(?:1|256))\b/g;
|
|
595
|
+
const RULE_SSH_PUBKEY = {
|
|
596
|
+
id: "ssh-public-key",
|
|
597
|
+
title: "Classical SSH public key",
|
|
598
|
+
description: "ssh-rsa / ssh-ed25519 / ssh-dss / ecdsa-sha2-* public keys",
|
|
599
|
+
category: "certificate",
|
|
600
|
+
severity: "low",
|
|
601
|
+
confidence: "medium",
|
|
602
|
+
algorithm: "unknown",
|
|
603
|
+
hndl: false,
|
|
604
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
605
|
+
sensitive: true,
|
|
606
|
+
message: "A classical SSH public key is forgeable by a quantum attacker.",
|
|
607
|
+
remediation: "Plan migration to PQC-capable SSH (e.g. sntrup761x25519 KEX, PQC host keys).",
|
|
608
|
+
};
|
|
609
|
+
const RULE_CERT_SIG_ALG = {
|
|
610
|
+
id: "cert-signature-algorithm",
|
|
611
|
+
title: "Classical certificate signature algorithm",
|
|
612
|
+
description: "X.509/TLS certificate signature-algorithm identifiers",
|
|
613
|
+
category: "certificate",
|
|
614
|
+
severity: "low",
|
|
615
|
+
confidence: "medium",
|
|
616
|
+
algorithm: "unknown",
|
|
617
|
+
hndl: false,
|
|
618
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
619
|
+
message: "A classical certificate signature algorithm (RSA/ECDSA/DSA) is a quantum forgery surface.",
|
|
620
|
+
remediation: "Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
621
|
+
};
|
|
499
622
|
/**
|
|
500
623
|
* Detects classical SSH public keys (`authorized_keys` / `known_hosts` lines)
|
|
501
624
|
* and X.509 certificate signature-algorithm identifiers in any text file. These
|
|
@@ -507,6 +630,7 @@ const sshCertDetector = {
|
|
|
507
630
|
description: "SSH public keys and TLS/X.509 certificate signature algorithms in config",
|
|
508
631
|
scope: "config",
|
|
509
632
|
language: "any",
|
|
633
|
+
rules: [RULE_SSH_PUBKEY, RULE_CERT_SIG_ALG],
|
|
510
634
|
appliesTo: () => true,
|
|
511
635
|
detect({ file, content }) {
|
|
512
636
|
const findings = [];
|
|
@@ -520,22 +644,10 @@ const sshCertDetector = {
|
|
|
520
644
|
: tok === "ssh-dss"
|
|
521
645
|
? "DSA"
|
|
522
646
|
: "ECDSA";
|
|
523
|
-
findings.push(
|
|
524
|
-
ruleId: "ssh-public-key",
|
|
647
|
+
findings.push(findingFromRule(RULE_SSH_PUBKEY, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
525
648
|
title: `Classical SSH public key (${tok})`,
|
|
526
|
-
category: "certificate",
|
|
527
|
-
severity: "low",
|
|
528
|
-
confidence: "medium",
|
|
529
649
|
algorithm,
|
|
530
|
-
hndl: false,
|
|
531
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
532
|
-
sensitive: true,
|
|
533
650
|
message: `SSH public key type "${tok}" is a classical key forgeable by a quantum attacker.`,
|
|
534
|
-
remediation: "Plan migration to PQC-capable SSH (e.g. sntrup761x25519 KEX, PQC host keys).",
|
|
535
|
-
file,
|
|
536
|
-
content,
|
|
537
|
-
index: m.index,
|
|
538
|
-
matchLength: m[0].length,
|
|
539
651
|
}));
|
|
540
652
|
});
|
|
541
653
|
// X.509 / TLS certificate signature algorithm identifiers (forgery surface).
|
|
@@ -546,21 +658,10 @@ const sshCertDetector = {
|
|
|
546
658
|
: tok.startsWith("ecdsa")
|
|
547
659
|
? "ECDSA"
|
|
548
660
|
: "DSA";
|
|
549
|
-
findings.push(
|
|
550
|
-
ruleId: "cert-signature-algorithm",
|
|
661
|
+
findings.push(findingFromRule(RULE_CERT_SIG_ALG, { file, content, index: m.index, matchLength: m[0].length }, {
|
|
551
662
|
title: `Classical certificate signature algorithm (${tok})`,
|
|
552
|
-
category: "certificate",
|
|
553
|
-
severity: "low",
|
|
554
|
-
confidence: "medium",
|
|
555
663
|
algorithm,
|
|
556
|
-
hndl: false,
|
|
557
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
558
664
|
message: `Certificate signature algorithm "${tok}" is classical (RSA/ECDSA/DSA) — a quantum forgery surface.`,
|
|
559
|
-
remediation: "Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
560
|
-
file,
|
|
561
|
-
content,
|
|
562
|
-
index: m.index,
|
|
563
|
-
matchLength: m[0].length,
|
|
564
665
|
}));
|
|
565
666
|
});
|
|
566
667
|
return findings;
|