@quantakrypto/core 0.2.2 → 0.4.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/agent-types.d.ts +48 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +2 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +93 -0
- package/dist/cache.js.map +1 -0
- package/dist/cbom.d.ts.map +1 -1
- package/dist/cbom.js +26 -1
- package/dist/cbom.js.map +1 -1
- package/dist/changed.d.ts.map +1 -1
- package/dist/changed.js +14 -2
- package/dist/changed.js.map +1 -1
- package/dist/codemods/config-toggle.d.ts +3 -0
- package/dist/codemods/config-toggle.d.ts.map +1 -0
- package/dist/codemods/config-toggle.js +26 -0
- package/dist/codemods/config-toggle.js.map +1 -0
- package/dist/codemods/registry.d.ts +26 -0
- package/dist/codemods/registry.d.ts.map +1 -0
- package/dist/codemods/registry.js +8 -0
- package/dist/codemods/registry.js.map +1 -0
- package/dist/comments.d.ts +45 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +164 -0
- package/dist/comments.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/dependencies.d.ts +31 -13
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +460 -40
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +70 -2
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +139 -17
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/c.d.ts +20 -0
- package/dist/detectors/c.d.ts.map +1 -0
- package/dist/detectors/c.js +104 -0
- package/dist/detectors/c.js.map +1 -0
- package/dist/detectors/csharp.d.ts +15 -0
- package/dist/detectors/csharp.d.ts.map +1 -0
- package/dist/detectors/csharp.js +76 -0
- package/dist/detectors/csharp.js.map +1 -0
- package/dist/detectors/go.d.ts +25 -0
- package/dist/detectors/go.d.ts.map +1 -0
- package/dist/detectors/go.js +132 -0
- package/dist/detectors/go.js.map +1 -0
- package/dist/detectors/java.d.ts +25 -0
- package/dist/detectors/java.d.ts.map +1 -0
- package/dist/detectors/java.js +200 -0
- package/dist/detectors/java.js.map +1 -0
- 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/python.d.ts +26 -0
- package/dist/detectors/python.d.ts.map +1 -0
- package/dist/detectors/python.js +171 -0
- package/dist/detectors/python.js.map +1 -0
- package/dist/detectors/ruby.d.ts +15 -0
- package/dist/detectors/ruby.d.ts.map +1 -0
- package/dist/detectors/ruby.js +75 -0
- package/dist/detectors/ruby.js.map +1 -0
- package/dist/detectors/rust.d.ts +13 -0
- package/dist/detectors/rust.d.ts.map +1 -0
- package/dist/detectors/rust.js +94 -0
- package/dist/detectors/rust.js.map +1 -0
- package/dist/detectors/source.d.ts +9 -2
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +435 -286
- package/dist/detectors/source.js.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/parallel.d.ts +4 -0
- package/dist/parallel.d.ts.map +1 -1
- package/dist/parallel.js +61 -8
- package/dist/parallel.js.map +1 -1
- package/dist/patch-policy.d.ts +22 -0
- package/dist/patch-policy.d.ts.map +1 -0
- package/dist/patch-policy.js +21 -0
- package/dist/patch-policy.js.map +1 -0
- package/dist/redact.d.ts +19 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +77 -0
- package/dist/redact.js.map +1 -0
- package/dist/registry.d.ts +29 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +62 -4
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +41 -0
- package/dist/remediate-pipeline.d.ts.map +1 -0
- package/dist/remediate-pipeline.js +38 -0
- package/dist/remediate-pipeline.js.map +1 -0
- package/dist/remediate-request.d.ts +21 -0
- package/dist/remediate-request.d.ts.map +1 -0
- package/dist/remediate-request.js +28 -0
- package/dist/remediate-request.js.map +1 -0
- package/dist/remediation.d.ts +10 -0
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +21 -8
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +9 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +72 -24
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +7 -3
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts +6 -5
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +59 -30
- package/dist/scan.js.map +1 -1
- package/dist/triage.d.ts +27 -0
- package/dist/triage.d.ts.map +1 -0
- package/dist/triage.js +33 -0
- package/dist/triage.js.map +1 -0
- package/dist/types.d.ts +106 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +65 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walk.d.ts +7 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +85 -11
- package/dist/walk.js.map +1 -1
- package/dist/worktree.d.ts +6 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +43 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-types.ts +52 -0
- package/src/cache.ts +120 -0
- package/src/cbom.ts +27 -1
- package/src/changed.ts +13 -4
- package/src/codemods/config-toggle.ts +35 -0
- package/src/codemods/registry.ts +31 -0
- package/src/comments.ts +180 -0
- package/src/config.ts +8 -0
- package/src/dependencies.ts +483 -51
- package/src/detect-utils.ts +180 -16
- package/src/detectors/c.ts +132 -0
- package/src/detectors/csharp.ts +98 -0
- package/src/detectors/go.ts +167 -0
- package/src/detectors/java.ts +235 -0
- package/src/detectors/pem.ts +124 -105
- package/src/detectors/python.ts +212 -0
- package/src/detectors/ruby.ts +97 -0
- package/src/detectors/rust.ts +112 -0
- package/src/detectors/source.ts +535 -364
- package/src/index.ts +47 -2
- package/src/parallel.ts +72 -7
- package/src/patch-policy.ts +42 -0
- package/src/redact.ts +92 -0
- package/src/registry.ts +71 -5
- package/src/remediate-pipeline.ts +81 -0
- package/src/remediate-request.ts +52 -0
- package/src/remediation.ts +27 -11
- package/src/report.ts +106 -24
- package/src/scan-worker.ts +18 -7
- package/src/scan.ts +74 -29
- package/src/triage.ts +58 -0
- package/src/types.ts +120 -3
- package/src/verify.ts +82 -0
- package/src/version.ts +1 -1
- package/src/walk.ts +81 -12
- package/src/worktree.ts +45 -0
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,13 @@
|
|
|
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,
|
|
32
|
+
JWT_HOST_EXTENSIONS,
|
|
25
33
|
eachMatch,
|
|
34
|
+
findingFromRule,
|
|
26
35
|
hasExtension,
|
|
27
|
-
makeFinding,
|
|
28
36
|
nearSortedCall,
|
|
29
37
|
} from "../detect-utils.js";
|
|
30
38
|
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
@@ -33,31 +41,41 @@ import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cw
|
|
|
33
41
|
/* Precompiled regexes (module scope — never recreated per file) */
|
|
34
42
|
/* -------------------------------------------------------------------------- */
|
|
35
43
|
|
|
44
|
+
// `rsa-pss` is listed before `rsa` so the alternation consumes the full token
|
|
45
|
+
// (ordered alternation would otherwise match `rsa` and reject the `-pss` tail).
|
|
36
46
|
const RE_GENERATE_KEYPAIR =
|
|
37
|
-
/generateKeyPair(?:Sync)?\s*\(\s*['"`](rsa|ec|dsa|dh|x25519|x448|ed25519|ed448)['"`]/g;
|
|
47
|
+
/generateKeyPair(?:Sync)?\s*\(\s*['"`](rsa-pss|rsa|ec|dsa|dh|x25519|x448|ed25519|ed448)['"`]/g;
|
|
38
48
|
const RE_CREATE_SIGN_VERIFY = /create(?:Sign|Verify)\s*\(/g;
|
|
39
|
-
// One-shot crypto.sign/verify(algorithm, data, key).
|
|
40
|
-
// fire inside identifiers like `assign(`
|
|
41
|
-
// createSign
|
|
42
|
-
//
|
|
43
|
-
//
|
|
49
|
+
// One-shot crypto.sign/verify(algorithm, data, key). A LOOKBEHIND (not a
|
|
50
|
+
// consumed char) anchors it so it doesn't fire inside identifiers like `assign(`
|
|
51
|
+
// or `createSign(` (handled by the dedicated createSign/createVerify rule) —
|
|
52
|
+
// consuming the preceding char used to push the match onto the previous line,
|
|
53
|
+
// corrupting the reported line/snippet and SARIF/baseline fingerprints. The
|
|
54
|
+
// first argument is either a quoted digest-algorithm string (RSA/ECDSA) or
|
|
55
|
+
// `null` — Node's EdDSA one-shot form is `crypto.sign(null, data, edKey)`.
|
|
44
56
|
const RE_ONESHOT_SIGN_VERIFY =
|
|
45
|
-
/(
|
|
57
|
+
/(?<![.\w])(?:crypto\.)?(sign|verify)\s*\(\s*(?:['"`][\w.-]+['"`]|null)\s*,/g;
|
|
46
58
|
const RE_CREATE_DH = /createDiffieHellman(?:Group)?\s*\(/g;
|
|
47
59
|
const RE_GET_DH = /getDiffieHellman\s*\(\s*['"`](modp\d+)['"`]\s*\)/g;
|
|
48
60
|
const RE_CREATE_ECDH = /createECDH\s*\(/g;
|
|
49
61
|
const RE_RSA_ENCRYPT = /(?:crypto\.)?(?:publicEncrypt|privateDecrypt)\s*\(/g;
|
|
50
62
|
const RE_DH_KEYOBJECT = /(?:crypto\.)?diffieHellman\s*\(\s*\{/g;
|
|
51
63
|
|
|
52
|
-
// WebCrypto.
|
|
53
|
-
|
|
64
|
+
// WebCrypto. Includes the newer curve algorithms (Ed25519/Ed448 signatures,
|
|
65
|
+
// X25519/X448 key agreement) shipping in modern SubtleCrypto implementations.
|
|
66
|
+
const RE_WEBCRYPTO_ALGO =
|
|
67
|
+
/\b(RSA-OAEP|RSA-PSS|RSASSA-PKCS1-v1_5|ECDH|ECDSA|Ed25519|Ed448|X25519|X448)\b/gi;
|
|
54
68
|
const RE_SUBTLE_CALL =
|
|
55
69
|
/subtle\s*\.\s*(generateKey|importKey|exportKey|deriveKey|deriveBits|sign|verify|encrypt|decrypt|wrapKey|unwrapKey)\s*\(/g;
|
|
56
70
|
|
|
57
71
|
// Libraries.
|
|
58
72
|
const RE_FORGE_RSA = /pki\.rsa\.generateKeyPair\s*\(/g;
|
|
59
73
|
const RE_FORGE_ED25519 = /forge\.ed25519\b/g;
|
|
60
|
-
|
|
74
|
+
// Require a curve-like first argument so `new EC("request-scope")` (a non-crypto
|
|
75
|
+
// `EC` class) is not flagged; the elliptic library is always constructed with a
|
|
76
|
+
// named curve (`new EC('secp256k1')`, `new EC('p256')`, …).
|
|
77
|
+
const RE_ELLIPTIC_EC =
|
|
78
|
+
/new\s+(?:elliptic\.)?ec\s*\(\s*['"`](?:sec[pt]|prime|nistp|curve|ed25519|ed448|brainpool|p-?(?:192|224|256|384|521)|x25519|x448)/gi;
|
|
61
79
|
const RE_JSRSASIGN_KEYGEN = /KEYUTIL\.generateKeypair\s*\(/g;
|
|
62
80
|
const RE_JSRSASIGN_SIGN = /KJUR\.crypto\.(?:Signature|ECDSA)\b/g;
|
|
63
81
|
const RE_NODE_RSA = /new\s+NodeRSA\s*\(/g;
|
|
@@ -84,22 +102,135 @@ const RE_TLS_WEAK_CIPHER =
|
|
|
84
102
|
/* Node.js `crypto` module */
|
|
85
103
|
/* -------------------------------------------------------------------------- */
|
|
86
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Rule catalog for the Node `crypto` detector. `node-crypto-keygen` and
|
|
107
|
+
* `node-crypto-dh-modp` refine some fields per match; the rest emit findings
|
|
108
|
+
* straight from these declarations.
|
|
109
|
+
*/
|
|
110
|
+
const RULE_NODE_KEYGEN: RuleMeta = {
|
|
111
|
+
id: "node-crypto-keygen",
|
|
112
|
+
title: "Classical key generation",
|
|
113
|
+
description: "crypto.generateKeyPair(Sync)('rsa'|'ec'|'dsa'|'dh'|…)",
|
|
114
|
+
category: "key-exchange",
|
|
115
|
+
severity: "high",
|
|
116
|
+
confidence: "high",
|
|
117
|
+
algorithm: "unknown",
|
|
118
|
+
hndl: true,
|
|
119
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
120
|
+
message: "Generates a classical asymmetric key pair, which is not quantum-safe.",
|
|
121
|
+
};
|
|
122
|
+
const RULE_NODE_SIGN: RuleMeta = {
|
|
123
|
+
id: "node-crypto-sign",
|
|
124
|
+
title: "Classical signature (createSign/createVerify)",
|
|
125
|
+
description: "crypto.createSign / crypto.createVerify",
|
|
126
|
+
category: "signature",
|
|
127
|
+
severity: "high",
|
|
128
|
+
confidence: "medium",
|
|
129
|
+
algorithm: "unknown",
|
|
130
|
+
hndl: false,
|
|
131
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
132
|
+
message:
|
|
133
|
+
"Uses createSign/createVerify, typically RSA, ECDSA or DSA — all forgeable by a quantum attacker.",
|
|
134
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
135
|
+
};
|
|
136
|
+
const RULE_NODE_SIGN_ONESHOT: RuleMeta = {
|
|
137
|
+
id: "node-crypto-sign-oneshot",
|
|
138
|
+
title: "Classical one-shot signature (crypto.sign/verify)",
|
|
139
|
+
description: "one-shot crypto.sign / crypto.verify (Node ≥ 12)",
|
|
140
|
+
category: "signature",
|
|
141
|
+
severity: "high",
|
|
142
|
+
confidence: "medium",
|
|
143
|
+
algorithm: "unknown",
|
|
144
|
+
hndl: false,
|
|
145
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
146
|
+
message:
|
|
147
|
+
"Uses the one-shot crypto.sign/crypto.verify API, typically RSA/ECDSA/EdDSA — forgeable by a quantum attacker.",
|
|
148
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
149
|
+
};
|
|
150
|
+
const RULE_NODE_DH: RuleMeta = {
|
|
151
|
+
id: "node-crypto-dh",
|
|
152
|
+
title: "Diffie-Hellman key exchange",
|
|
153
|
+
description: "crypto.createDiffieHellman(Group)",
|
|
154
|
+
category: "key-exchange",
|
|
155
|
+
severity: "high",
|
|
156
|
+
confidence: "high",
|
|
157
|
+
algorithm: "DH",
|
|
158
|
+
hndl: true,
|
|
159
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
160
|
+
message: "Finite-field Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
161
|
+
};
|
|
162
|
+
const RULE_NODE_DH_MODP: RuleMeta = {
|
|
163
|
+
id: "node-crypto-dh-modp",
|
|
164
|
+
title: "Diffie-Hellman MODP group",
|
|
165
|
+
description: "crypto.getDiffieHellman('modpN')",
|
|
166
|
+
category: "key-exchange",
|
|
167
|
+
severity: "high",
|
|
168
|
+
confidence: "high",
|
|
169
|
+
algorithm: "DH",
|
|
170
|
+
hndl: true,
|
|
171
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
172
|
+
message:
|
|
173
|
+
"Named finite-field DH MODP group is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
174
|
+
};
|
|
175
|
+
const RULE_NODE_ECDH: RuleMeta = {
|
|
176
|
+
id: "node-crypto-ecdh",
|
|
177
|
+
title: "ECDH key exchange",
|
|
178
|
+
description: "crypto.createECDH",
|
|
179
|
+
category: "key-exchange",
|
|
180
|
+
severity: "high",
|
|
181
|
+
confidence: "high",
|
|
182
|
+
algorithm: "ECDH",
|
|
183
|
+
hndl: true,
|
|
184
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
185
|
+
message:
|
|
186
|
+
"Elliptic-curve Diffie-Hellman is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
187
|
+
};
|
|
188
|
+
const RULE_NODE_RSA_ENCRYPT: RuleMeta = {
|
|
189
|
+
id: "node-crypto-rsa-encrypt",
|
|
190
|
+
title: "RSA public-key encryption",
|
|
191
|
+
description: "crypto.publicEncrypt / crypto.privateDecrypt",
|
|
192
|
+
category: "kem",
|
|
193
|
+
severity: "high",
|
|
194
|
+
confidence: "high",
|
|
195
|
+
algorithm: "RSA",
|
|
196
|
+
hndl: true,
|
|
197
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
198
|
+
message:
|
|
199
|
+
"RSA public-key encryption is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.",
|
|
200
|
+
};
|
|
201
|
+
const RULE_NODE_DH_KEYOBJECT: RuleMeta = {
|
|
202
|
+
id: "node-crypto-dh-keyobject",
|
|
203
|
+
title: "Diffie-Hellman (KeyObject) key exchange",
|
|
204
|
+
description: "crypto.diffieHellman({ privateKey, publicKey })",
|
|
205
|
+
category: "key-exchange",
|
|
206
|
+
severity: "high",
|
|
207
|
+
confidence: "high",
|
|
208
|
+
algorithm: "ECDH",
|
|
209
|
+
hndl: true,
|
|
210
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
211
|
+
message:
|
|
212
|
+
"crypto.diffieHellman() performs a classical (EC)DH agreement (harvest-now-decrypt-later).",
|
|
213
|
+
};
|
|
214
|
+
|
|
87
215
|
/** Detects classical asymmetric usage from Node's built-in `crypto` module. */
|
|
88
216
|
const nodeCryptoDetector: Detector = {
|
|
89
217
|
id: "node-crypto",
|
|
90
218
|
description: "Classical asymmetric crypto via the Node.js `crypto` module",
|
|
91
219
|
scope: "source",
|
|
92
220
|
language: "js",
|
|
221
|
+
rules: [
|
|
222
|
+
RULE_NODE_KEYGEN,
|
|
223
|
+
RULE_NODE_SIGN,
|
|
224
|
+
RULE_NODE_SIGN_ONESHOT,
|
|
225
|
+
RULE_NODE_DH,
|
|
226
|
+
RULE_NODE_DH_MODP,
|
|
227
|
+
RULE_NODE_ECDH,
|
|
228
|
+
RULE_NODE_RSA_ENCRYPT,
|
|
229
|
+
RULE_NODE_DH_KEYOBJECT,
|
|
230
|
+
],
|
|
93
231
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
94
232
|
detect({ file, content }): Finding[] {
|
|
95
233
|
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
234
|
|
|
104
235
|
// generateKeyPair(Sync)('rsa' | 'ec' | 'dsa' | 'dh' | 'x25519' | 'ed25519', ...)
|
|
105
236
|
eachMatch(RE_GENERATE_KEYPAIR, content, (m) => {
|
|
@@ -117,6 +248,18 @@ const nodeCryptoDetector: Detector = {
|
|
|
117
248
|
}
|
|
118
249
|
> = {
|
|
119
250
|
rsa: { algo: "RSA", cat: "kem", sev: "high", hndl: true, label: "RSA" },
|
|
251
|
+
// RSA-PSS is signature-only, so classify it as a (forgeable) signature
|
|
252
|
+
// rather than a KEM — no HNDL confidentiality exposure.
|
|
253
|
+
"rsa-pss": {
|
|
254
|
+
algo: "RSA",
|
|
255
|
+
cat: "signature",
|
|
256
|
+
sev: "high",
|
|
257
|
+
hndl: false,
|
|
258
|
+
label: "RSA-PSS",
|
|
259
|
+
message:
|
|
260
|
+
"Generates a classical RSA-PSS signing key, which is forgeable by a quantum attacker.",
|
|
261
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
262
|
+
},
|
|
120
263
|
// EC keys feed BOTH ECDSA (sign) and ECDH (key agreement). ECDH is
|
|
121
264
|
// HNDL-exposed, so classify conservatively as key-exchange-capable and
|
|
122
265
|
// surface both concerns rather than asserting signature-only (P0-4).
|
|
@@ -140,156 +283,103 @@ const nodeCryptoDetector: Detector = {
|
|
|
140
283
|
ed448: { algo: "EdDSA", cat: "signature", sev: "low", hndl: false, label: "Ed448" },
|
|
141
284
|
};
|
|
142
285
|
const info = map[type];
|
|
143
|
-
push(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
286
|
+
findings.push(
|
|
287
|
+
findingFromRule(
|
|
288
|
+
RULE_NODE_KEYGEN,
|
|
289
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
290
|
+
{
|
|
291
|
+
title: `${info.label} key generation`,
|
|
292
|
+
category: info.cat,
|
|
293
|
+
severity: info.sev,
|
|
294
|
+
algorithm: info.algo,
|
|
295
|
+
hndl: info.hndl,
|
|
296
|
+
message:
|
|
297
|
+
info.message ??
|
|
298
|
+
`Generates a classical ${info.label} key pair, which is not quantum-safe.`,
|
|
299
|
+
...(info.remediation ? { remediation: info.remediation } : {}),
|
|
300
|
+
},
|
|
301
|
+
),
|
|
159
302
|
);
|
|
160
303
|
});
|
|
161
304
|
|
|
162
305
|
// createSign / createVerify — RSA / ECDSA / DSA signatures.
|
|
163
306
|
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,
|
|
307
|
+
findings.push(
|
|
308
|
+
findingFromRule(RULE_NODE_SIGN, {
|
|
309
|
+
file,
|
|
310
|
+
content,
|
|
311
|
+
index: m.index,
|
|
312
|
+
matchLength: m[0].length,
|
|
313
|
+
}),
|
|
179
314
|
);
|
|
180
315
|
});
|
|
181
316
|
|
|
182
317
|
// One-shot crypto.sign(algorithm, data, key) / crypto.verify(...) (Node ≥ 12).
|
|
183
318
|
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,
|
|
319
|
+
findings.push(
|
|
320
|
+
findingFromRule(RULE_NODE_SIGN_ONESHOT, {
|
|
321
|
+
file,
|
|
322
|
+
content,
|
|
323
|
+
index: m.index,
|
|
324
|
+
matchLength: m[0].length,
|
|
325
|
+
}),
|
|
199
326
|
);
|
|
200
327
|
});
|
|
201
328
|
|
|
202
329
|
// createDiffieHellman / createDiffieHellmanGroup — finite-field DH key exchange.
|
|
203
330
|
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,
|
|
331
|
+
findings.push(
|
|
332
|
+
findingFromRule(RULE_NODE_DH, { file, content, index: m.index, matchLength: m[0].length }),
|
|
218
333
|
);
|
|
219
334
|
});
|
|
220
335
|
|
|
221
336
|
// getDiffieHellman('modpN') — named built-in finite-field MODP groups.
|
|
222
337
|
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,
|
|
338
|
+
findings.push(
|
|
339
|
+
findingFromRule(
|
|
340
|
+
RULE_NODE_DH_MODP,
|
|
341
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
342
|
+
{
|
|
343
|
+
title: `Diffie-Hellman MODP group (${m[1]})`,
|
|
344
|
+
message: `Named finite-field DH MODP group "${m[1]}" is broken by Shor's algorithm (harvest-now-decrypt-later).`,
|
|
345
|
+
},
|
|
346
|
+
),
|
|
236
347
|
);
|
|
237
348
|
});
|
|
238
349
|
|
|
239
350
|
// createECDH — elliptic-curve Diffie-Hellman key exchange.
|
|
240
351
|
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,
|
|
352
|
+
findings.push(
|
|
353
|
+
findingFromRule(RULE_NODE_ECDH, {
|
|
354
|
+
file,
|
|
355
|
+
content,
|
|
356
|
+
index: m.index,
|
|
357
|
+
matchLength: m[0].length,
|
|
358
|
+
}),
|
|
255
359
|
);
|
|
256
360
|
});
|
|
257
361
|
|
|
258
362
|
// publicEncrypt / privateDecrypt — RSA encryption (KEM-like confidentiality).
|
|
259
363
|
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,
|
|
364
|
+
findings.push(
|
|
365
|
+
findingFromRule(RULE_NODE_RSA_ENCRYPT, {
|
|
366
|
+
file,
|
|
367
|
+
content,
|
|
368
|
+
index: m.index,
|
|
369
|
+
matchLength: m[0].length,
|
|
370
|
+
}),
|
|
274
371
|
);
|
|
275
372
|
});
|
|
276
373
|
|
|
277
374
|
// diffieHellman({ privateKey, publicKey }) — KeyObject-based DH/ECDH.
|
|
278
375
|
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,
|
|
376
|
+
findings.push(
|
|
377
|
+
findingFromRule(RULE_NODE_DH_KEYOBJECT, {
|
|
378
|
+
file,
|
|
379
|
+
content,
|
|
380
|
+
index: m.index,
|
|
381
|
+
matchLength: m[0].length,
|
|
382
|
+
}),
|
|
293
383
|
);
|
|
294
384
|
});
|
|
295
385
|
|
|
@@ -301,6 +391,19 @@ const nodeCryptoDetector: Detector = {
|
|
|
301
391
|
/* WebCrypto (SubtleCrypto) */
|
|
302
392
|
/* -------------------------------------------------------------------------- */
|
|
303
393
|
|
|
394
|
+
const RULE_WEBCRYPTO: RuleMeta = {
|
|
395
|
+
id: "webcrypto-classical",
|
|
396
|
+
title: "WebCrypto classical algorithm",
|
|
397
|
+
description: "classical asymmetric algorithm passed to SubtleCrypto",
|
|
398
|
+
category: "signature",
|
|
399
|
+
severity: "high",
|
|
400
|
+
confidence: "high",
|
|
401
|
+
algorithm: "unknown",
|
|
402
|
+
hndl: false,
|
|
403
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
404
|
+
message: "A classical asymmetric WebCrypto algorithm is used, which is not quantum-safe.",
|
|
405
|
+
};
|
|
406
|
+
|
|
304
407
|
/**
|
|
305
408
|
* Detects classical algorithms passed to WebCrypto's SubtleCrypto methods. The
|
|
306
409
|
* algorithm name can appear as a bare string ("RSA-OAEP") or as
|
|
@@ -311,6 +414,7 @@ const webCryptoDetector: Detector = {
|
|
|
311
414
|
description: "Classical asymmetric algorithms via WebCrypto SubtleCrypto",
|
|
312
415
|
scope: "source",
|
|
313
416
|
language: "js",
|
|
417
|
+
rules: [RULE_WEBCRYPTO],
|
|
314
418
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
315
419
|
detect({ file, content }): Finding[] {
|
|
316
420
|
const findings: Finding[] = [];
|
|
@@ -325,31 +429,49 @@ const webCryptoDetector: Detector = {
|
|
|
325
429
|
eachMatch(RE_WEBCRYPTO_ALGO, content, (m) => {
|
|
326
430
|
if (!nearSortedCall(callIndexes, m.index, 400)) return;
|
|
327
431
|
const name = m[1].toUpperCase();
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
432
|
+
// Classify by algorithm: RSA-OAEP is KEM (HNDL); ECDH/X25519/X448 are key
|
|
433
|
+
// agreement (HNDL); RSA-PSS/RSASSA/ECDSA/Ed25519/Ed448 are signatures.
|
|
434
|
+
let algorithm: Finding["algorithm"];
|
|
435
|
+
let category: Finding["category"];
|
|
436
|
+
let hndl: boolean;
|
|
437
|
+
let severity: Finding["severity"] | undefined;
|
|
438
|
+
if (name.startsWith("RSA")) {
|
|
439
|
+
algorithm = "RSA";
|
|
440
|
+
const isKem = name === "RSA-OAEP";
|
|
441
|
+
category = isKem ? "kem" : "signature";
|
|
442
|
+
hndl = isKem;
|
|
443
|
+
} else if (name === "ECDH") {
|
|
444
|
+
algorithm = "ECDH";
|
|
445
|
+
category = "key-exchange";
|
|
446
|
+
hndl = true;
|
|
447
|
+
} else if (name === "X25519" || name === "X448") {
|
|
448
|
+
algorithm = name === "X448" ? "X448" : "X25519";
|
|
449
|
+
category = "key-exchange";
|
|
450
|
+
hndl = true;
|
|
451
|
+
severity = "low"; // modern but classical
|
|
452
|
+
} else if (name === "ED25519" || name === "ED448") {
|
|
453
|
+
algorithm = "EdDSA";
|
|
454
|
+
category = "signature";
|
|
455
|
+
hndl = false;
|
|
456
|
+
severity = "low";
|
|
457
|
+
} else {
|
|
458
|
+
algorithm = "ECDSA";
|
|
459
|
+
category = "signature";
|
|
460
|
+
hndl = false;
|
|
461
|
+
}
|
|
337
462
|
findings.push(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
index: m.index,
|
|
351
|
-
matchLength: m[0].length,
|
|
352
|
-
}),
|
|
463
|
+
findingFromRule(
|
|
464
|
+
RULE_WEBCRYPTO,
|
|
465
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
466
|
+
{
|
|
467
|
+
title: `WebCrypto ${m[1]}`,
|
|
468
|
+
category,
|
|
469
|
+
algorithm,
|
|
470
|
+
hndl,
|
|
471
|
+
...(severity ? { severity } : {}),
|
|
472
|
+
message: `WebCrypto algorithm "${m[1]}" is classical asymmetric crypto and not quantum-safe.`,
|
|
473
|
+
},
|
|
474
|
+
),
|
|
353
475
|
);
|
|
354
476
|
});
|
|
355
477
|
|
|
@@ -361,119 +483,129 @@ const webCryptoDetector: Detector = {
|
|
|
361
483
|
/* Popular crypto libraries */
|
|
362
484
|
/* -------------------------------------------------------------------------- */
|
|
363
485
|
|
|
486
|
+
const RULE_FORGE_RSA: RuleMeta = {
|
|
487
|
+
id: "forge-rsa-keygen",
|
|
488
|
+
title: "node-forge RSA key generation",
|
|
489
|
+
description: "node-forge pki.rsa.generateKeyPair",
|
|
490
|
+
category: "kem",
|
|
491
|
+
severity: "high",
|
|
492
|
+
confidence: "high",
|
|
493
|
+
algorithm: "RSA",
|
|
494
|
+
hndl: true,
|
|
495
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
496
|
+
message: "node-forge generates a classical RSA key pair, which is not quantum-safe.",
|
|
497
|
+
};
|
|
498
|
+
const RULE_FORGE_ED25519: RuleMeta = {
|
|
499
|
+
id: "forge-ed25519",
|
|
500
|
+
title: "node-forge Ed25519 usage",
|
|
501
|
+
description: "node-forge forge.ed25519.*",
|
|
502
|
+
category: "signature",
|
|
503
|
+
severity: "low",
|
|
504
|
+
confidence: "high",
|
|
505
|
+
algorithm: "EdDSA",
|
|
506
|
+
hndl: false,
|
|
507
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
508
|
+
message: "node-forge Ed25519 is a modern but still classical signature scheme.",
|
|
509
|
+
};
|
|
510
|
+
const RULE_ELLIPTIC_EC: RuleMeta = {
|
|
511
|
+
id: "elliptic-ec",
|
|
512
|
+
title: "elliptic curve instantiation",
|
|
513
|
+
description: "the `elliptic` library — new EC(...)",
|
|
514
|
+
category: "signature",
|
|
515
|
+
severity: "high",
|
|
516
|
+
confidence: "high",
|
|
517
|
+
algorithm: "ECDSA",
|
|
518
|
+
hndl: false,
|
|
519
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
520
|
+
message:
|
|
521
|
+
"The `elliptic` library implements classical ECDSA/ECDH, both broken by Shor's algorithm.",
|
|
522
|
+
};
|
|
523
|
+
const RULE_SECP256K1: RuleMeta = {
|
|
524
|
+
id: "secp256k1-usage",
|
|
525
|
+
title: "secp256k1 ECDSA/ECDH usage",
|
|
526
|
+
description: "direct @noble/secp256k1-style API usage",
|
|
527
|
+
category: "signature",
|
|
528
|
+
severity: "high",
|
|
529
|
+
confidence: "medium",
|
|
530
|
+
algorithm: "ECDSA",
|
|
531
|
+
hndl: false,
|
|
532
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
533
|
+
message:
|
|
534
|
+
"Direct secp256k1 usage (ECDSA signatures / ECDH agreement) is classical and broken by Shor's algorithm.",
|
|
535
|
+
remediation: "ML-DSA-65 (FIPS 204) for signatures; hybrid X25519MLKEM768 for key agreement.",
|
|
536
|
+
};
|
|
537
|
+
const RULE_JSRSASIGN_KEYGEN: RuleMeta = {
|
|
538
|
+
id: "jsrsasign-keygen",
|
|
539
|
+
title: "jsrsasign key generation",
|
|
540
|
+
description: "jsrsasign KEYUTIL.generateKeypair",
|
|
541
|
+
category: "signature",
|
|
542
|
+
severity: "high",
|
|
543
|
+
confidence: "high",
|
|
544
|
+
algorithm: "unknown",
|
|
545
|
+
hndl: false,
|
|
546
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
547
|
+
message: "jsrsasign generates classical RSA/EC key pairs, which are not quantum-safe.",
|
|
548
|
+
remediation: "ML-KEM-768 (FIPS 203) / ML-DSA-65 (FIPS 204)",
|
|
549
|
+
};
|
|
550
|
+
const RULE_JSRSASIGN_SIGN: RuleMeta = {
|
|
551
|
+
id: "jsrsasign-sign",
|
|
552
|
+
title: "jsrsasign signature",
|
|
553
|
+
description: "jsrsasign KJUR.crypto.Signature / ECDSA",
|
|
554
|
+
category: "signature",
|
|
555
|
+
severity: "high",
|
|
556
|
+
confidence: "high",
|
|
557
|
+
algorithm: "unknown",
|
|
558
|
+
hndl: false,
|
|
559
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
560
|
+
message:
|
|
561
|
+
"jsrsasign signing uses classical RSA/ECDSA signatures, forgeable by a quantum attacker.",
|
|
562
|
+
remediation: "ML-DSA-65 (FIPS 204)",
|
|
563
|
+
};
|
|
564
|
+
const RULE_NODE_RSA_LIB: RuleMeta = {
|
|
565
|
+
id: "node-rsa",
|
|
566
|
+
title: "node-rsa key/usage",
|
|
567
|
+
description: "the `node-rsa` library — new NodeRSA(...)",
|
|
568
|
+
category: "kem",
|
|
569
|
+
severity: "high",
|
|
570
|
+
confidence: "high",
|
|
571
|
+
algorithm: "RSA",
|
|
572
|
+
hndl: true,
|
|
573
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
574
|
+
message: "node-rsa wraps classical RSA encryption/signing, which is not quantum-safe.",
|
|
575
|
+
};
|
|
576
|
+
|
|
364
577
|
/** Detects classical crypto from popular npm libraries used in source. */
|
|
365
578
|
const libraryDetector: Detector = {
|
|
366
579
|
id: "crypto-libs",
|
|
367
580
|
description: "Classical asymmetric crypto via node-forge, elliptic, jsrsasign, node-rsa",
|
|
368
581
|
scope: "source",
|
|
369
582
|
language: "js",
|
|
583
|
+
rules: [
|
|
584
|
+
RULE_FORGE_RSA,
|
|
585
|
+
RULE_FORGE_ED25519,
|
|
586
|
+
RULE_ELLIPTIC_EC,
|
|
587
|
+
RULE_SECP256K1,
|
|
588
|
+
RULE_JSRSASIGN_KEYGEN,
|
|
589
|
+
RULE_JSRSASIGN_SIGN,
|
|
590
|
+
RULE_NODE_RSA_LIB,
|
|
591
|
+
],
|
|
370
592
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
371
593
|
detect({ file, content }): Finding[] {
|
|
372
594
|
const findings: Finding[] = [];
|
|
373
|
-
const add = (
|
|
374
|
-
re: RegExp,
|
|
375
|
-
spec: Omit<Parameters<typeof makeFinding>[0], "file" | "content" | "index" | "matchLength">,
|
|
376
|
-
) =>
|
|
595
|
+
const add = (re: RegExp, rule: RuleMeta) =>
|
|
377
596
|
eachMatch(re, content, (m) =>
|
|
378
597
|
findings.push(
|
|
379
|
-
|
|
598
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
380
599
|
),
|
|
381
600
|
);
|
|
382
601
|
|
|
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
|
-
});
|
|
602
|
+
add(RE_FORGE_RSA, RULE_FORGE_RSA); // node-forge: pki.rsa.generateKeyPair(...)
|
|
603
|
+
add(RE_FORGE_ED25519, RULE_FORGE_ED25519); // node-forge: forge.ed25519.*
|
|
604
|
+
add(RE_ELLIPTIC_EC, RULE_ELLIPTIC_EC); // elliptic: new EC('secp256k1')
|
|
605
|
+
add(RE_SECP256K1, RULE_SECP256K1); // secp.sign / getPublicKey / getSharedSecret
|
|
606
|
+
add(RE_JSRSASIGN_KEYGEN, RULE_JSRSASIGN_KEYGEN); // jsrsasign: KEYUTIL.generateKeypair(...)
|
|
607
|
+
add(RE_JSRSASIGN_SIGN, RULE_JSRSASIGN_SIGN); // jsrsasign: KJUR.crypto.*
|
|
608
|
+
add(RE_NODE_RSA, RULE_NODE_RSA_LIB); // node-rsa: new NodeRSA(...)
|
|
477
609
|
|
|
478
610
|
return findings;
|
|
479
611
|
},
|
|
@@ -483,6 +615,34 @@ const libraryDetector: Detector = {
|
|
|
483
615
|
/* JWT / JOSE / COSE algorithm strings */
|
|
484
616
|
/* -------------------------------------------------------------------------- */
|
|
485
617
|
|
|
618
|
+
const RULE_JWT_ALG: RuleMeta = {
|
|
619
|
+
id: "jwt-classical-alg",
|
|
620
|
+
title: "Classical JWT/JOSE algorithm",
|
|
621
|
+
description: "JWS alg tokens (RS/PS/ES/EdDSA)",
|
|
622
|
+
category: "signature",
|
|
623
|
+
severity: "high",
|
|
624
|
+
confidence: "medium",
|
|
625
|
+
algorithm: "unknown",
|
|
626
|
+
hndl: false,
|
|
627
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
628
|
+
message: "A classical JWT/JOSE signature algorithm is used, forgeable by a quantum attacker.",
|
|
629
|
+
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
630
|
+
};
|
|
631
|
+
const RULE_JOSE_ECDH: RuleMeta = {
|
|
632
|
+
id: "jose-ecdh-es",
|
|
633
|
+
title: "JOSE ECDH-ES key agreement",
|
|
634
|
+
description: "JOSE ECDH-ES / ECDH-ES+A*KW key agreement",
|
|
635
|
+
category: "key-exchange",
|
|
636
|
+
severity: "high",
|
|
637
|
+
confidence: "medium",
|
|
638
|
+
algorithm: "ECDH",
|
|
639
|
+
hndl: true,
|
|
640
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
641
|
+
message:
|
|
642
|
+
"JOSE ECDH-ES performs classical ECDH key agreement — harvest-now-decrypt-later exposed.",
|
|
643
|
+
remediation: "Track IETF PQC JOSE/COSE; adopt hybrid X25519MLKEM768 KEM-based encryption.",
|
|
644
|
+
};
|
|
645
|
+
|
|
486
646
|
/**
|
|
487
647
|
* Detects classical signature algorithm identifiers used by JWT/JOSE, plus
|
|
488
648
|
* ECDH-ES key-agreement identifiers (HNDL-exposed). These appear as string
|
|
@@ -492,8 +652,12 @@ const jwtDetector: Detector = {
|
|
|
492
652
|
id: "jwt-jose",
|
|
493
653
|
description: "Classical JWT/JOSE algorithms (RS/PS/ES/EdDSA) and ECDH-ES key agreement",
|
|
494
654
|
scope: "source",
|
|
495
|
-
|
|
496
|
-
|
|
655
|
+
// Language-agnostic evidence: a quoted "RS256"/"ES256" alg token is the same
|
|
656
|
+
// signal in JS/TS or Python (e.g. PyJWT `algorithm="RS256"`), so this detector
|
|
657
|
+
// is un-gated from JS-only to the JWT host surfaces.
|
|
658
|
+
language: "any",
|
|
659
|
+
rules: [RULE_JWT_ALG, RULE_JOSE_ECDH],
|
|
660
|
+
appliesTo: (f) => hasExtension(f, JWT_HOST_EXTENSIONS),
|
|
497
661
|
detect({ file, content }): Finding[] {
|
|
498
662
|
const findings: Finding[] = [];
|
|
499
663
|
|
|
@@ -505,45 +669,29 @@ const jwtDetector: Detector = {
|
|
|
505
669
|
else if (alg === "EdDSA") algorithm = "EdDSA";
|
|
506
670
|
else algorithm = "ECDSA"; // ES*
|
|
507
671
|
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
|
-
}),
|
|
672
|
+
findingFromRule(
|
|
673
|
+
RULE_JWT_ALG,
|
|
674
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
675
|
+
{
|
|
676
|
+
title: `JWT/JOSE algorithm ${alg}`,
|
|
677
|
+
algorithm,
|
|
678
|
+
message: `JWT/JOSE algorithm "${alg}" is a classical signature, forgeable by a quantum attacker.`,
|
|
679
|
+
},
|
|
680
|
+
),
|
|
524
681
|
);
|
|
525
682
|
});
|
|
526
683
|
|
|
527
684
|
// JOSE ECDH-ES key agreement (and ECDH-ES+A*KW) — confidentiality, HNDL.
|
|
528
685
|
eachMatch(RE_JOSE_ECDH, content, (m) => {
|
|
529
686
|
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
|
-
}),
|
|
687
|
+
findingFromRule(
|
|
688
|
+
RULE_JOSE_ECDH,
|
|
689
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
690
|
+
{
|
|
691
|
+
title: `JOSE key agreement ${m[1]}`,
|
|
692
|
+
message: `JOSE "${m[1]}" performs classical ECDH key agreement — harvest-now-decrypt-later exposed.`,
|
|
693
|
+
},
|
|
694
|
+
),
|
|
547
695
|
);
|
|
548
696
|
});
|
|
549
697
|
|
|
@@ -555,6 +703,43 @@ const jwtDetector: Detector = {
|
|
|
555
703
|
/* TLS legacy configuration */
|
|
556
704
|
/* -------------------------------------------------------------------------- */
|
|
557
705
|
|
|
706
|
+
const RULE_TLS_LEGACY: RuleMeta = {
|
|
707
|
+
id: "tls-legacy-version",
|
|
708
|
+
title: "Legacy TLS version pinned",
|
|
709
|
+
description: "minVersion/maxVersion/secureProtocol pinned to TLS 1.0/1.1",
|
|
710
|
+
category: "tls",
|
|
711
|
+
severity: "medium",
|
|
712
|
+
confidence: "high",
|
|
713
|
+
hndl: false,
|
|
714
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
715
|
+
message: "TLS 1.0/1.1 are deprecated and insecure; require TLS 1.3.",
|
|
716
|
+
remediation: "Set minVersion: 'TLSv1.3' and prefer PQC-hybrid key exchange.",
|
|
717
|
+
};
|
|
718
|
+
const RULE_TLS_REJECT: RuleMeta = {
|
|
719
|
+
id: "tls-reject-unauthorized",
|
|
720
|
+
title: "TLS certificate verification disabled",
|
|
721
|
+
description: "rejectUnauthorized: false",
|
|
722
|
+
category: "tls",
|
|
723
|
+
severity: "high",
|
|
724
|
+
confidence: "high",
|
|
725
|
+
hndl: false,
|
|
726
|
+
cwe: CWE_CERT_VALIDATION,
|
|
727
|
+
message: "rejectUnauthorized:false disables TLS certificate verification (MITM risk).",
|
|
728
|
+
remediation: "Remove rejectUnauthorized:false; verify certificates properly.",
|
|
729
|
+
};
|
|
730
|
+
const RULE_TLS_WEAK_CIPHER: RuleMeta = {
|
|
731
|
+
id: "tls-weak-cipher",
|
|
732
|
+
title: "Weak TLS cipher configured",
|
|
733
|
+
description: "weak/export cipher in a `ciphers` string",
|
|
734
|
+
category: "tls",
|
|
735
|
+
severity: "medium",
|
|
736
|
+
confidence: "medium",
|
|
737
|
+
hndl: false,
|
|
738
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
739
|
+
message: "A weak cipher is configured in the TLS ciphers list.",
|
|
740
|
+
remediation: "Use a modern AEAD cipher suite (TLS 1.3 defaults).",
|
|
741
|
+
};
|
|
742
|
+
|
|
558
743
|
/**
|
|
559
744
|
* Detects legacy / insecure TLS configuration expressed as JS object literals:
|
|
560
745
|
* forced TLS 1.0/1.1, disabled certificate verification, and weak ciphers.
|
|
@@ -566,6 +751,7 @@ const tlsDetector: Detector = {
|
|
|
566
751
|
description: "Legacy / insecure TLS configuration in JS objects",
|
|
567
752
|
scope: "config",
|
|
568
753
|
language: "js",
|
|
754
|
+
rules: [RULE_TLS_LEGACY, RULE_TLS_REJECT, RULE_TLS_WEAK_CIPHER],
|
|
569
755
|
appliesTo: (f) => hasExtension(f, JS_TS_EXTENSIONS),
|
|
570
756
|
detect({ file, content }): Finding[] {
|
|
571
757
|
const findings: Finding[] = [];
|
|
@@ -573,16 +759,7 @@ const tlsDetector: Detector = {
|
|
|
573
759
|
// minVersion / maxVersion / secureProtocol pinned to TLS 1.0 or 1.1.
|
|
574
760
|
eachMatch(RE_TLS_LEGACY_VERSION, content, (m) => {
|
|
575
761
|
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.",
|
|
762
|
+
findingFromRule(RULE_TLS_LEGACY, {
|
|
586
763
|
file,
|
|
587
764
|
content,
|
|
588
765
|
index: m.index,
|
|
@@ -594,16 +771,7 @@ const tlsDetector: Detector = {
|
|
|
594
771
|
// rejectUnauthorized: false — disables certificate verification.
|
|
595
772
|
eachMatch(RE_TLS_REJECT, content, (m) => {
|
|
596
773
|
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.",
|
|
774
|
+
findingFromRule(RULE_TLS_REJECT, {
|
|
607
775
|
file,
|
|
608
776
|
content,
|
|
609
777
|
index: m.index,
|
|
@@ -615,21 +783,11 @@ const tlsDetector: Detector = {
|
|
|
615
783
|
// Weak / export ciphers referenced in a ciphers string (bounded regex).
|
|
616
784
|
eachMatch(RE_TLS_WEAK_CIPHER, content, (m) => {
|
|
617
785
|
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
|
-
}),
|
|
786
|
+
findingFromRule(
|
|
787
|
+
RULE_TLS_WEAK_CIPHER,
|
|
788
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
789
|
+
{ message: `Weak cipher (${m[1]}) configured in the TLS ciphers list.` },
|
|
790
|
+
),
|
|
633
791
|
);
|
|
634
792
|
});
|
|
635
793
|
|
|
@@ -645,6 +803,35 @@ const RE_SSH_PUBKEY = /\b(ssh-rsa|ssh-ed25519|ssh-dss|ecdsa-sha2-nistp(?:256|384
|
|
|
645
803
|
const RE_CERT_SIG_ALG =
|
|
646
804
|
/\b(sha(?:1|256|384|512)WithRSAEncryption|ecdsa-with-SHA(?:1|256|384|512)|rsassaPss|dsaWithSHA(?:1|256))\b/g;
|
|
647
805
|
|
|
806
|
+
const RULE_SSH_PUBKEY: RuleMeta = {
|
|
807
|
+
id: "ssh-public-key",
|
|
808
|
+
title: "Classical SSH public key",
|
|
809
|
+
description: "ssh-rsa / ssh-ed25519 / ssh-dss / ecdsa-sha2-* public keys",
|
|
810
|
+
category: "certificate",
|
|
811
|
+
severity: "low",
|
|
812
|
+
confidence: "medium",
|
|
813
|
+
algorithm: "unknown",
|
|
814
|
+
hndl: false,
|
|
815
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
816
|
+
sensitive: true,
|
|
817
|
+
message: "A classical SSH public key is forgeable by a quantum attacker.",
|
|
818
|
+
remediation: "Plan migration to PQC-capable SSH (e.g. sntrup761x25519 KEX, PQC host keys).",
|
|
819
|
+
};
|
|
820
|
+
const RULE_CERT_SIG_ALG: RuleMeta = {
|
|
821
|
+
id: "cert-signature-algorithm",
|
|
822
|
+
title: "Classical certificate signature algorithm",
|
|
823
|
+
description: "X.509/TLS certificate signature-algorithm identifiers",
|
|
824
|
+
category: "certificate",
|
|
825
|
+
severity: "low",
|
|
826
|
+
confidence: "medium",
|
|
827
|
+
algorithm: "unknown",
|
|
828
|
+
hndl: false,
|
|
829
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
830
|
+
message:
|
|
831
|
+
"A classical certificate signature algorithm (RSA/ECDSA/DSA) is a quantum forgery surface.",
|
|
832
|
+
remediation: "Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
833
|
+
};
|
|
834
|
+
|
|
648
835
|
/**
|
|
649
836
|
* Detects classical SSH public keys (`authorized_keys` / `known_hosts` lines)
|
|
650
837
|
* and X.509 certificate signature-algorithm identifiers in any text file. These
|
|
@@ -656,6 +843,7 @@ const sshCertDetector: Detector = {
|
|
|
656
843
|
description: "SSH public keys and TLS/X.509 certificate signature algorithms in config",
|
|
657
844
|
scope: "config",
|
|
658
845
|
language: "any",
|
|
846
|
+
rules: [RULE_SSH_PUBKEY, RULE_CERT_SIG_ALG],
|
|
659
847
|
appliesTo: () => true,
|
|
660
848
|
detect({ file, content }): Finding[] {
|
|
661
849
|
const findings: Finding[] = [];
|
|
@@ -671,24 +859,15 @@ const sshCertDetector: Detector = {
|
|
|
671
859
|
? "DSA"
|
|
672
860
|
: "ECDSA";
|
|
673
861
|
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
|
-
}),
|
|
862
|
+
findingFromRule(
|
|
863
|
+
RULE_SSH_PUBKEY,
|
|
864
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
865
|
+
{
|
|
866
|
+
title: `Classical SSH public key (${tok})`,
|
|
867
|
+
algorithm,
|
|
868
|
+
message: `SSH public key type "${tok}" is a classical key forgeable by a quantum attacker.`,
|
|
869
|
+
},
|
|
870
|
+
),
|
|
692
871
|
);
|
|
693
872
|
});
|
|
694
873
|
|
|
@@ -701,23 +880,15 @@ const sshCertDetector: Detector = {
|
|
|
701
880
|
? "ECDSA"
|
|
702
881
|
: "DSA";
|
|
703
882
|
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
|
-
}),
|
|
883
|
+
findingFromRule(
|
|
884
|
+
RULE_CERT_SIG_ALG,
|
|
885
|
+
{ file, content, index: m.index, matchLength: m[0].length },
|
|
886
|
+
{
|
|
887
|
+
title: `Classical certificate signature algorithm (${tok})`,
|
|
888
|
+
algorithm,
|
|
889
|
+
message: `Certificate signature algorithm "${tok}" is classical (RSA/ECDSA/DSA) — a quantum forgery surface.`,
|
|
890
|
+
},
|
|
891
|
+
),
|
|
721
892
|
);
|
|
722
893
|
});
|
|
723
894
|
|