@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/src/detectors/source.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Source-code detectors for classical, non-quantum-safe asymmetric cryptography
|
|
3
3
|
* in JavaScript / TypeScript. Each detector is pure and stateless: it declares
|
|
4
|
-
* which files it applies to
|
|
5
|
-
* contents.
|
|
4
|
+
* which files it applies to, the catalog of rules it can emit (`rules`), and
|
|
5
|
+
* returns zero or more Findings for a file's contents.
|
|
6
|
+
*
|
|
7
|
+
* Rule metadata (title / severity / category / remediation / …) lives ONCE in
|
|
8
|
+
* the per-detector `RuleMeta` declarations below, not inline in `detect()`.
|
|
9
|
+
* `detect()` builds findings from those declarations via `findingFromRule`,
|
|
10
|
+
* overriding only the fields that genuinely vary per match (e.g. the concrete
|
|
11
|
+
* algorithm family of a `generateKeyPair('ec')` call). The declarations are the
|
|
12
|
+
* catalog surfaced by the registry, SARIF `rules[]`, and the MCP resolver.
|
|
6
13
|
*
|
|
7
14
|
* The detection strategy is deliberately lexical (regex over source text). This
|
|
8
15
|
* is robust to bundling and partial files and keeps the package dependency-free.
|
|
@@ -19,12 +26,12 @@
|
|
|
19
26
|
* - EC keygen is ambiguous (an 'ec' key feeds BOTH ECDSA and ECDH); it is
|
|
20
27
|
* classified conservatively as key-exchange-capable (hndl:true).
|
|
21
28
|
*/
|
|
22
|
-
import type { Detector, Finding } from "../types.js";
|
|
29
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
23
30
|
import {
|
|
24
31
|
JS_TS_EXTENSIONS,
|
|
25
32
|
eachMatch,
|
|
33
|
+
findingFromRule,
|
|
26
34
|
hasExtension,
|
|
27
|
-
makeFinding,
|
|
28
35
|
nearSortedCall,
|
|
29
36
|
} from "../detect-utils.js";
|
|
30
37
|
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
@@ -84,22 +91,135 @@ const RE_TLS_WEAK_CIPHER =
|
|
|
84
91
|
/* Node.js `crypto` module */
|
|
85
92
|
/* -------------------------------------------------------------------------- */
|
|
86
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Rule catalog for the Node `crypto` detector. `node-crypto-keygen` and
|
|
96
|
+
* `node-crypto-dh-modp` refine some fields per match; the rest emit findings
|
|
97
|
+
* straight from these declarations.
|
|
98
|
+
*/
|
|
99
|
+
const RULE_NODE_KEYGEN: RuleMeta = {
|
|
100
|
+
id: "node-crypto-keygen",
|
|
101
|
+
title: "Classical key generation",
|
|
102
|
+
description: "crypto.generateKeyPair(Sync)('rsa'|'ec'|'dsa'|'dh'|…)",
|
|
103
|
+
category: "key-exchange",
|
|
104
|
+
severity: "high",
|
|
105
|
+
confidence: "high",
|
|
106
|
+
algorithm: "unknown",
|
|
107
|
+
hndl: true,
|
|
108
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
109
|
+
message: "Generates a classical asymmetric key pair, which is not quantum-safe.",
|
|
110
|
+
};
|
|
111
|
+
const RULE_NODE_SIGN: RuleMeta = {
|
|
112
|
+
id: "node-crypto-sign",
|
|
113
|
+
title: "Classical signature (createSign/createVerify)",
|
|
114
|
+
description: "crypto.createSign / crypto.createVerify",
|
|
115
|
+
category: "signature",
|
|
116
|
+
severity: "high",
|
|
117
|
+
confidence: "medium",
|
|
118
|
+
algorithm: "unknown",
|
|
119
|
+
hndl: false,
|
|
120
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
121
|
+
message:
|
|
122
|
+
"Uses createSign/createVerify, typically RSA, ECDSA or DSA — all forgeable by a quantum attacker.",
|
|
123
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
124
|
+
};
|
|
125
|
+
const RULE_NODE_SIGN_ONESHOT: RuleMeta = {
|
|
126
|
+
id: "node-crypto-sign-oneshot",
|
|
127
|
+
title: "Classical one-shot signature (crypto.sign/verify)",
|
|
128
|
+
description: "one-shot crypto.sign / crypto.verify (Node ≥ 12)",
|
|
129
|
+
category: "signature",
|
|
130
|
+
severity: "high",
|
|
131
|
+
confidence: "medium",
|
|
132
|
+
algorithm: "unknown",
|
|
133
|
+
hndl: false,
|
|
134
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
135
|
+
message:
|
|
136
|
+
"Uses the one-shot crypto.sign/crypto.verify API, typically RSA/ECDSA/EdDSA — forgeable by a quantum attacker.",
|
|
137
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
138
|
+
};
|
|
139
|
+
const RULE_NODE_DH: RuleMeta = {
|
|
140
|
+
id: "node-crypto-dh",
|
|
141
|
+
title: "Diffie-Hellman key exchange",
|
|
142
|
+
description: "crypto.createDiffieHellman(Group)",
|
|
143
|
+
category: "key-exchange",
|
|
144
|
+
severity: "high",
|
|
145
|
+
confidence: "high",
|
|
146
|
+
algorithm: "DH",
|
|
147
|
+
hndl: true,
|
|
148
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
149
|
+
message: "Finite-field Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
150
|
+
};
|
|
151
|
+
const RULE_NODE_DH_MODP: RuleMeta = {
|
|
152
|
+
id: "node-crypto-dh-modp",
|
|
153
|
+
title: "Diffie-Hellman MODP group",
|
|
154
|
+
description: "crypto.getDiffieHellman('modpN')",
|
|
155
|
+
category: "key-exchange",
|
|
156
|
+
severity: "high",
|
|
157
|
+
confidence: "high",
|
|
158
|
+
algorithm: "DH",
|
|
159
|
+
hndl: true,
|
|
160
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
161
|
+
message:
|
|
162
|
+
"Named finite-field DH MODP group is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
163
|
+
};
|
|
164
|
+
const RULE_NODE_ECDH: RuleMeta = {
|
|
165
|
+
id: "node-crypto-ecdh",
|
|
166
|
+
title: "ECDH key exchange",
|
|
167
|
+
description: "crypto.createECDH",
|
|
168
|
+
category: "key-exchange",
|
|
169
|
+
severity: "high",
|
|
170
|
+
confidence: "high",
|
|
171
|
+
algorithm: "ECDH",
|
|
172
|
+
hndl: true,
|
|
173
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
174
|
+
message:
|
|
175
|
+
"Elliptic-curve Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
176
|
+
};
|
|
177
|
+
const RULE_NODE_RSA_ENCRYPT: RuleMeta = {
|
|
178
|
+
id: "node-crypto-rsa-encrypt",
|
|
179
|
+
title: "RSA public-key encryption",
|
|
180
|
+
description: "crypto.publicEncrypt / crypto.privateDecrypt",
|
|
181
|
+
category: "kem",
|
|
182
|
+
severity: "high",
|
|
183
|
+
confidence: "high",
|
|
184
|
+
algorithm: "RSA",
|
|
185
|
+
hndl: true,
|
|
186
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
187
|
+
message:
|
|
188
|
+
"RSA public-key encryption is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.",
|
|
189
|
+
};
|
|
190
|
+
const RULE_NODE_DH_KEYOBJECT: RuleMeta = {
|
|
191
|
+
id: "node-crypto-dh-keyobject",
|
|
192
|
+
title: "Diffie-Hellman (KeyObject) key exchange",
|
|
193
|
+
description: "crypto.diffieHellman({ privateKey, publicKey })",
|
|
194
|
+
category: "key-exchange",
|
|
195
|
+
severity: "high",
|
|
196
|
+
confidence: "high",
|
|
197
|
+
algorithm: "ECDH",
|
|
198
|
+
hndl: true,
|
|
199
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
200
|
+
message:
|
|
201
|
+
"crypto.diffieHellman() performs a classical (EC)DH agreement (harvest-now-decrypt-later).",
|
|
202
|
+
};
|
|
203
|
+
|
|
87
204
|
/** Detects classical asymmetric usage from Node's built-in `crypto` module. */
|
|
88
205
|
const nodeCryptoDetector: Detector = {
|
|
89
206
|
id: "node-crypto",
|
|
90
207
|
description: "Classical asymmetric crypto via the Node.js `crypto` module",
|
|
91
208
|
scope: "source",
|
|
92
209
|
language: "js",
|
|
210
|
+
rules: [
|
|
211
|
+
RULE_NODE_KEYGEN,
|
|
212
|
+
RULE_NODE_SIGN,
|
|
213
|
+
RULE_NODE_SIGN_ONESHOT,
|
|
214
|
+
RULE_NODE_DH,
|
|
215
|
+
RULE_NODE_DH_MODP,
|
|
216
|
+
RULE_NODE_ECDH,
|
|
217
|
+
RULE_NODE_RSA_ENCRYPT,
|
|
218
|
+
RULE_NODE_DH_KEYOBJECT,
|
|
219
|
+
],
|
|
93
220
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
94
221
|
detect({ file, content }): Finding[] {
|
|
95
222
|
const findings: Finding[] = [];
|
|
96
|
-
const push = (
|
|
97
|
-
spec: Omit<Parameters<typeof makeFinding>[0], "file" | "content" | "index" | "matchLength">,
|
|
98
|
-
m: RegExpExecArray,
|
|
99
|
-
) =>
|
|
100
|
-
findings.push(
|
|
101
|
-
makeFinding({ ...spec, file, content, index: m.index, matchLength: m[0].length }),
|
|
102
|
-
);
|
|
103
223
|
|
|
104
224
|
// generateKeyPair(Sync)('rsa' | 'ec' | 'dsa' | 'dh' | 'x25519' | 'ed25519', ...)
|
|
105
225
|
eachMatch(RE_GENERATE_KEYPAIR, content, (m) => {
|
|
@@ -140,156 +260,103 @@ const nodeCryptoDetector: Detector = {
|
|
|
140
260
|
ed448: { algo: "EdDSA", cat: "signature", sev: "low", hndl: false, label: "Ed448" },
|
|
141
261
|
};
|
|
142
262
|
const info = map[type];
|
|
143
|
-
push(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
263
|
+
findings.push(
|
|
264
|
+
findingFromRule(
|
|
265
|
+
RULE_NODE_KEYGEN,
|
|
266
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
267
|
+
{
|
|
268
|
+
title: `${info.label} key generation`,
|
|
269
|
+
category: info.cat,
|
|
270
|
+
severity: info.sev,
|
|
271
|
+
algorithm: info.algo,
|
|
272
|
+
hndl: info.hndl,
|
|
273
|
+
message:
|
|
274
|
+
info.message ??
|
|
275
|
+
`Generates a classical ${info.label} key pair, which is not quantum-safe.`,
|
|
276
|
+
...(info.remediation ? { remediation: info.remediation } : {}),
|
|
277
|
+
},
|
|
278
|
+
),
|
|
159
279
|
);
|
|
160
280
|
});
|
|
161
281
|
|
|
162
282
|
// createSign / createVerify — RSA / ECDSA / DSA signatures.
|
|
163
283
|
eachMatch(RE_CREATE_SIGN_VERIFY, content, (m) => {
|
|
164
|
-
push(
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
algorithm: "unknown",
|
|
172
|
-
hndl: false,
|
|
173
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
174
|
-
message:
|
|
175
|
-
"Uses createSign/createVerify, typically RSA, ECDSA or DSA — all forgeable by a quantum attacker.",
|
|
176
|
-
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
177
|
-
},
|
|
178
|
-
m,
|
|
284
|
+
findings.push(
|
|
285
|
+
findingFromRule(RULE_NODE_SIGN, {
|
|
286
|
+
file,
|
|
287
|
+
content,
|
|
288
|
+
index: m.index,
|
|
289
|
+
matchLength: m[0].length,
|
|
290
|
+
}),
|
|
179
291
|
);
|
|
180
292
|
});
|
|
181
293
|
|
|
182
294
|
// One-shot crypto.sign(algorithm, data, key) / crypto.verify(...) (Node ≥ 12).
|
|
183
295
|
eachMatch(RE_ONESHOT_SIGN_VERIFY, content, (m) => {
|
|
184
|
-
push(
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
algorithm: "unknown",
|
|
192
|
-
hndl: false,
|
|
193
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
194
|
-
message:
|
|
195
|
-
"Uses the one-shot crypto.sign/crypto.verify API, typically RSA/ECDSA/EdDSA — forgeable by a quantum attacker.",
|
|
196
|
-
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
197
|
-
},
|
|
198
|
-
m,
|
|
296
|
+
findings.push(
|
|
297
|
+
findingFromRule(RULE_NODE_SIGN_ONESHOT, {
|
|
298
|
+
file,
|
|
299
|
+
content,
|
|
300
|
+
index: m.index,
|
|
301
|
+
matchLength: m[0].length,
|
|
302
|
+
}),
|
|
199
303
|
);
|
|
200
304
|
});
|
|
201
305
|
|
|
202
306
|
// createDiffieHellman / createDiffieHellmanGroup — finite-field DH key exchange.
|
|
203
307
|
eachMatch(RE_CREATE_DH, content, (m) => {
|
|
204
|
-
push(
|
|
205
|
-
{
|
|
206
|
-
ruleId: "node-crypto-dh",
|
|
207
|
-
title: "Diffie-Hellman key exchange",
|
|
208
|
-
category: "key-exchange",
|
|
209
|
-
severity: "high",
|
|
210
|
-
confidence: "high",
|
|
211
|
-
algorithm: "DH",
|
|
212
|
-
hndl: true,
|
|
213
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
214
|
-
message:
|
|
215
|
-
"Finite-field Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
216
|
-
},
|
|
217
|
-
m,
|
|
308
|
+
findings.push(
|
|
309
|
+
findingFromRule(RULE_NODE_DH, { file, content, index: m.index, matchLength: m[0].length }),
|
|
218
310
|
);
|
|
219
311
|
});
|
|
220
312
|
|
|
221
313
|
// getDiffieHellman('modpN') — named built-in finite-field MODP groups.
|
|
222
314
|
eachMatch(RE_GET_DH, content, (m) => {
|
|
223
|
-
push(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
233
|
-
message: `Named finite-field DH MODP group "${m[1]}" is broken by Shor's algorithm (harvest-now-decrypt-later).`,
|
|
234
|
-
},
|
|
235
|
-
m,
|
|
315
|
+
findings.push(
|
|
316
|
+
findingFromRule(
|
|
317
|
+
RULE_NODE_DH_MODP,
|
|
318
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
319
|
+
{
|
|
320
|
+
title: `Diffie-Hellman MODP group (${m[1]})`,
|
|
321
|
+
message: `Named finite-field DH MODP group "${m[1]}" is broken by Shor's algorithm (harvest-now-decrypt-later).`,
|
|
322
|
+
},
|
|
323
|
+
),
|
|
236
324
|
);
|
|
237
325
|
});
|
|
238
326
|
|
|
239
327
|
// createECDH — elliptic-curve Diffie-Hellman key exchange.
|
|
240
328
|
eachMatch(RE_CREATE_ECDH, content, (m) => {
|
|
241
|
-
push(
|
|
242
|
-
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
algorithm: "ECDH",
|
|
249
|
-
hndl: true,
|
|
250
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
251
|
-
message:
|
|
252
|
-
"Elliptic-curve Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
253
|
-
},
|
|
254
|
-
m,
|
|
329
|
+
findings.push(
|
|
330
|
+
findingFromRule(RULE_NODE_ECDH, {
|
|
331
|
+
file,
|
|
332
|
+
content,
|
|
333
|
+
index: m.index,
|
|
334
|
+
matchLength: m[0].length,
|
|
335
|
+
}),
|
|
255
336
|
);
|
|
256
337
|
});
|
|
257
338
|
|
|
258
339
|
// publicEncrypt / privateDecrypt — RSA encryption (KEM-like confidentiality).
|
|
259
340
|
eachMatch(RE_RSA_ENCRYPT, content, (m) => {
|
|
260
|
-
push(
|
|
261
|
-
{
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
algorithm: "RSA",
|
|
268
|
-
hndl: true,
|
|
269
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
270
|
-
message:
|
|
271
|
-
"RSA public-key encryption is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.",
|
|
272
|
-
},
|
|
273
|
-
m,
|
|
341
|
+
findings.push(
|
|
342
|
+
findingFromRule(RULE_NODE_RSA_ENCRYPT, {
|
|
343
|
+
file,
|
|
344
|
+
content,
|
|
345
|
+
index: m.index,
|
|
346
|
+
matchLength: m[0].length,
|
|
347
|
+
}),
|
|
274
348
|
);
|
|
275
349
|
});
|
|
276
350
|
|
|
277
351
|
// diffieHellman({ privateKey, publicKey }) — KeyObject-based DH/ECDH.
|
|
278
352
|
eachMatch(RE_DH_KEYOBJECT, content, (m) => {
|
|
279
|
-
push(
|
|
280
|
-
{
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
algorithm: "ECDH",
|
|
287
|
-
hndl: true,
|
|
288
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
289
|
-
message:
|
|
290
|
-
"crypto.diffieHellman() performs a classical (EC)DH agreement (harvest-now-decrypt-later).",
|
|
291
|
-
},
|
|
292
|
-
m,
|
|
353
|
+
findings.push(
|
|
354
|
+
findingFromRule(RULE_NODE_DH_KEYOBJECT, {
|
|
355
|
+
file,
|
|
356
|
+
content,
|
|
357
|
+
index: m.index,
|
|
358
|
+
matchLength: m[0].length,
|
|
359
|
+
}),
|
|
293
360
|
);
|
|
294
361
|
});
|
|
295
362
|
|
|
@@ -301,6 +368,19 @@ const nodeCryptoDetector: Detector = {
|
|
|
301
368
|
/* WebCrypto (SubtleCrypto) */
|
|
302
369
|
/* -------------------------------------------------------------------------- */
|
|
303
370
|
|
|
371
|
+
const RULE_WEBCRYPTO: RuleMeta = {
|
|
372
|
+
id: "webcrypto-classical",
|
|
373
|
+
title: "WebCrypto classical algorithm",
|
|
374
|
+
description: "classical asymmetric algorithm passed to SubtleCrypto",
|
|
375
|
+
category: "signature",
|
|
376
|
+
severity: "high",
|
|
377
|
+
confidence: "high",
|
|
378
|
+
algorithm: "unknown",
|
|
379
|
+
hndl: false,
|
|
380
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
381
|
+
message: "A classical asymmetric WebCrypto algorithm is used, which is not quantum-safe.",
|
|
382
|
+
};
|
|
383
|
+
|
|
304
384
|
/**
|
|
305
385
|
* Detects classical algorithms passed to WebCrypto's SubtleCrypto methods. The
|
|
306
386
|
* algorithm name can appear as a bare string ("RSA-OAEP") or as
|
|
@@ -311,6 +391,7 @@ const webCryptoDetector: Detector = {
|
|
|
311
391
|
description: "Classical asymmetric algorithms via WebCrypto SubtleCrypto",
|
|
312
392
|
scope: "source",
|
|
313
393
|
language: "js",
|
|
394
|
+
rules: [RULE_WEBCRYPTO],
|
|
314
395
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
315
396
|
detect({ file, content }): Finding[] {
|
|
316
397
|
const findings: Finding[] = [];
|
|
@@ -335,21 +416,17 @@ const webCryptoDetector: Detector = {
|
|
|
335
416
|
const category: Finding["category"] = isEcdh ? "key-exchange" : isKem ? "kem" : "signature";
|
|
336
417
|
const hndl = isKem || isEcdh;
|
|
337
418
|
findings.push(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
content,
|
|
350
|
-
index: m.index,
|
|
351
|
-
matchLength: m[0].length,
|
|
352
|
-
}),
|
|
419
|
+
findingFromRule(
|
|
420
|
+
RULE_WEBCRYPTO,
|
|
421
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
422
|
+
{
|
|
423
|
+
title: `WebCrypto ${m[1]}`,
|
|
424
|
+
category,
|
|
425
|
+
algorithm,
|
|
426
|
+
hndl,
|
|
427
|
+
message: `WebCrypto algorithm "${m[1]}" is classical asymmetric crypto and not quantum-safe.`,
|
|
428
|
+
},
|
|
429
|
+
),
|
|
353
430
|
);
|
|
354
431
|
});
|
|
355
432
|
|
|
@@ -361,119 +438,129 @@ const webCryptoDetector: Detector = {
|
|
|
361
438
|
/* Popular crypto libraries */
|
|
362
439
|
/* -------------------------------------------------------------------------- */
|
|
363
440
|
|
|
441
|
+
const RULE_FORGE_RSA: RuleMeta = {
|
|
442
|
+
id: "forge-rsa-keygen",
|
|
443
|
+
title: "node-forge RSA key generation",
|
|
444
|
+
description: "node-forge pki.rsa.generateKeyPair",
|
|
445
|
+
category: "kem",
|
|
446
|
+
severity: "high",
|
|
447
|
+
confidence: "high",
|
|
448
|
+
algorithm: "RSA",
|
|
449
|
+
hndl: true,
|
|
450
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
451
|
+
message: "node-forge generates a classical RSA key pair, which is not quantum-safe.",
|
|
452
|
+
};
|
|
453
|
+
const RULE_FORGE_ED25519: RuleMeta = {
|
|
454
|
+
id: "forge-ed25519",
|
|
455
|
+
title: "node-forge Ed25519 usage",
|
|
456
|
+
description: "node-forge forge.ed25519.*",
|
|
457
|
+
category: "signature",
|
|
458
|
+
severity: "low",
|
|
459
|
+
confidence: "high",
|
|
460
|
+
algorithm: "EdDSA",
|
|
461
|
+
hndl: false,
|
|
462
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
463
|
+
message: "node-forge Ed25519 is a modern but still classical signature scheme.",
|
|
464
|
+
};
|
|
465
|
+
const RULE_ELLIPTIC_EC: RuleMeta = {
|
|
466
|
+
id: "elliptic-ec",
|
|
467
|
+
title: "elliptic curve instantiation",
|
|
468
|
+
description: "the `elliptic` library — new EC(...)",
|
|
469
|
+
category: "signature",
|
|
470
|
+
severity: "high",
|
|
471
|
+
confidence: "high",
|
|
472
|
+
algorithm: "ECDSA",
|
|
473
|
+
hndl: false,
|
|
474
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
475
|
+
message:
|
|
476
|
+
"The `elliptic` library implements classical ECDSA/ECDH, both broken by Shor's algorithm.",
|
|
477
|
+
};
|
|
478
|
+
const RULE_SECP256K1: RuleMeta = {
|
|
479
|
+
id: "secp256k1-usage",
|
|
480
|
+
title: "secp256k1 ECDSA/ECDH usage",
|
|
481
|
+
description: "direct @noble/secp256k1-style API usage",
|
|
482
|
+
category: "signature",
|
|
483
|
+
severity: "high",
|
|
484
|
+
confidence: "medium",
|
|
485
|
+
algorithm: "ECDSA",
|
|
486
|
+
hndl: false,
|
|
487
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
488
|
+
message:
|
|
489
|
+
"Direct secp256k1 usage (ECDSA signatures / ECDH agreement) is classical and broken by Shor's algorithm.",
|
|
490
|
+
remediation: "ML-DSA-65 (FIPS 204) for signatures; hybrid X25519MLKEM768 for key agreement.",
|
|
491
|
+
};
|
|
492
|
+
const RULE_JSRSASIGN_KEYGEN: RuleMeta = {
|
|
493
|
+
id: "jsrsasign-keygen",
|
|
494
|
+
title: "jsrsasign key generation",
|
|
495
|
+
description: "jsrsasign KEYUTIL.generateKeypair",
|
|
496
|
+
category: "signature",
|
|
497
|
+
severity: "high",
|
|
498
|
+
confidence: "high",
|
|
499
|
+
algorithm: "unknown",
|
|
500
|
+
hndl: false,
|
|
501
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
502
|
+
message: "jsrsasign generates classical RSA/EC key pairs, which are not quantum-safe.",
|
|
503
|
+
remediation: "ML-KEM-768 (FIPS 203) / ML-DSA-65 (FIPS 204)",
|
|
504
|
+
};
|
|
505
|
+
const RULE_JSRSASIGN_SIGN: RuleMeta = {
|
|
506
|
+
id: "jsrsasign-sign",
|
|
507
|
+
title: "jsrsasign signature",
|
|
508
|
+
description: "jsrsasign KJUR.crypto.Signature / ECDSA",
|
|
509
|
+
category: "signature",
|
|
510
|
+
severity: "high",
|
|
511
|
+
confidence: "high",
|
|
512
|
+
algorithm: "unknown",
|
|
513
|
+
hndl: false,
|
|
514
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
515
|
+
message:
|
|
516
|
+
"jsrsasign signing uses classical RSA/ECDSA signatures, forgeable by a quantum attacker.",
|
|
517
|
+
remediation: "ML-DSA-65 (FIPS 204)",
|
|
518
|
+
};
|
|
519
|
+
const RULE_NODE_RSA_LIB: RuleMeta = {
|
|
520
|
+
id: "node-rsa",
|
|
521
|
+
title: "node-rsa key/usage",
|
|
522
|
+
description: "the `node-rsa` library — new NodeRSA(...)",
|
|
523
|
+
category: "kem",
|
|
524
|
+
severity: "high",
|
|
525
|
+
confidence: "high",
|
|
526
|
+
algorithm: "RSA",
|
|
527
|
+
hndl: true,
|
|
528
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
529
|
+
message: "node-rsa wraps classical RSA encryption/signing, which is not quantum-safe.",
|
|
530
|
+
};
|
|
531
|
+
|
|
364
532
|
/** Detects classical crypto from popular npm libraries used in source. */
|
|
365
533
|
const libraryDetector: Detector = {
|
|
366
534
|
id: "crypto-libs",
|
|
367
535
|
description: "Classical asymmetric crypto via node-forge, elliptic, jsrsasign, node-rsa",
|
|
368
536
|
scope: "source",
|
|
369
537
|
language: "js",
|
|
538
|
+
rules: [
|
|
539
|
+
RULE_FORGE_RSA,
|
|
540
|
+
RULE_FORGE_ED25519,
|
|
541
|
+
RULE_ELLIPTIC_EC,
|
|
542
|
+
RULE_SECP256K1,
|
|
543
|
+
RULE_JSRSASIGN_KEYGEN,
|
|
544
|
+
RULE_JSRSASIGN_SIGN,
|
|
545
|
+
RULE_NODE_RSA_LIB,
|
|
546
|
+
],
|
|
370
547
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
371
548
|
detect({ file, content }): Finding[] {
|
|
372
549
|
const findings: Finding[] = [];
|
|
373
|
-
const add = (
|
|
374
|
-
re: RegExp,
|
|
375
|
-
spec: Omit<Parameters<typeof makeFinding>[0], "file" | "content" | "index" | "matchLength">,
|
|
376
|
-
) =>
|
|
550
|
+
const add = (re: RegExp, rule: RuleMeta) =>
|
|
377
551
|
eachMatch(re, content, (m) =>
|
|
378
552
|
findings.push(
|
|
379
|
-
|
|
553
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
380
554
|
),
|
|
381
555
|
);
|
|
382
556
|
|
|
383
|
-
// node-forge: pki.rsa.generateKeyPair(...)
|
|
384
|
-
add(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
algorithm: "RSA",
|
|
391
|
-
hndl: true,
|
|
392
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
393
|
-
message: "node-forge generates a classical RSA key pair, which is not quantum-safe.",
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// node-forge: forge.ed25519.* (classical EdDSA)
|
|
397
|
-
add(RE_FORGE_ED25519, {
|
|
398
|
-
ruleId: "forge-ed25519",
|
|
399
|
-
title: "node-forge Ed25519 usage",
|
|
400
|
-
category: "signature",
|
|
401
|
-
severity: "low",
|
|
402
|
-
confidence: "high",
|
|
403
|
-
algorithm: "EdDSA",
|
|
404
|
-
hndl: false,
|
|
405
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
406
|
-
message: "node-forge Ed25519 is a modern but still classical signature scheme.",
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// elliptic: new EC('secp256k1') / new ec(...)
|
|
410
|
-
add(RE_ELLIPTIC_EC, {
|
|
411
|
-
ruleId: "elliptic-ec",
|
|
412
|
-
title: "elliptic curve instantiation",
|
|
413
|
-
category: "signature",
|
|
414
|
-
severity: "high",
|
|
415
|
-
confidence: "high",
|
|
416
|
-
algorithm: "ECDSA",
|
|
417
|
-
hndl: false,
|
|
418
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
419
|
-
message:
|
|
420
|
-
"The `elliptic` library implements classical ECDSA/ECDH, both broken by Shor's algorithm.",
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// Direct secp256k1 API usage: secp.sign / getPublicKey / getSharedSecret.
|
|
424
|
-
add(RE_SECP256K1, {
|
|
425
|
-
ruleId: "secp256k1-usage",
|
|
426
|
-
title: "secp256k1 ECDSA/ECDH usage",
|
|
427
|
-
category: "signature",
|
|
428
|
-
severity: "high",
|
|
429
|
-
confidence: "medium",
|
|
430
|
-
algorithm: "ECDSA",
|
|
431
|
-
hndl: false,
|
|
432
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
433
|
-
message:
|
|
434
|
-
"Direct secp256k1 usage (ECDSA signatures / ECDH agreement) is classical and broken by Shor's algorithm.",
|
|
435
|
-
remediation: "ML-DSA-65 (FIPS 204) for signatures; hybrid X25519MLKEM768 for key agreement.",
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// jsrsasign: KEYUTIL.generateKeypair('RSA'|'EC', ...) or KJUR.crypto.*
|
|
439
|
-
add(RE_JSRSASIGN_KEYGEN, {
|
|
440
|
-
ruleId: "jsrsasign-keygen",
|
|
441
|
-
title: "jsrsasign key generation",
|
|
442
|
-
category: "signature",
|
|
443
|
-
severity: "high",
|
|
444
|
-
confidence: "high",
|
|
445
|
-
algorithm: "unknown",
|
|
446
|
-
hndl: false,
|
|
447
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
448
|
-
message: "jsrsasign generates classical RSA/EC key pairs, which are not quantum-safe.",
|
|
449
|
-
remediation: "ML-KEM-768 (FIPS 203) / ML-DSA-65 (FIPS 204)",
|
|
450
|
-
});
|
|
451
|
-
add(RE_JSRSASIGN_SIGN, {
|
|
452
|
-
ruleId: "jsrsasign-sign",
|
|
453
|
-
title: "jsrsasign signature",
|
|
454
|
-
category: "signature",
|
|
455
|
-
severity: "high",
|
|
456
|
-
confidence: "high",
|
|
457
|
-
algorithm: "unknown",
|
|
458
|
-
hndl: false,
|
|
459
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
460
|
-
message:
|
|
461
|
-
"jsrsasign signing uses classical RSA/ECDSA signatures, forgeable by a quantum attacker.",
|
|
462
|
-
remediation: "ML-DSA-65 (FIPS 204)",
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// node-rsa: new NodeRSA(...)
|
|
466
|
-
add(RE_NODE_RSA, {
|
|
467
|
-
ruleId: "node-rsa",
|
|
468
|
-
title: "node-rsa key/usage",
|
|
469
|
-
category: "kem",
|
|
470
|
-
severity: "high",
|
|
471
|
-
confidence: "high",
|
|
472
|
-
algorithm: "RSA",
|
|
473
|
-
hndl: true,
|
|
474
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
475
|
-
message: "node-rsa wraps classical RSA encryption/signing, which is not quantum-safe.",
|
|
476
|
-
});
|
|
557
|
+
add(RE_FORGE_RSA, RULE_FORGE_RSA); // node-forge: pki.rsa.generateKeyPair(...)
|
|
558
|
+
add(RE_FORGE_ED25519, RULE_FORGE_ED25519); // node-forge: forge.ed25519.*
|
|
559
|
+
add(RE_ELLIPTIC_EC, RULE_ELLIPTIC_EC); // elliptic: new EC('secp256k1')
|
|
560
|
+
add(RE_SECP256K1, RULE_SECP256K1); // secp.sign / getPublicKey / getSharedSecret
|
|
561
|
+
add(RE_JSRSASIGN_KEYGEN, RULE_JSRSASIGN_KEYGEN); // jsrsasign: KEYUTIL.generateKeypair(...)
|
|
562
|
+
add(RE_JSRSASIGN_SIGN, RULE_JSRSASIGN_SIGN); // jsrsasign: KJUR.crypto.*
|
|
563
|
+
add(RE_NODE_RSA, RULE_NODE_RSA_LIB); // node-rsa: new NodeRSA(...)
|
|
477
564
|
|
|
478
565
|
return findings;
|
|
479
566
|
},
|
|
@@ -483,6 +570,34 @@ const libraryDetector: Detector = {
|
|
|
483
570
|
/* JWT / JOSE / COSE algorithm strings */
|
|
484
571
|
/* -------------------------------------------------------------------------- */
|
|
485
572
|
|
|
573
|
+
const RULE_JWT_ALG: RuleMeta = {
|
|
574
|
+
id: "jwt-classical-alg",
|
|
575
|
+
title: "Classical JWT/JOSE algorithm",
|
|
576
|
+
description: "JWS alg tokens (RS/PS/ES/EdDSA)",
|
|
577
|
+
category: "signature",
|
|
578
|
+
severity: "high",
|
|
579
|
+
confidence: "medium",
|
|
580
|
+
algorithm: "unknown",
|
|
581
|
+
hndl: false,
|
|
582
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
583
|
+
message: "A classical JWT/JOSE signature algorithm is used, forgeable by a quantum attacker.",
|
|
584
|
+
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
585
|
+
};
|
|
586
|
+
const RULE_JOSE_ECDH: RuleMeta = {
|
|
587
|
+
id: "jose-ecdh-es",
|
|
588
|
+
title: "JOSE ECDH-ES key agreement",
|
|
589
|
+
description: "JOSE ECDH-ES / ECDH-ES+A*KW key agreement",
|
|
590
|
+
category: "key-exchange",
|
|
591
|
+
severity: "high",
|
|
592
|
+
confidence: "medium",
|
|
593
|
+
algorithm: "ECDH",
|
|
594
|
+
hndl: true,
|
|
595
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
596
|
+
message:
|
|
597
|
+
"JOSE ECDH-ES performs classical ECDH key agreement — harvest-now-decrypt-later exposed.",
|
|
598
|
+
remediation: "Track IETF PQC JOSE/COSE; adopt hybrid X25519MLKEM768 KEM-based encryption.",
|
|
599
|
+
};
|
|
600
|
+
|
|
486
601
|
/**
|
|
487
602
|
* Detects classical signature algorithm identifiers used by JWT/JOSE, plus
|
|
488
603
|
* ECDH-ES key-agreement identifiers (HNDL-exposed). These appear as string
|
|
@@ -493,6 +608,7 @@ const jwtDetector: Detector = {
|
|
|
493
608
|
description: "Classical JWT/JOSE algorithms (RS/PS/ES/EdDSA) and ECDH-ES key agreement",
|
|
494
609
|
scope: "source",
|
|
495
610
|
language: "js",
|
|
611
|
+
rules: [RULE_JWT_ALG, RULE_JOSE_ECDH],
|
|
496
612
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
497
613
|
detect({ file, content }): Finding[] {
|
|
498
614
|
const findings: Finding[] = [];
|
|
@@ -505,45 +621,29 @@ const jwtDetector: Detector = {
|
|
|
505
621
|
else if (alg === "EdDSA") algorithm = "EdDSA";
|
|
506
622
|
else algorithm = "ECDSA"; // ES*
|
|
507
623
|
findings.push(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
message: `JWT/JOSE algorithm "${alg}" is a classical signature, forgeable by a quantum attacker.`,
|
|
518
|
-
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
519
|
-
file,
|
|
520
|
-
content,
|
|
521
|
-
index: m.index,
|
|
522
|
-
matchLength: m[0].length,
|
|
523
|
-
}),
|
|
624
|
+
findingFromRule(
|
|
625
|
+
RULE_JWT_ALG,
|
|
626
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
627
|
+
{
|
|
628
|
+
title: `JWT/JOSE algorithm ${alg}`,
|
|
629
|
+
algorithm,
|
|
630
|
+
message: `JWT/JOSE algorithm "${alg}" is a classical signature, forgeable by a quantum attacker.`,
|
|
631
|
+
},
|
|
632
|
+
),
|
|
524
633
|
);
|
|
525
634
|
});
|
|
526
635
|
|
|
527
636
|
// JOSE ECDH-ES key agreement (and ECDH-ES+A*KW) — confidentiality, HNDL.
|
|
528
637
|
eachMatch(RE_JOSE_ECDH, content, (m) => {
|
|
529
638
|
findings.push(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
cwe: CWE_BROKEN_CRYPTO,
|
|
539
|
-
message: `JOSE "${m[1]}" performs classical ECDH key agreement — harvest-now-decrypt-later exposed.`,
|
|
540
|
-
remediation:
|
|
541
|
-
"Track IETF PQC JOSE/COSE; adopt hybrid X25519MLKEM768 KEM-based encryption.",
|
|
542
|
-
file,
|
|
543
|
-
content,
|
|
544
|
-
index: m.index,
|
|
545
|
-
matchLength: m[0].length,
|
|
546
|
-
}),
|
|
639
|
+
findingFromRule(
|
|
640
|
+
RULE_JOSE_ECDH,
|
|
641
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
642
|
+
{
|
|
643
|
+
title: `JOSE key agreement ${m[1]}`,
|
|
644
|
+
message: `JOSE "${m[1]}" performs classical ECDH key agreement — harvest-now-decrypt-later exposed.`,
|
|
645
|
+
},
|
|
646
|
+
),
|
|
547
647
|
);
|
|
548
648
|
});
|
|
549
649
|
|
|
@@ -555,6 +655,43 @@ const jwtDetector: Detector = {
|
|
|
555
655
|
/* TLS legacy configuration */
|
|
556
656
|
/* -------------------------------------------------------------------------- */
|
|
557
657
|
|
|
658
|
+
const RULE_TLS_LEGACY: RuleMeta = {
|
|
659
|
+
id: "tls-legacy-version",
|
|
660
|
+
title: "Legacy TLS version pinned",
|
|
661
|
+
description: "minVersion/maxVersion/secureProtocol pinned to TLS 1.0/1.1",
|
|
662
|
+
category: "tls",
|
|
663
|
+
severity: "medium",
|
|
664
|
+
confidence: "high",
|
|
665
|
+
hndl: false,
|
|
666
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
667
|
+
message: "TLS 1.0/1.1 are deprecated and insecure; require TLS 1.3.",
|
|
668
|
+
remediation: "Set minVersion: 'TLSv1.3' and prefer PQC-hybrid key exchange.",
|
|
669
|
+
};
|
|
670
|
+
const RULE_TLS_REJECT: RuleMeta = {
|
|
671
|
+
id: "tls-reject-unauthorized",
|
|
672
|
+
title: "TLS certificate verification disabled",
|
|
673
|
+
description: "rejectUnauthorized: false",
|
|
674
|
+
category: "tls",
|
|
675
|
+
severity: "high",
|
|
676
|
+
confidence: "high",
|
|
677
|
+
hndl: false,
|
|
678
|
+
cwe: CWE_CERT_VALIDATION,
|
|
679
|
+
message: "rejectUnauthorized:false disables TLS certificate verification (MITM risk).",
|
|
680
|
+
remediation: "Remove rejectUnauthorized:false; verify certificates properly.",
|
|
681
|
+
};
|
|
682
|
+
const RULE_TLS_WEAK_CIPHER: RuleMeta = {
|
|
683
|
+
id: "tls-weak-cipher",
|
|
684
|
+
title: "Weak TLS cipher configured",
|
|
685
|
+
description: "weak/export cipher in a `ciphers` string",
|
|
686
|
+
category: "tls",
|
|
687
|
+
severity: "medium",
|
|
688
|
+
confidence: "medium",
|
|
689
|
+
hndl: false,
|
|
690
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
691
|
+
message: "A weak cipher is configured in the TLS ciphers list.",
|
|
692
|
+
remediation: "Use a modern AEAD cipher suite (TLS 1.3 defaults).",
|
|
693
|
+
};
|
|
694
|
+
|
|
558
695
|
/**
|
|
559
696
|
* Detects legacy / insecure TLS configuration expressed as JS object literals:
|
|
560
697
|
* forced TLS 1.0/1.1, disabled certificate verification, and weak ciphers.
|
|
@@ -566,6 +703,7 @@ const tlsDetector: Detector = {
|
|
|
566
703
|
description: "Legacy / insecure TLS configuration in JS objects",
|
|
567
704
|
scope: "config",
|
|
568
705
|
language: "js",
|
|
706
|
+
rules: [RULE_TLS_LEGACY, RULE_TLS_REJECT, RULE_TLS_WEAK_CIPHER],
|
|
569
707
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
570
708
|
detect({ file, content }): Finding[] {
|
|
571
709
|
const findings: Finding[] = [];
|
|
@@ -573,16 +711,7 @@ const tlsDetector: Detector = {
|
|
|
573
711
|
// minVersion / maxVersion / secureProtocol pinned to TLS 1.0 or 1.1.
|
|
574
712
|
eachMatch(RE_TLS_LEGACY_VERSION, content, (m) => {
|
|
575
713
|
findings.push(
|
|
576
|
-
|
|
577
|
-
ruleId: "tls-legacy-version",
|
|
578
|
-
title: "Legacy TLS version pinned",
|
|
579
|
-
category: "tls",
|
|
580
|
-
severity: "medium",
|
|
581
|
-
confidence: "high",
|
|
582
|
-
hndl: false,
|
|
583
|
-
cwe: CWE_WEAK_STRENGTH,
|
|
584
|
-
message: "TLS 1.0/1.1 are deprecated and insecure; require TLS 1.3.",
|
|
585
|
-
remediation: "Set minVersion: 'TLSv1.3' and prefer PQC-hybrid key exchange.",
|
|
714
|
+
findingFromRule(RULE_TLS_LEGACY, {
|
|
586
715
|
file,
|
|
587
716
|
content,
|
|
588
717
|
index: m.index,
|
|
@@ -594,16 +723,7 @@ const tlsDetector: Detector = {
|
|
|
594
723
|
// rejectUnauthorized: false — disables certificate verification.
|
|
595
724
|
eachMatch(RE_TLS_REJECT, content, (m) => {
|
|
596
725
|
findings.push(
|
|
597
|
-
|
|
598
|
-
ruleId: "tls-reject-unauthorized",
|
|
599
|
-
title: "TLS certificate verification disabled",
|
|
600
|
-
category: "tls",
|
|
601
|
-
severity: "high",
|
|
602
|
-
confidence: "high",
|
|
603
|
-
hndl: false,
|
|
604
|
-
cwe: CWE_CERT_VALIDATION,
|
|
605
|
-
message: "rejectUnauthorized:false disables TLS certificate verification (MITM risk).",
|
|
606
|
-
remediation: "Remove rejectUnauthorized:false; verify certificates properly.",
|
|
726
|
+
findingFromRule(RULE_TLS_REJECT, {
|
|
607
727
|
file,
|
|
608
728
|
content,
|
|
609
729
|
index: m.index,
|
|
@@ -615,21 +735,11 @@ const tlsDetector: Detector = {
|
|
|
615
735
|
// Weak / export ciphers referenced in a ciphers string (bounded regex).
|
|
616
736
|
eachMatch(RE_TLS_WEAK_CIPHER, content, (m) => {
|
|
617
737
|
findings.push(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
confidence: "medium",
|
|
624
|
-
hndl: false,
|
|
625
|
-
cwe: CWE_WEAK_STRENGTH,
|
|
626
|
-
message: `Weak cipher (${m[1]}) configured in the TLS ciphers list.`,
|
|
627
|
-
remediation: "Use a modern AEAD cipher suite (TLS 1.3 defaults).",
|
|
628
|
-
file,
|
|
629
|
-
content,
|
|
630
|
-
index: m.index,
|
|
631
|
-
matchLength: m[0].length,
|
|
632
|
-
}),
|
|
738
|
+
findingFromRule(
|
|
739
|
+
RULE_TLS_WEAK_CIPHER,
|
|
740
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
741
|
+
{ message: `Weak cipher (${m[1]}) configured in the TLS ciphers list.` },
|
|
742
|
+
),
|
|
633
743
|
);
|
|
634
744
|
});
|
|
635
745
|
|
|
@@ -645,6 +755,35 @@ const RE_SSH_PUBKEY = /\b(ssh-rsa|ssh-ed25519|ssh-dss|ecdsa-sha2-nistp(?:256|384
|
|
|
645
755
|
const RE_CERT_SIG_ALG =
|
|
646
756
|
/\b(sha(?:1|256|384|512)WithRSAEncryption|ecdsa-with-SHA(?:1|256|384|512)|rsassaPss|dsaWithSHA(?:1|256))\b/g;
|
|
647
757
|
|
|
758
|
+
const RULE_SSH_PUBKEY: RuleMeta = {
|
|
759
|
+
id: "ssh-public-key",
|
|
760
|
+
title: "Classical SSH public key",
|
|
761
|
+
description: "ssh-rsa / ssh-ed25519 / ssh-dss / ecdsa-sha2-* public keys",
|
|
762
|
+
category: "certificate",
|
|
763
|
+
severity: "low",
|
|
764
|
+
confidence: "medium",
|
|
765
|
+
algorithm: "unknown",
|
|
766
|
+
hndl: false,
|
|
767
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
768
|
+
sensitive: true,
|
|
769
|
+
message: "A classical SSH public key is forgeable by a quantum attacker.",
|
|
770
|
+
remediation: "Plan migration to PQC-capable SSH (e.g. sntrup761x25519 KEX, PQC host keys).",
|
|
771
|
+
};
|
|
772
|
+
const RULE_CERT_SIG_ALG: RuleMeta = {
|
|
773
|
+
id: "cert-signature-algorithm",
|
|
774
|
+
title: "Classical certificate signature algorithm",
|
|
775
|
+
description: "X.509/TLS certificate signature-algorithm identifiers",
|
|
776
|
+
category: "certificate",
|
|
777
|
+
severity: "low",
|
|
778
|
+
confidence: "medium",
|
|
779
|
+
algorithm: "unknown",
|
|
780
|
+
hndl: false,
|
|
781
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
782
|
+
message:
|
|
783
|
+
"A classical certificate signature algorithm (RSA/ECDSA/DSA) is a quantum forgery surface.",
|
|
784
|
+
remediation: "Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
785
|
+
};
|
|
786
|
+
|
|
648
787
|
/**
|
|
649
788
|
* Detects classical SSH public keys (`authorized_keys` / `known_hosts` lines)
|
|
650
789
|
* and X.509 certificate signature-algorithm identifiers in any text file. These
|
|
@@ -656,6 +795,7 @@ const sshCertDetector: Detector = {
|
|
|
656
795
|
description: "SSH public keys and TLS/X.509 certificate signature algorithms in config",
|
|
657
796
|
scope: "config",
|
|
658
797
|
language: "any",
|
|
798
|
+
rules: [RULE_SSH_PUBKEY, RULE_CERT_SIG_ALG],
|
|
659
799
|
appliesTo: () => true,
|
|
660
800
|
detect({ file, content }): Finding[] {
|
|
661
801
|
const findings: Finding[] = [];
|
|
@@ -671,24 +811,15 @@ const sshCertDetector: Detector = {
|
|
|
671
811
|
? "DSA"
|
|
672
812
|
: "ECDSA";
|
|
673
813
|
findings.push(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
sensitive: true,
|
|
684
|
-
message: `SSH public key type "${tok}" is a classical key forgeable by a quantum attacker.`,
|
|
685
|
-
remediation:
|
|
686
|
-
"Plan migration to PQC-capable SSH (e.g. sntrup761x25519 KEX, PQC host keys).",
|
|
687
|
-
file,
|
|
688
|
-
content,
|
|
689
|
-
index: m.index,
|
|
690
|
-
matchLength: m[0].length,
|
|
691
|
-
}),
|
|
814
|
+
findingFromRule(
|
|
815
|
+
RULE_SSH_PUBKEY,
|
|
816
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
817
|
+
{
|
|
818
|
+
title: `Classical SSH public key (${tok})`,
|
|
819
|
+
algorithm,
|
|
820
|
+
message: `SSH public key type "${tok}" is a classical key forgeable by a quantum attacker.`,
|
|
821
|
+
},
|
|
822
|
+
),
|
|
692
823
|
);
|
|
693
824
|
});
|
|
694
825
|
|
|
@@ -701,23 +832,15 @@ const sshCertDetector: Detector = {
|
|
|
701
832
|
? "ECDSA"
|
|
702
833
|
: "DSA";
|
|
703
834
|
findings.push(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
message: `Certificate signature algorithm "${tok}" is classical (RSA/ECDSA/DSA) — a quantum forgery surface.`,
|
|
714
|
-
remediation:
|
|
715
|
-
"Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
716
|
-
file,
|
|
717
|
-
content,
|
|
718
|
-
index: m.index,
|
|
719
|
-
matchLength: m[0].length,
|
|
720
|
-
}),
|
|
835
|
+
findingFromRule(
|
|
836
|
+
RULE_CERT_SIG_ALG,
|
|
837
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
838
|
+
{
|
|
839
|
+
title: `Classical certificate signature algorithm (${tok})`,
|
|
840
|
+
algorithm,
|
|
841
|
+
message: `Certificate signature algorithm "${tok}" is classical (RSA/ECDSA/DSA) — a quantum forgery surface.`,
|
|
842
|
+
},
|
|
843
|
+
),
|
|
721
844
|
);
|
|
722
845
|
});
|
|
723
846
|
|