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