@quantakrypto/core 0.4.3 → 0.4.4
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/cbom-merge.d.ts +9 -0
- package/dist/cbom-merge.d.ts.map +1 -0
- package/dist/cbom-merge.js +99 -0
- package/dist/cbom-merge.js.map +1 -0
- package/dist/comments.d.ts +4 -3
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +53 -33
- package/dist/comments.js.map +1 -1
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +1 -1
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +5 -1
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +11 -2
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/c.d.ts +4 -2
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +192 -1
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts +27 -0
- package/dist/detectors/cicd.d.ts.map +1 -0
- package/dist/detectors/cicd.js +116 -0
- package/dist/detectors/cicd.js.map +1 -0
- package/dist/detectors/cloud-kms.d.ts +21 -0
- package/dist/detectors/cloud-kms.d.ts.map +1 -0
- package/dist/detectors/cloud-kms.js +57 -0
- package/dist/detectors/cloud-kms.js.map +1 -0
- package/dist/detectors/cloudformation.d.ts +30 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -0
- package/dist/detectors/cloudformation.js +170 -0
- package/dist/detectors/cloudformation.js.map +1 -0
- package/dist/detectors/database.d.ts +17 -0
- package/dist/detectors/database.d.ts.map +1 -0
- package/dist/detectors/database.js +60 -0
- package/dist/detectors/database.js.map +1 -0
- package/dist/detectors/dnssec.d.ts +55 -0
- package/dist/detectors/dnssec.d.ts.map +1 -0
- package/dist/detectors/dnssec.js +107 -0
- package/dist/detectors/dnssec.js.map +1 -0
- package/dist/detectors/elixir.d.ts +20 -0
- package/dist/detectors/elixir.d.ts.map +1 -0
- package/dist/detectors/elixir.js +195 -0
- package/dist/detectors/elixir.js.map +1 -0
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +1 -1
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts +20 -0
- package/dist/detectors/jose.d.ts.map +1 -0
- package/dist/detectors/jose.js +62 -0
- package/dist/detectors/jose.js.map +1 -0
- package/dist/detectors/jwk.d.ts +25 -0
- package/dist/detectors/jwk.d.ts.map +1 -0
- package/dist/detectors/jwk.js +95 -0
- package/dist/detectors/jwk.js.map +1 -0
- package/dist/detectors/k8s.d.ts +17 -0
- package/dist/detectors/k8s.d.ts.map +1 -0
- package/dist/detectors/k8s.js +85 -0
- package/dist/detectors/k8s.js.map +1 -0
- package/dist/detectors/mesh.d.ts +35 -0
- package/dist/detectors/mesh.d.ts.map +1 -0
- package/dist/detectors/mesh.js +109 -0
- package/dist/detectors/mesh.js.map +1 -0
- package/dist/detectors/messaging.d.ts +16 -0
- package/dist/detectors/messaging.d.ts.map +1 -0
- package/dist/detectors/messaging.js +72 -0
- package/dist/detectors/messaging.js.map +1 -0
- package/dist/detectors/pem.js +1 -1
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +24 -0
- package/dist/detectors/php.d.ts.map +1 -0
- package/dist/detectors/php.js +167 -0
- package/dist/detectors/php.js.map +1 -0
- package/dist/detectors/python.d.ts.map +1 -1
- package/dist/detectors/python.js +70 -0
- package/dist/detectors/python.js.map +1 -1
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +68 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +21 -0
- package/dist/detectors/secrets.d.ts.map +1 -0
- package/dist/detectors/secrets.js +79 -0
- package/dist/detectors/secrets.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +190 -47
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/stateful-hbs.d.ts.map +1 -1
- package/dist/detectors/stateful-hbs.js +9 -6
- package/dist/detectors/stateful-hbs.js.map +1 -1
- package/dist/detectors/terraform.d.ts +21 -0
- package/dist/detectors/terraform.d.ts.map +1 -0
- package/dist/detectors/terraform.js +123 -0
- package/dist/detectors/terraform.js.map +1 -0
- package/dist/detectors/vpn.d.ts +22 -0
- package/dist/detectors/vpn.d.ts.map +1 -0
- package/dist/detectors/vpn.js +99 -0
- package/dist/detectors/vpn.js.map +1 -0
- package/dist/evidence.d.ts +5 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +5 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/policy.d.ts +67 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +30 -0
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +22 -1
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +7 -0
- package/dist/report.js.map +1 -1
- package/dist/standards.d.ts +96 -0
- package/dist/standards.d.ts.map +1 -0
- package/dist/standards.js +107 -0
- package/dist/standards.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +116 -0
- package/src/comments.ts +59 -33
- package/src/dependencies.ts +2 -1
- package/src/detect-utils.ts +14 -2
- package/src/detectors/c.ts +209 -3
- package/src/detectors/cicd.ts +158 -0
- package/src/detectors/cloud-kms.ts +87 -0
- package/src/detectors/cloudformation.ts +215 -0
- package/src/detectors/database.ts +88 -0
- package/src/detectors/dnssec.ts +186 -0
- package/src/detectors/elixir.ts +237 -0
- package/src/detectors/java.ts +2 -1
- package/src/detectors/jose.ts +94 -0
- package/src/detectors/jwk.ts +128 -0
- package/src/detectors/k8s.ts +112 -0
- package/src/detectors/mesh.ts +154 -0
- package/src/detectors/messaging.ts +98 -0
- package/src/detectors/pem.ts +1 -1
- package/src/detectors/php.ts +225 -0
- package/src/detectors/python.ts +71 -0
- package/src/detectors/rust.ts +71 -0
- package/src/detectors/secrets.ts +114 -0
- package/src/detectors/source.ts +235 -68
- package/src/detectors/stateful-hbs.ts +9 -6
- package/src/detectors/terraform.ts +154 -0
- package/src/detectors/vpn.ts +131 -0
- package/src/evidence.ts +11 -0
- package/src/index.ts +10 -0
- package/src/policy.ts +180 -0
- package/src/registry.ts +30 -0
- package/src/remediate-pipeline.ts +25 -2
- package/src/report.ts +7 -0
- package/src/standards.ts +186 -0
- package/src/types.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-code detector for classical asymmetric cryptography in Elixir (the BEAM
|
|
3
|
+
* / Phoenix ecosystem), previously uncovered. Handles the three common surfaces:
|
|
4
|
+
*
|
|
5
|
+
* - **Erlang `:crypto`** — `:crypto.generate_key(:rsa|:ecdh|:dh|:eddsa, …)`
|
|
6
|
+
* (the curve atom disambiguates X25519/X448 from a generic ECDH curve) and
|
|
7
|
+
* `:crypto.sign/:crypto.verify(:rsa|:ecdsa|:eddsa, …)`.
|
|
8
|
+
* - **`X509`** (the `x509` hex package) — `X509.PrivateKey.new_rsa` / `new_ec`.
|
|
9
|
+
* - **`JOSE`** (erlang-jose) — `JOSE.JWK.generate_key({:rsa|:ec|:okp, …})`.
|
|
10
|
+
*
|
|
11
|
+
* Lexical, like the other packs; the `:crypto.` atom-module syntax and the
|
|
12
|
+
* `X509.` / `JOSE.` module paths are distinctive, so the false-positive rate is low.
|
|
13
|
+
*
|
|
14
|
+
* HNDL: RSA and (EC/X)DH key agreement are harvest-now-decrypt-later exposed
|
|
15
|
+
* (hndl:true); ECDSA / EdDSA signatures are hndl:false but forgeable.
|
|
16
|
+
*/
|
|
17
|
+
import type { AlgorithmFamily, Detector, Finding, RuleMeta } from "../types.js";
|
|
18
|
+
import { ELIXIR_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
19
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
20
|
+
|
|
21
|
+
const RE_EX_GEN = /:crypto\.generate_key\s*\(\s*:(\w+)/g;
|
|
22
|
+
const RE_EX_SIGN = /:crypto\.(?:sign|verify)\s*\(\s*:(\w+)/g;
|
|
23
|
+
const RE_EX_X509_RSA = /\bX509\.PrivateKey\.new_rsa\s*\(/g;
|
|
24
|
+
const RE_EX_X509_EC = /\bX509\.PrivateKey\.new_ec\s*\(/g;
|
|
25
|
+
const RE_EX_JOSE = /\bJOSE\.JWK\.generate_key\s*\(\s*\{\s*:(\w+)/g;
|
|
26
|
+
|
|
27
|
+
interface Cls {
|
|
28
|
+
algo: AlgorithmFamily;
|
|
29
|
+
cat: Finding["category"];
|
|
30
|
+
sev: Finding["severity"];
|
|
31
|
+
hndl: boolean;
|
|
32
|
+
label: string;
|
|
33
|
+
remediation?: string;
|
|
34
|
+
}
|
|
35
|
+
const SIG_REM = "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)";
|
|
36
|
+
const KEX_REM = "hybrid X25519MLKEM768 (ML-KEM-768)";
|
|
37
|
+
|
|
38
|
+
const RULE_EX_KEYGEN: RuleMeta = {
|
|
39
|
+
id: "elixir-crypto-keygen",
|
|
40
|
+
title: "Elixir :crypto key generation",
|
|
41
|
+
description: ":crypto.generate_key (rsa/ecdh/dh/eddsa)",
|
|
42
|
+
category: "kem",
|
|
43
|
+
severity: "high",
|
|
44
|
+
confidence: "high",
|
|
45
|
+
algorithm: "RSA",
|
|
46
|
+
hndl: true,
|
|
47
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
48
|
+
message: "Generates a classical key pair via Erlang :crypto (Elixir) — not quantum-safe.",
|
|
49
|
+
};
|
|
50
|
+
const RULE_EX_SIGN: RuleMeta = {
|
|
51
|
+
id: "elixir-crypto-sign",
|
|
52
|
+
title: "Elixir :crypto signature",
|
|
53
|
+
description: ":crypto.sign / :crypto.verify (rsa/ecdsa/eddsa)",
|
|
54
|
+
category: "signature",
|
|
55
|
+
severity: "high",
|
|
56
|
+
confidence: "high",
|
|
57
|
+
algorithm: "unknown",
|
|
58
|
+
hndl: false,
|
|
59
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
60
|
+
message: "Classical signature via Erlang :crypto (Elixir) is forgeable by a quantum attacker.",
|
|
61
|
+
remediation: SIG_REM,
|
|
62
|
+
};
|
|
63
|
+
const RULE_EX_X509: RuleMeta = {
|
|
64
|
+
id: "elixir-x509-keygen",
|
|
65
|
+
title: "Elixir X509 key generation",
|
|
66
|
+
description: "X509.PrivateKey.new_rsa / new_ec",
|
|
67
|
+
category: "kem",
|
|
68
|
+
severity: "high",
|
|
69
|
+
confidence: "high",
|
|
70
|
+
algorithm: "RSA",
|
|
71
|
+
hndl: true,
|
|
72
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
73
|
+
message: "Generates a classical key pair via the X509 library (Elixir) — not quantum-safe.",
|
|
74
|
+
};
|
|
75
|
+
const RULE_EX_JOSE: RuleMeta = {
|
|
76
|
+
id: "elixir-jose-jwk",
|
|
77
|
+
title: "Elixir JOSE JWK generation",
|
|
78
|
+
description: "JOSE.JWK.generate_key ({:rsa|:ec|:okp})",
|
|
79
|
+
category: "kem",
|
|
80
|
+
severity: "high",
|
|
81
|
+
confidence: "high",
|
|
82
|
+
algorithm: "RSA",
|
|
83
|
+
hndl: true,
|
|
84
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
85
|
+
message: "Generates a classical JWK via JOSE (Elixir) — not quantum-safe.",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const RSA_CLS: Cls = { algo: "RSA", cat: "kem", sev: "high", hndl: true, label: "RSA" };
|
|
89
|
+
const DH_CLS: Cls = { algo: "DH", cat: "key-exchange", sev: "high", hndl: true, label: "DH" };
|
|
90
|
+
const ECDH_CLS: Cls = {
|
|
91
|
+
algo: "ECDH",
|
|
92
|
+
cat: "key-exchange",
|
|
93
|
+
sev: "high",
|
|
94
|
+
hndl: true,
|
|
95
|
+
label: "ECDH",
|
|
96
|
+
remediation: KEX_REM,
|
|
97
|
+
};
|
|
98
|
+
const X25519_CLS: Cls = {
|
|
99
|
+
algo: "X25519",
|
|
100
|
+
cat: "key-exchange",
|
|
101
|
+
sev: "medium",
|
|
102
|
+
hndl: true,
|
|
103
|
+
label: "X25519",
|
|
104
|
+
remediation: KEX_REM,
|
|
105
|
+
};
|
|
106
|
+
const X448_CLS: Cls = { ...X25519_CLS, algo: "X448", label: "X448" };
|
|
107
|
+
const EDDSA_CLS: Cls = {
|
|
108
|
+
algo: "EdDSA",
|
|
109
|
+
cat: "signature",
|
|
110
|
+
sev: "low",
|
|
111
|
+
hndl: false,
|
|
112
|
+
label: "EdDSA",
|
|
113
|
+
remediation: SIG_REM,
|
|
114
|
+
};
|
|
115
|
+
const EC_CLS: Cls = {
|
|
116
|
+
algo: "ECDH",
|
|
117
|
+
cat: "key-exchange",
|
|
118
|
+
sev: "high",
|
|
119
|
+
hndl: true,
|
|
120
|
+
label: "EC (ECDSA/ECDH)",
|
|
121
|
+
remediation: KEX_REM,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Classify a `:crypto.generate_key(:<type>, …)` by its type atom (+ the curve
|
|
125
|
+
* atom in a short window for the ecdh X25519/X448 case). */
|
|
126
|
+
function classifyGen(type: string, window: string): Cls | null {
|
|
127
|
+
switch (type) {
|
|
128
|
+
case "rsa":
|
|
129
|
+
return RSA_CLS;
|
|
130
|
+
case "dh":
|
|
131
|
+
return DH_CLS;
|
|
132
|
+
case "eddsa":
|
|
133
|
+
case "ed25519":
|
|
134
|
+
return EDDSA_CLS;
|
|
135
|
+
case "ecdh":
|
|
136
|
+
if (/:x25519\b/i.test(window)) return X25519_CLS;
|
|
137
|
+
if (/:x448\b/i.test(window)) return X448_CLS;
|
|
138
|
+
return ECDH_CLS;
|
|
139
|
+
default:
|
|
140
|
+
return null; // srp / other non-broken-in-this-model types
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Signature algorithm classification for `:crypto.sign(:<type>, …)`. */
|
|
145
|
+
function classifySign(type: string): AlgorithmFamily | null {
|
|
146
|
+
if (type === "rsa") return "RSA";
|
|
147
|
+
if (type === "ecdsa") return "ECDSA";
|
|
148
|
+
if (type === "eddsa" || type === "ed25519") return "EdDSA";
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Detects classical asymmetric crypto in Elixir (:crypto, X509, JOSE). */
|
|
153
|
+
export const elixirDetector: Detector = {
|
|
154
|
+
id: "elixir-crypto",
|
|
155
|
+
description: "Classical asymmetric crypto in Elixir (:crypto, X509, JOSE)",
|
|
156
|
+
scope: "source",
|
|
157
|
+
language: "elixir",
|
|
158
|
+
rules: [RULE_EX_KEYGEN, RULE_EX_SIGN, RULE_EX_X509, RULE_EX_JOSE],
|
|
159
|
+
appliesTo: (f) => hasExtension(f, ELIXIR_EXTENSIONS),
|
|
160
|
+
detect({ file, content }): Finding[] {
|
|
161
|
+
const findings: Finding[] = [];
|
|
162
|
+
const at = (m: RegExpExecArray) => ({
|
|
163
|
+
file,
|
|
164
|
+
content,
|
|
165
|
+
index: m.index,
|
|
166
|
+
matchLength: m[0].length,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// :crypto.generate_key(:type, …) — classify by the type atom (+ curve window).
|
|
170
|
+
eachMatch(RE_EX_GEN, content, (m) => {
|
|
171
|
+
const cls = classifyGen(m[1], content.slice(m.index, m.index + 80));
|
|
172
|
+
if (!cls) return;
|
|
173
|
+
findings.push(
|
|
174
|
+
findingFromRule(RULE_EX_KEYGEN, at(m), {
|
|
175
|
+
title: `Elixir :crypto ${cls.label} key generation`,
|
|
176
|
+
category: cls.cat,
|
|
177
|
+
severity: cls.sev,
|
|
178
|
+
algorithm: cls.algo,
|
|
179
|
+
hndl: cls.hndl,
|
|
180
|
+
message: `Generates a classical ${cls.label} key pair via Erlang :crypto (Elixir) — not quantum-safe.`,
|
|
181
|
+
...(cls.remediation ? { remediation: cls.remediation } : {}),
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// :crypto.sign / :crypto.verify(:type, …).
|
|
187
|
+
eachMatch(RE_EX_SIGN, content, (m) => {
|
|
188
|
+
const algo = classifySign(m[1]);
|
|
189
|
+
if (!algo) return;
|
|
190
|
+
findings.push(
|
|
191
|
+
findingFromRule(RULE_EX_SIGN, at(m), {
|
|
192
|
+
algorithm: algo,
|
|
193
|
+
message: `Classical ${algo} signature via Erlang :crypto (Elixir) is forgeable by a quantum attacker.`,
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// X509.PrivateKey.new_rsa / new_ec.
|
|
199
|
+
eachMatch(RE_EX_X509_RSA, content, (m) =>
|
|
200
|
+
findings.push(findingFromRule(RULE_EX_X509, at(m), { algorithm: "RSA" })),
|
|
201
|
+
);
|
|
202
|
+
eachMatch(RE_EX_X509_EC, content, (m) =>
|
|
203
|
+
findings.push(
|
|
204
|
+
findingFromRule(RULE_EX_X509, at(m), {
|
|
205
|
+
title: "Elixir X509 EC key generation",
|
|
206
|
+
category: EC_CLS.cat,
|
|
207
|
+
algorithm: EC_CLS.algo,
|
|
208
|
+
hndl: EC_CLS.hndl,
|
|
209
|
+
message:
|
|
210
|
+
"Generates a classical EC key pair via the X509 library (Elixir); EC keys feed BOTH ECDSA and ECDH.",
|
|
211
|
+
remediation: KEX_REM,
|
|
212
|
+
}),
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// JOSE.JWK.generate_key({:rsa|:ec|:okp, …}).
|
|
217
|
+
eachMatch(RE_EX_JOSE, content, (m) => {
|
|
218
|
+
const kind = m[1];
|
|
219
|
+
const cls =
|
|
220
|
+
kind === "rsa" ? RSA_CLS : kind === "ec" ? EC_CLS : kind === "okp" ? EDDSA_CLS : null;
|
|
221
|
+
if (!cls) return;
|
|
222
|
+
findings.push(
|
|
223
|
+
findingFromRule(RULE_EX_JOSE, at(m), {
|
|
224
|
+
title: `Elixir JOSE ${cls.label} JWK`,
|
|
225
|
+
category: cls.cat,
|
|
226
|
+
severity: cls.sev,
|
|
227
|
+
algorithm: cls.algo,
|
|
228
|
+
hndl: cls.hndl,
|
|
229
|
+
message: `Generates a classical ${cls.label} JWK via JOSE (Elixir) — not quantum-safe.`,
|
|
230
|
+
...(cls.remediation ? { remediation: cls.remediation } : {}),
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return findings;
|
|
236
|
+
},
|
|
237
|
+
};
|
package/src/detectors/java.ts
CHANGED
|
@@ -328,7 +328,8 @@ const BC_CURVE_CLASS_RULES: Record<string, RuleMeta> = {
|
|
|
328
328
|
/** Detects classical asymmetric crypto in Java / Kotlin (JCA + BouncyCastle). */
|
|
329
329
|
export const javaDetector: Detector = {
|
|
330
330
|
id: "java-crypto",
|
|
331
|
-
description:
|
|
331
|
+
description:
|
|
332
|
+
"Classical asymmetric crypto on the JVM — Java/Kotlin/Scala (JCA getInstance + BouncyCastle)",
|
|
332
333
|
scope: "source",
|
|
333
334
|
language: "java",
|
|
334
335
|
rules: [
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical KEY-MANAGEMENT (JWE) algorithms in JOSE. This is
|
|
3
|
+
* distinct from the JWS signature side (RS256/ES256, handled elsewhere) and from
|
|
4
|
+
* the JWK key-material detector: here we flag the `alg` values that wrap a
|
|
5
|
+
* content-encryption key with classical asymmetric crypto, because the encrypted
|
|
6
|
+
* payload is CONFIDENTIALITY — harvest-now-decrypt-later exposed. A JWE token
|
|
7
|
+
* captured today (from a log, an audit trail, a stored assertion) is decryptable
|
|
8
|
+
* once the wrapping key falls to a CRQC.
|
|
9
|
+
*
|
|
10
|
+
* Covered `alg` (RFC 7518 §4) values:
|
|
11
|
+
* - `RSA-OAEP`, `RSA-OAEP-256`, `RSA1_5` → RSA key encryption.
|
|
12
|
+
* - `ECDH-ES`, `ECDH-ES+A128KW`, … → classical EC Diffie-Hellman key agreement.
|
|
13
|
+
*
|
|
14
|
+
* These exact strings are JOSE identifiers, so a content-level match is precise;
|
|
15
|
+
* the detector applies to any file (config, JSON, source) after a fast reject.
|
|
16
|
+
*/
|
|
17
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
18
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
19
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
20
|
+
|
|
21
|
+
interface JoseRule {
|
|
22
|
+
re: RegExp;
|
|
23
|
+
meta: RuleMeta;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const JOSE_RULES: JoseRule[] = [
|
|
27
|
+
{
|
|
28
|
+
re: /"alg"\s*:\s*"RSA(?:-OAEP(?:-256)?|1_5)"/g,
|
|
29
|
+
meta: {
|
|
30
|
+
id: "jose-jwe-rsa",
|
|
31
|
+
title: "JWE RSA key wrapping",
|
|
32
|
+
description: 'JWE "alg" of RSA-OAEP / RSA-OAEP-256 / RSA1_5 (RFC 7518)',
|
|
33
|
+
category: "kem",
|
|
34
|
+
severity: "high",
|
|
35
|
+
confidence: "high",
|
|
36
|
+
algorithm: "RSA",
|
|
37
|
+
hndl: true,
|
|
38
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
39
|
+
message:
|
|
40
|
+
"JWE wraps the content-encryption key with classical RSA (RSA-OAEP/RSA1_5); the encrypted payload is harvest-now-decrypt-later exposed.",
|
|
41
|
+
remediation:
|
|
42
|
+
"Plan migration to a post-quantum KEM (ML-KEM-768) for key wrapping as JOSE/COSE PQ algorithms are standardised.",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
re: /"alg"\s*:\s*"ECDH-ES(?:\+A\d{3}KW)?"/g,
|
|
47
|
+
meta: {
|
|
48
|
+
id: "jose-jwe-ecdh",
|
|
49
|
+
title: "JWE ECDH-ES key agreement",
|
|
50
|
+
description: 'JWE "alg" of ECDH-ES / ECDH-ES+A*KW (RFC 7518)',
|
|
51
|
+
category: "key-exchange",
|
|
52
|
+
severity: "high",
|
|
53
|
+
confidence: "high",
|
|
54
|
+
algorithm: "ECDH",
|
|
55
|
+
hndl: true,
|
|
56
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
57
|
+
message:
|
|
58
|
+
"JWE derives the content-encryption key via classical ECDH-ES; the encrypted payload is harvest-now-decrypt-later exposed.",
|
|
59
|
+
remediation:
|
|
60
|
+
"Plan migration to hybrid post-quantum key agreement (X25519MLKEM768) as JOSE PQ algorithms are standardised.",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/** Detects classical JWE key-management (`alg`) values in JOSE tokens/config. */
|
|
66
|
+
export const joseDetector: Detector = {
|
|
67
|
+
id: "jose-jwe-keymgmt",
|
|
68
|
+
description:
|
|
69
|
+
"Classical JWE key-management algorithms (RSA-OAEP, ECDH-ES) — confidentiality, HNDL",
|
|
70
|
+
scope: "config",
|
|
71
|
+
language: "any",
|
|
72
|
+
rules: JOSE_RULES.map((r) => r.meta),
|
|
73
|
+
// Prose examples (a README showing `"alg":"RSA-OAEP"`) are not JOSE config.
|
|
74
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
75
|
+
detect({ file, content }): Finding[] {
|
|
76
|
+
// Fast reject: no JOSE key-management alg token present.
|
|
77
|
+
if (
|
|
78
|
+
!content.includes("RSA-OAEP") &&
|
|
79
|
+
!content.includes("RSA1_5") &&
|
|
80
|
+
!content.includes("ECDH-ES")
|
|
81
|
+
) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const findings: Finding[] = [];
|
|
85
|
+
for (const rule of JOSE_RULES) {
|
|
86
|
+
eachMatch(rule.re, content, (m) => {
|
|
87
|
+
findings.push(
|
|
88
|
+
findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return findings;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical key material in JSON Web Keys (JWK / JWKS, RFC 7517
|
|
3
|
+
* / 7518). JWKs appear in `.json` / `.jwks` files, OIDC discovery documents, and
|
|
4
|
+
* config — a real key-material surface the source packs (which look at *code*)
|
|
5
|
+
* and the PEM detector (which looks at PEM markers) both miss.
|
|
6
|
+
*
|
|
7
|
+
* Detection keys off the JWK's distinctive JSON fields:
|
|
8
|
+
* - `"kty":"RSA"` → classical RSA key.
|
|
9
|
+
* - `"crv":"P-256"|"P-384"|"P-521"|…` → classical EC key (ECDSA + ECDH).
|
|
10
|
+
* - `"crv":"Ed25519"|"Ed448"` → EdDSA signing key.
|
|
11
|
+
* - `"crv":"X25519"|"X448"` → classical Montgomery-curve key agreement.
|
|
12
|
+
*
|
|
13
|
+
* Keying EC/OKP off `crv` (present in every EC/OKP JWK) rather than `kty` avoids
|
|
14
|
+
* double-counting a single key, and `"kty"`/`"crv"` are specific enough to RFC
|
|
15
|
+
* 7517 that the false-positive risk on ordinary JSON is low.
|
|
16
|
+
*
|
|
17
|
+
* HNDL: RSA and (EC)DH / X25519 / X448 key agreement are harvest-now-decrypt-later
|
|
18
|
+
* exposed (hndl:true); EdDSA signing keys are hndl:false but forgeable. A JWKS is
|
|
19
|
+
* usually PUBLIC keys, so severity is medium — the exposure is a classical key
|
|
20
|
+
* pair, not necessarily an embedded secret.
|
|
21
|
+
*/
|
|
22
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
23
|
+
import { eachMatch, findingFromRule } from "../detect-utils.js";
|
|
24
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
25
|
+
|
|
26
|
+
interface JwkRule {
|
|
27
|
+
re: RegExp;
|
|
28
|
+
meta: RuleMeta;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const JWK_RULES: JwkRule[] = [
|
|
32
|
+
{
|
|
33
|
+
re: /"kty"\s*:\s*"RSA"/g,
|
|
34
|
+
meta: {
|
|
35
|
+
id: "jwk-rsa",
|
|
36
|
+
title: "RSA JSON Web Key (JWK)",
|
|
37
|
+
description: 'JWK with "kty":"RSA" (RFC 7518)',
|
|
38
|
+
category: "certificate",
|
|
39
|
+
severity: "medium",
|
|
40
|
+
confidence: "high",
|
|
41
|
+
algorithm: "RSA",
|
|
42
|
+
hndl: true,
|
|
43
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
44
|
+
message: "RSA JSON Web Key (JWK); classical RSA, not quantum-safe.",
|
|
45
|
+
remediation: "Re-issue with PQC keys (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
re: /"crv"\s*:\s*"(?:P-256K?|P-384|P-521|secp256k1)"/g,
|
|
50
|
+
meta: {
|
|
51
|
+
id: "jwk-ec",
|
|
52
|
+
title: "EC JSON Web Key (JWK)",
|
|
53
|
+
description: 'JWK with an "crv" naming a NIST/secp curve (RFC 7518)',
|
|
54
|
+
category: "key-exchange",
|
|
55
|
+
severity: "medium",
|
|
56
|
+
confidence: "high",
|
|
57
|
+
algorithm: "ECDH",
|
|
58
|
+
hndl: true,
|
|
59
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
60
|
+
message:
|
|
61
|
+
"Elliptic-curve JSON Web Key (JWK); a classical EC key feeds BOTH ECDSA signatures and ECDH key agreement — the ECDH path is harvest-now-decrypt-later exposed.",
|
|
62
|
+
remediation:
|
|
63
|
+
"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
re: /"crv"\s*:\s*"Ed(?:25519|448)"/g,
|
|
68
|
+
meta: {
|
|
69
|
+
id: "jwk-eddsa",
|
|
70
|
+
title: "EdDSA JSON Web Key (JWK)",
|
|
71
|
+
description: 'JWK OKP key with "crv":"Ed25519"/"Ed448" (RFC 8037)',
|
|
72
|
+
category: "signature",
|
|
73
|
+
severity: "low",
|
|
74
|
+
confidence: "high",
|
|
75
|
+
algorithm: "EdDSA",
|
|
76
|
+
hndl: false,
|
|
77
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
78
|
+
message:
|
|
79
|
+
"EdDSA (Ed25519/Ed448) JSON Web Key (JWK); classical and forgeable by a quantum attacker.",
|
|
80
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
re: /"crv"\s*:\s*"X(?:25519|448)"/g,
|
|
85
|
+
meta: {
|
|
86
|
+
id: "jwk-xdh",
|
|
87
|
+
title: "X25519/X448 JSON Web Key (JWK)",
|
|
88
|
+
description: 'JWK OKP key with "crv":"X25519"/"X448" (RFC 8037)',
|
|
89
|
+
category: "key-exchange",
|
|
90
|
+
severity: "medium",
|
|
91
|
+
confidence: "high",
|
|
92
|
+
algorithm: "X25519",
|
|
93
|
+
hndl: true,
|
|
94
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
95
|
+
message:
|
|
96
|
+
"X25519/X448 JSON Web Key (JWK); modern but classical key agreement, and harvest-now-decrypt-later exposed.",
|
|
97
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
/** Detects classical JSON Web Key (JWK/JWKS) material in any JSON/text file. */
|
|
103
|
+
export const jwkDetector: Detector = {
|
|
104
|
+
id: "jwk-material",
|
|
105
|
+
description: "Classical key material in JSON Web Keys (JWK / JWKS)",
|
|
106
|
+
scope: "config",
|
|
107
|
+
language: "any",
|
|
108
|
+
rules: JWK_RULES.map((r) => r.meta),
|
|
109
|
+
appliesTo: () => true,
|
|
110
|
+
detect({ file, content }): Finding[] {
|
|
111
|
+
// Fast reject: a JWK always has one of these two members.
|
|
112
|
+
if (!content.includes('"kty"') && !content.includes('"crv"')) return [];
|
|
113
|
+
const findings: Finding[] = [];
|
|
114
|
+
for (const rule of JWK_RULES) {
|
|
115
|
+
eachMatch(rule.re, content, (m) => {
|
|
116
|
+
findings.push(
|
|
117
|
+
findingFromRule(rule.meta, {
|
|
118
|
+
file,
|
|
119
|
+
content,
|
|
120
|
+
index: m.index,
|
|
121
|
+
matchLength: m[0].length,
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return findings;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical crypto in Kubernetes manifests — cert-manager key
|
|
3
|
+
* algorithms and service-mesh (Istio) TLS floors. These mint the certificates
|
|
4
|
+
* and secure the pod-to-pod traffic of a cluster, a surface neither the language
|
|
5
|
+
* packs nor the Terraform detector see.
|
|
6
|
+
*
|
|
7
|
+
* Covered (gated by a cert-manager / Istio marker in the document to keep the
|
|
8
|
+
* generic `algorithm:` / `minProtocolVersion:` keys from firing on unrelated YAML):
|
|
9
|
+
* - cert-manager `Certificate`/`Issuer` `privateKey.algorithm` (or legacy
|
|
10
|
+
* `keyAlgorithm`): `RSA` | `ECDSA` | `Ed25519`.
|
|
11
|
+
* - Istio `minProtocolVersion: TLSV1_0 | TLSV1_1` — a legacy TLS floor on mesh
|
|
12
|
+
* traffic, whose (EC)DHE key exchange is classical and harvestable.
|
|
13
|
+
*/
|
|
14
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
15
|
+
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
16
|
+
import { CWE_BROKEN_CRYPTO, CWE_RISKY_PRIMITIVE } from "../cwe.js";
|
|
17
|
+
|
|
18
|
+
const K8S_EXTENSIONS: readonly string[] = [".yaml", ".yml", ".json"];
|
|
19
|
+
|
|
20
|
+
const RE_CM_RSA = /(?:algorithm|keyAlgorithm):\s*["']?RSA\b/g;
|
|
21
|
+
const RE_CM_ECDSA = /(?:algorithm|keyAlgorithm):\s*["']?ECDSA\b/g;
|
|
22
|
+
const RE_CM_ED25519 = /(?:algorithm|keyAlgorithm):\s*["']?Ed25519\b/g;
|
|
23
|
+
const RE_ISTIO_LEGACY_TLS = /minProtocolVersion:\s*["']?TLSV1_[01]\b/g;
|
|
24
|
+
|
|
25
|
+
const RULE_CM_RSA: RuleMeta = {
|
|
26
|
+
id: "k8s-certmanager-rsa",
|
|
27
|
+
title: "cert-manager RSA key",
|
|
28
|
+
description: "cert-manager Certificate/Issuer privateKey.algorithm = RSA",
|
|
29
|
+
category: "certificate",
|
|
30
|
+
severity: "medium",
|
|
31
|
+
confidence: "high",
|
|
32
|
+
algorithm: "RSA",
|
|
33
|
+
hndl: true,
|
|
34
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
35
|
+
message: "cert-manager mints certificates with a classical RSA key, which is not quantum-safe.",
|
|
36
|
+
remediation:
|
|
37
|
+
"Plan migration to PQC certificate keys (ML-DSA-65) as the CA/issuer chain adds support.",
|
|
38
|
+
};
|
|
39
|
+
const RULE_CM_ECDSA: RuleMeta = {
|
|
40
|
+
id: "k8s-certmanager-ecdsa",
|
|
41
|
+
title: "cert-manager ECDSA key",
|
|
42
|
+
description: "cert-manager Certificate/Issuer privateKey.algorithm = ECDSA",
|
|
43
|
+
category: "certificate",
|
|
44
|
+
severity: "medium",
|
|
45
|
+
confidence: "high",
|
|
46
|
+
algorithm: "ECDSA",
|
|
47
|
+
hndl: false,
|
|
48
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
49
|
+
message:
|
|
50
|
+
"cert-manager mints certificates with a classical ECDSA key, forgeable by a quantum attacker.",
|
|
51
|
+
remediation: "Plan migration to ML-DSA-65 (FIPS 204) certificate keys.",
|
|
52
|
+
};
|
|
53
|
+
const RULE_CM_ED25519: RuleMeta = {
|
|
54
|
+
id: "k8s-certmanager-ed25519",
|
|
55
|
+
title: "cert-manager Ed25519 key",
|
|
56
|
+
description: "cert-manager Certificate/Issuer privateKey.algorithm = Ed25519",
|
|
57
|
+
category: "certificate",
|
|
58
|
+
severity: "low",
|
|
59
|
+
confidence: "high",
|
|
60
|
+
algorithm: "EdDSA",
|
|
61
|
+
hndl: false,
|
|
62
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
63
|
+
message:
|
|
64
|
+
"cert-manager mints certificates with a classical Ed25519 key, forgeable by a quantum attacker.",
|
|
65
|
+
remediation: "Plan migration to ML-DSA-65 (FIPS 204) certificate keys.",
|
|
66
|
+
};
|
|
67
|
+
const RULE_ISTIO_LEGACY_TLS: RuleMeta = {
|
|
68
|
+
id: "k8s-istio-legacy-tls",
|
|
69
|
+
title: "Istio legacy TLS floor",
|
|
70
|
+
description: "Istio minProtocolVersion allows TLS 1.0 / 1.1 on mesh traffic",
|
|
71
|
+
category: "tls",
|
|
72
|
+
severity: "medium",
|
|
73
|
+
confidence: "high",
|
|
74
|
+
hndl: false,
|
|
75
|
+
cwe: CWE_RISKY_PRIMITIVE,
|
|
76
|
+
message:
|
|
77
|
+
"Istio mesh TLS floor allows TLS 1.0/1.1; its classical (EC)DHE key exchange is weak and harvestable.",
|
|
78
|
+
remediation:
|
|
79
|
+
"Raise minProtocolVersion to TLSV1_3 and track PQC-hybrid mesh KEX (X25519MLKEM768).",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Detects classical cert-manager keys and legacy Istio TLS floors in K8s YAML. */
|
|
83
|
+
export const k8sDetector: Detector = {
|
|
84
|
+
id: "k8s-crypto",
|
|
85
|
+
description: "Classical crypto in Kubernetes manifests (cert-manager keys, Istio TLS floors)",
|
|
86
|
+
scope: "config",
|
|
87
|
+
language: "any",
|
|
88
|
+
rules: [RULE_CM_RSA, RULE_CM_ECDSA, RULE_CM_ED25519, RULE_ISTIO_LEGACY_TLS],
|
|
89
|
+
appliesTo: (f) => hasExtension(f, K8S_EXTENSIONS),
|
|
90
|
+
detect({ file, content }): Finding[] {
|
|
91
|
+
const isCertManager =
|
|
92
|
+
content.includes("cert-manager.io") ||
|
|
93
|
+
/kind:\s*["']?(?:Certificate|Issuer|ClusterIssuer)\b/.test(content);
|
|
94
|
+
const isIstio = content.includes("minProtocolVersion");
|
|
95
|
+
if (!isCertManager && !isIstio) return [];
|
|
96
|
+
|
|
97
|
+
const findings: Finding[] = [];
|
|
98
|
+
const add = (re: RegExp, rule: RuleMeta) =>
|
|
99
|
+
eachMatch(re, content, (m) =>
|
|
100
|
+
findings.push(
|
|
101
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
if (isCertManager) {
|
|
105
|
+
add(RE_CM_RSA, RULE_CM_RSA);
|
|
106
|
+
add(RE_CM_ECDSA, RULE_CM_ECDSA);
|
|
107
|
+
add(RE_CM_ED25519, RULE_CM_ED25519);
|
|
108
|
+
}
|
|
109
|
+
if (isIstio) add(RE_ISTIO_LEGACY_TLS, RULE_ISTIO_LEGACY_TLS);
|
|
110
|
+
return findings;
|
|
111
|
+
},
|
|
112
|
+
};
|