@quantakrypto/core 0.4.2 → 0.4.3
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/LICENSE +201 -0
- package/README.md +38 -10
- package/dist/agent-types.js.map +1 -1
- package/dist/baseline.js.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/cbom.js.map +1 -1
- package/dist/changed.js.map +1 -1
- package/dist/codemods/config-toggle.js.map +1 -1
- package/dist/codemods/registry.js.map +1 -1
- package/dist/comments.d.ts +26 -0
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +141 -1
- package/dist/comments.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/cwe.js.map +1 -1
- package/dist/dependencies.d.ts +3 -2
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +138 -2
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +14 -3
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +32 -4
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +190 -2
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/csharp.d.ts.map +1 -1
- package/dist/detectors/csharp.js +141 -3
- package/dist/detectors/csharp.js.map +1 -1
- package/dist/detectors/go.d.ts +5 -3
- package/dist/detectors/go.d.ts.map +1 -1
- package/dist/detectors/go.js +187 -3
- package/dist/detectors/go.js.map +1 -1
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +168 -3
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +49 -1
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/python.d.ts.map +1 -1
- package/dist/detectors/python.js +74 -3
- package/dist/detectors/python.js.map +1 -1
- package/dist/detectors/ruby.d.ts +9 -5
- package/dist/detectors/ruby.d.ts.map +1 -1
- package/dist/detectors/ruby.js +122 -2
- package/dist/detectors/ruby.js.map +1 -1
- package/dist/detectors/rust.d.ts +8 -3
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +185 -3
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +117 -7
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/stateful-hbs.d.ts +22 -0
- package/dist/detectors/stateful-hbs.d.ts.map +1 -0
- package/dist/detectors/stateful-hbs.js +139 -0
- package/dist/detectors/stateful-hbs.js.map +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +48 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +76 -0
- package/dist/evidence.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +2 -0
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +27 -1
- package/dist/inventory.js.map +1 -1
- package/dist/parallel.js.map +1 -1
- package/dist/patch-policy.js.map +1 -1
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +39 -0
- package/dist/redact.js.map +1 -1
- package/dist/registry.d.ts +2 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -2
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +3 -0
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +50 -0
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/remediate-request.js.map +1 -1
- package/dist/remediation.d.ts +9 -3
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +17 -5
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +9 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +31 -0
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +8 -1
- package/dist/scan.js.map +1 -1
- package/dist/severity.js.map +1 -1
- package/dist/triage.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/verify.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/dist/walk.js.map +1 -1
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
- package/src/comments.ts +140 -1
- package/src/dependencies.ts +143 -4
- package/src/detect-utils.ts +33 -4
- package/src/detectors/c.ts +202 -2
- package/src/detectors/csharp.ts +154 -3
- package/src/detectors/go.ts +209 -6
- package/src/detectors/java.ts +189 -3
- package/src/detectors/pem.ts +53 -1
- package/src/detectors/python.ts +82 -3
- package/src/detectors/ruby.ts +138 -7
- package/src/detectors/rust.ts +211 -7
- package/src/detectors/source.ts +149 -6
- package/src/detectors/stateful-hbs.ts +177 -0
- package/src/evidence.ts +124 -0
- package/src/index.ts +5 -1
- package/src/inventory.ts +31 -1
- package/src/redact.ts +41 -0
- package/src/registry.ts +4 -2
- package/src/remediate-pipeline.ts +55 -0
- package/src/remediation.ts +23 -8
- package/src/report.ts +41 -2
- package/src/scan.ts +13 -1
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
package/src/detectors/java.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
23
23
|
import { JAVA_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
24
|
-
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
24
|
+
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
25
25
|
|
|
26
26
|
/* -------------------------------------------------------------------------- */
|
|
27
27
|
/* Regexes */
|
|
@@ -35,7 +35,34 @@ const RE_JAVA_GETINSTANCE =
|
|
|
35
35
|
|
|
36
36
|
// BouncyCastle lightweight-API class instantiations.
|
|
37
37
|
const RE_JAVA_BC =
|
|
38
|
-
/\bnew\s+(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|Ed25519Signer|Ed448Signer|X25519Agreement|X448Agreement)\s*\(/g;
|
|
38
|
+
/\bnew\s+(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|Ed25519Signer|Ed448Signer|X25519Agreement|X448Agreement|ECDHBasicAgreement|DHBasicAgreement|X25519KeyPairGenerator|Ed25519KeyPairGenerator|RSAEngine|OAEPEncoding)\s*\(/g;
|
|
39
|
+
|
|
40
|
+
// BouncyCastle lightweight-API curve/EdDSA classes as a BARE constructor call —
|
|
41
|
+
// the Kotlin form (`X448KeyPairGenerator()`, no `new` keyword). RE_JAVA_BC above
|
|
42
|
+
// only matches the Java `new <Class>(` form, so Kotlin sources that drive the
|
|
43
|
+
// lightweight API directly are missed. The negative lookbehind `(?<!\bnew\s+)`
|
|
44
|
+
// keeps this additive: `new X448Agreement(` stays with RE_JAVA_BC (java-xdh),
|
|
45
|
+
// while bare `X448Agreement(` routes to the family-specific rules below. Scoped
|
|
46
|
+
// to the exact X448/X25519/Ed448/Ed25519 class names to stay precise.
|
|
47
|
+
const RE_JAVA_BC_CURVE =
|
|
48
|
+
/(?<!\bnew\s+)\b(X448KeyPairGenerator|X448Agreement|X448PrivateKeyParameters|X25519KeyPairGenerator|X25519Agreement|Ed448KeyPairGenerator|Ed448Signer|Ed25519KeyPairGenerator|Ed25519Signer)\s*\(/g;
|
|
49
|
+
|
|
50
|
+
// Insecure JSSE / TLS configuration expressed in Java source. Mirrors the JS
|
|
51
|
+
// tlsDetector in source.ts (category "tls"): a legacy SSL/TLS protocol version
|
|
52
|
+
// requested from SSLContext, and TLS hostname verification neutered via an
|
|
53
|
+
// all-trusting verifier (Apache HttpClient's NoopHostnameVerifier /
|
|
54
|
+
// ALLOW_ALL_HOSTNAME_VERIFIER).
|
|
55
|
+
const RE_JAVA_TLS_LEGACY = /\bSSLContext\s*\.\s*getInstance\s*\(\s*"(SSL|SSLv3|TLSv1)"/g;
|
|
56
|
+
const RE_JAVA_TLS_NOVERIFY = /\b(NoopHostnameVerifier|ALLOW_ALL_HOSTNAME_VERIFIER)\b/g;
|
|
57
|
+
|
|
58
|
+
// Identifier-form JWT/JOSE signature algorithms (audit F7). The quoted-string
|
|
59
|
+
// alg token ("RS256") is caught by the language-agnostic jwt-jose detector, but
|
|
60
|
+
// the Java JWT libraries pass the alg as an IDENTIFIER, not a string literal:
|
|
61
|
+
// jjwt's `SignatureAlgorithm.RS256` and auth0 java-jwt's `Algorithm.RSA256` /
|
|
62
|
+
// `Algorithm.ECDSA256`. ("SignatureAlgorithm" has no word boundary before its
|
|
63
|
+
// "Algorithm", so the auth0 branch cannot re-match the jjwt form.)
|
|
64
|
+
const RE_JAVA_JWT_ALG =
|
|
65
|
+
/\bSignatureAlgorithm\.(?:RS|PS|ES)(?:256|384|512)\b|\bAlgorithm\.(?:RSA|ECDSA)(?:256|384|512)\b/g;
|
|
39
66
|
|
|
40
67
|
/* -------------------------------------------------------------------------- */
|
|
41
68
|
/* Rule catalog */
|
|
@@ -138,7 +165,7 @@ const RULE_JAVA_XDH: RuleMeta = {
|
|
|
138
165
|
title: "Java X25519/X448 key agreement",
|
|
139
166
|
description: "JCA XDH / X25519 / X448 (KeyPairGenerator / KeyAgreement)",
|
|
140
167
|
category: "key-exchange",
|
|
141
|
-
severity: "
|
|
168
|
+
severity: "medium",
|
|
142
169
|
confidence: "high",
|
|
143
170
|
algorithm: "X25519",
|
|
144
171
|
hndl: true,
|
|
@@ -158,6 +185,87 @@ const RULE_JAVA_EDDSA: RuleMeta = {
|
|
|
158
185
|
cwe: CWE_BROKEN_CRYPTO,
|
|
159
186
|
message: "Ed25519/Ed448 (Java/JCA) is a modern but still classical signature scheme.",
|
|
160
187
|
};
|
|
188
|
+
// BouncyCastle lightweight-API rules for bare (Kotlin-form) constructor calls.
|
|
189
|
+
// Split by family so X448 and X25519 report their own algorithm string (java-xdh
|
|
190
|
+
// lumps both under "X25519"). Severity/CWE copied from the JCA siblings above.
|
|
191
|
+
const RULE_JAVA_BC_X448: RuleMeta = {
|
|
192
|
+
id: "java-bc-x448",
|
|
193
|
+
title: "Java/Kotlin X448 key agreement (BouncyCastle lightweight API)",
|
|
194
|
+
description: "BouncyCastle X448KeyPairGenerator / X448Agreement / X448PrivateKeyParameters",
|
|
195
|
+
category: "key-exchange",
|
|
196
|
+
severity: "medium",
|
|
197
|
+
confidence: "high",
|
|
198
|
+
algorithm: "X448",
|
|
199
|
+
hndl: true,
|
|
200
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
201
|
+
message:
|
|
202
|
+
"X448 (Java/Kotlin, BouncyCastle lightweight API) is modern but still classical key agreement — harvest-now-decrypt-later.",
|
|
203
|
+
};
|
|
204
|
+
const RULE_JAVA_BC_X25519: RuleMeta = {
|
|
205
|
+
id: "java-bc-x25519",
|
|
206
|
+
title: "Java/Kotlin X25519 key agreement (BouncyCastle lightweight API)",
|
|
207
|
+
description: "BouncyCastle X25519KeyPairGenerator / X25519Agreement",
|
|
208
|
+
category: "key-exchange",
|
|
209
|
+
severity: "medium",
|
|
210
|
+
confidence: "high",
|
|
211
|
+
algorithm: "X25519",
|
|
212
|
+
hndl: true,
|
|
213
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
214
|
+
message:
|
|
215
|
+
"X25519 (Java/Kotlin, BouncyCastle lightweight API) is modern but still classical key agreement — harvest-now-decrypt-later.",
|
|
216
|
+
};
|
|
217
|
+
const RULE_JAVA_BC_EDDSA: RuleMeta = {
|
|
218
|
+
id: "java-bc-eddsa",
|
|
219
|
+
title: "Java/Kotlin Ed25519/Ed448 signature (BouncyCastle lightweight API)",
|
|
220
|
+
description:
|
|
221
|
+
"BouncyCastle Ed448KeyPairGenerator / Ed448Signer / Ed25519KeyPairGenerator / Ed25519Signer",
|
|
222
|
+
category: "signature",
|
|
223
|
+
severity: "low",
|
|
224
|
+
confidence: "high",
|
|
225
|
+
algorithm: "EdDSA",
|
|
226
|
+
hndl: false,
|
|
227
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
228
|
+
message:
|
|
229
|
+
"Ed25519/Ed448 (Java/Kotlin, BouncyCastle lightweight API) is a modern but still classical signature scheme.",
|
|
230
|
+
};
|
|
231
|
+
const RULE_JAVA_TLS_LEGACY: RuleMeta = {
|
|
232
|
+
id: "java-tls-legacy-version",
|
|
233
|
+
title: "Legacy SSL/TLS version requested",
|
|
234
|
+
description: 'SSLContext.getInstance("SSL" | "SSLv3" | "TLSv1")',
|
|
235
|
+
category: "tls",
|
|
236
|
+
severity: "medium",
|
|
237
|
+
confidence: "high",
|
|
238
|
+
hndl: false,
|
|
239
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
240
|
+
message: "SSL/SSLv3/TLS 1.0 are deprecated and insecure (Java/JSSE); require TLS 1.3.",
|
|
241
|
+
remediation: 'Use SSLContext.getInstance("TLSv1.3") and prefer PQC-hybrid key exchange.',
|
|
242
|
+
};
|
|
243
|
+
const RULE_JAVA_TLS_NOVERIFY: RuleMeta = {
|
|
244
|
+
id: "java-tls-hostname-verification-disabled",
|
|
245
|
+
title: "TLS hostname verification disabled",
|
|
246
|
+
description: "NoopHostnameVerifier / ALLOW_ALL_HOSTNAME_VERIFIER",
|
|
247
|
+
category: "tls",
|
|
248
|
+
severity: "high",
|
|
249
|
+
confidence: "high",
|
|
250
|
+
hndl: false,
|
|
251
|
+
cwe: CWE_CERT_VALIDATION,
|
|
252
|
+
message: "An all-trusting hostname verifier (Java) disables TLS hostname checking (MITM risk).",
|
|
253
|
+
remediation: "Remove the all-trusting verifier; rely on the default hostname verifier.",
|
|
254
|
+
};
|
|
255
|
+
const RULE_JAVA_JWT_ALG: RuleMeta = {
|
|
256
|
+
id: "java-jwt-alg",
|
|
257
|
+
title: "Java identifier-form JWT/JOSE algorithm",
|
|
258
|
+
description: "jjwt SignatureAlgorithm.RS/PS/ES* / auth0 Algorithm.RSA*/ECDSA*",
|
|
259
|
+
category: "signature",
|
|
260
|
+
severity: "high",
|
|
261
|
+
confidence: "high",
|
|
262
|
+
algorithm: "unknown",
|
|
263
|
+
hndl: false,
|
|
264
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
265
|
+
message:
|
|
266
|
+
"A classical JWT/JOSE signature algorithm (Java, identifier form) is used, forgeable by a quantum attacker.",
|
|
267
|
+
remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
|
|
268
|
+
};
|
|
161
269
|
|
|
162
270
|
/**
|
|
163
271
|
* Classify a `<factory>.getInstance("<alg>")` pair into a rule, or null when the
|
|
@@ -175,6 +283,11 @@ function classifyGetInstance(factory: string, rawAlg: string): RuleMeta | null {
|
|
|
175
283
|
if (alg.includes("ED25519") || alg.includes("ED448") || alg.includes("EDDSA"))
|
|
176
284
|
return RULE_JAVA_EDDSA;
|
|
177
285
|
if (alg.includes("X25519") || alg.includes("X448") || alg === "XDH") return RULE_JAVA_XDH;
|
|
286
|
+
// RSASSA-PSS is an RSA *signature* scheme, not encryption/KEM. The string
|
|
287
|
+
// contains "RSA", so it must be caught here — BEFORE the generic RSA branch
|
|
288
|
+
// below, which would otherwise (mis)classify it as HNDL-exposed RSA
|
|
289
|
+
// encryption (java-rsa / hndl:true). Signatures are hndl:false. (Fixes F8.)
|
|
290
|
+
if (alg.includes("PSS")) return RULE_JAVA_RSA_SIGN;
|
|
178
291
|
if (alg.includes("RSA")) return isSignature ? RULE_JAVA_RSA_SIGN : RULE_JAVA_RSA;
|
|
179
292
|
if (alg.includes("DSA")) return RULE_JAVA_DSA; // ECDSA already handled above
|
|
180
293
|
if (alg.includes("DH") || alg.includes("DIFFIEHELLMAN")) return RULE_JAVA_DH;
|
|
@@ -191,6 +304,25 @@ const BC_CLASS_RULES: Record<string, RuleMeta> = {
|
|
|
191
304
|
Ed448Signer: RULE_JAVA_EDDSA,
|
|
192
305
|
X25519Agreement: RULE_JAVA_XDH,
|
|
193
306
|
X448Agreement: RULE_JAVA_XDH,
|
|
307
|
+
ECDHBasicAgreement: RULE_JAVA_ECDH,
|
|
308
|
+
DHBasicAgreement: RULE_JAVA_DH,
|
|
309
|
+
X25519KeyPairGenerator: RULE_JAVA_XDH,
|
|
310
|
+
Ed25519KeyPairGenerator: RULE_JAVA_EDDSA,
|
|
311
|
+
RSAEngine: RULE_JAVA_RSA,
|
|
312
|
+
OAEPEncoding: RULE_JAVA_RSA,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/** BouncyCastle lightweight-API bare-constructor class name → family rule. */
|
|
316
|
+
const BC_CURVE_CLASS_RULES: Record<string, RuleMeta> = {
|
|
317
|
+
X448KeyPairGenerator: RULE_JAVA_BC_X448,
|
|
318
|
+
X448Agreement: RULE_JAVA_BC_X448,
|
|
319
|
+
X448PrivateKeyParameters: RULE_JAVA_BC_X448,
|
|
320
|
+
X25519KeyPairGenerator: RULE_JAVA_BC_X25519,
|
|
321
|
+
X25519Agreement: RULE_JAVA_BC_X25519,
|
|
322
|
+
Ed448KeyPairGenerator: RULE_JAVA_BC_EDDSA,
|
|
323
|
+
Ed448Signer: RULE_JAVA_BC_EDDSA,
|
|
324
|
+
Ed25519KeyPairGenerator: RULE_JAVA_BC_EDDSA,
|
|
325
|
+
Ed25519Signer: RULE_JAVA_BC_EDDSA,
|
|
194
326
|
};
|
|
195
327
|
|
|
196
328
|
/** Detects classical asymmetric crypto in Java / Kotlin (JCA + BouncyCastle). */
|
|
@@ -209,6 +341,12 @@ export const javaDetector: Detector = {
|
|
|
209
341
|
RULE_JAVA_DH,
|
|
210
342
|
RULE_JAVA_XDH,
|
|
211
343
|
RULE_JAVA_EDDSA,
|
|
344
|
+
RULE_JAVA_BC_X448,
|
|
345
|
+
RULE_JAVA_BC_X25519,
|
|
346
|
+
RULE_JAVA_BC_EDDSA,
|
|
347
|
+
RULE_JAVA_TLS_LEGACY,
|
|
348
|
+
RULE_JAVA_TLS_NOVERIFY,
|
|
349
|
+
RULE_JAVA_JWT_ALG,
|
|
212
350
|
],
|
|
213
351
|
appliesTo: (f) => hasExtension(f, JAVA_EXTENSIONS),
|
|
214
352
|
detect({ file, content }): Finding[] {
|
|
@@ -230,6 +368,54 @@ export const javaDetector: Detector = {
|
|
|
230
368
|
);
|
|
231
369
|
});
|
|
232
370
|
|
|
371
|
+
// BouncyCastle lightweight-API classes as a bare constructor call (Kotlin
|
|
372
|
+
// form, no `new`). RE_JAVA_BC_CURVE's negative lookbehind excludes the
|
|
373
|
+
// `new <Class>(` form already handled above, so this only adds the missed
|
|
374
|
+
// 448/25519 Kotlin-style matches — no double-counting.
|
|
375
|
+
eachMatch(RE_JAVA_BC_CURVE, content, (m) => {
|
|
376
|
+
const rule = BC_CURVE_CLASS_RULES[m[1]];
|
|
377
|
+
if (!rule) return;
|
|
378
|
+
findings.push(
|
|
379
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Legacy SSL/TLS protocol version requested from SSLContext.
|
|
384
|
+
eachMatch(RE_JAVA_TLS_LEGACY, content, (m) => {
|
|
385
|
+
findings.push(
|
|
386
|
+
findingFromRule(RULE_JAVA_TLS_LEGACY, {
|
|
387
|
+
file,
|
|
388
|
+
content,
|
|
389
|
+
index: m.index,
|
|
390
|
+
matchLength: m[0].length,
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// All-trusting hostname verifier — disables TLS hostname verification.
|
|
396
|
+
eachMatch(RE_JAVA_TLS_NOVERIFY, content, (m) => {
|
|
397
|
+
findings.push(
|
|
398
|
+
findingFromRule(RULE_JAVA_TLS_NOVERIFY, {
|
|
399
|
+
file,
|
|
400
|
+
content,
|
|
401
|
+
index: m.index,
|
|
402
|
+
matchLength: m[0].length,
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Identifier-form JWT/JOSE signature algorithms (jjwt / auth0).
|
|
408
|
+
eachMatch(RE_JAVA_JWT_ALG, content, (m) => {
|
|
409
|
+
findings.push(
|
|
410
|
+
findingFromRule(RULE_JAVA_JWT_ALG, {
|
|
411
|
+
file,
|
|
412
|
+
content,
|
|
413
|
+
index: m.index,
|
|
414
|
+
matchLength: m[0].length,
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
|
|
233
419
|
return findings;
|
|
234
420
|
},
|
|
235
421
|
};
|
package/src/detectors/pem.ts
CHANGED
|
@@ -86,7 +86,8 @@ const PEM_RULES: PemRule[] = [
|
|
|
86
86
|
cwe: CWE_HARDCODED_KEY,
|
|
87
87
|
sensitive: true,
|
|
88
88
|
message: "Embedded OpenSSH private key (RSA/ECDSA/Ed25519); classical and not quantum-safe.",
|
|
89
|
-
remediation:
|
|
89
|
+
remediation:
|
|
90
|
+
"Rotate the key; plan migration to PQC-capable SSH (prefer the mlkem768x25519-sha256 KEX, OpenSSH 10's default since Apr 2025).",
|
|
90
91
|
},
|
|
91
92
|
},
|
|
92
93
|
{
|
|
@@ -157,6 +158,57 @@ const PEM_RULES: PemRule[] = [
|
|
|
157
158
|
remediation: "Plan re-issuance with PQC-capable CAs as ML-DSA certificate profiles mature.",
|
|
158
159
|
},
|
|
159
160
|
},
|
|
161
|
+
{
|
|
162
|
+
re: /-----BEGIN (?:RSA )?PUBLIC KEY-----/g,
|
|
163
|
+
meta: {
|
|
164
|
+
id: "pem-public-key",
|
|
165
|
+
title: "Classical public key (PEM)",
|
|
166
|
+
description: "SubjectPublicKeyInfo / PKCS#1 RSA public key block",
|
|
167
|
+
category: "certificate",
|
|
168
|
+
severity: "low",
|
|
169
|
+
confidence: "high",
|
|
170
|
+
algorithm: "unknown",
|
|
171
|
+
hndl: false,
|
|
172
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
173
|
+
message:
|
|
174
|
+
"Embedded classical public key (RSA/EC/DSA); its key pair is not quantum-safe — forgeable signatures or classical key exchange.",
|
|
175
|
+
remediation: "Re-issue with PQC keys (ML-DSA / ML-KEM) as the ecosystem adopts them.",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
re: /-----BEGIN DH PARAMETERS-----/g,
|
|
180
|
+
meta: {
|
|
181
|
+
id: "pem-dh-parameters",
|
|
182
|
+
title: "Diffie-Hellman parameters (PEM)",
|
|
183
|
+
description: "Finite-field DH group parameters block",
|
|
184
|
+
category: "key-exchange",
|
|
185
|
+
severity: "medium",
|
|
186
|
+
confidence: "high",
|
|
187
|
+
algorithm: "DH",
|
|
188
|
+
hndl: true,
|
|
189
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
190
|
+
message:
|
|
191
|
+
"Embedded finite-field Diffie-Hellman parameters; classical DH key exchange is harvest-now-decrypt-later exposed.",
|
|
192
|
+
remediation: "Migrate key exchange to hybrid X25519MLKEM768 (ML-KEM-768).",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
re: /-----BEGIN (?:NEW )?CERTIFICATE REQUEST-----/g,
|
|
197
|
+
meta: {
|
|
198
|
+
id: "pem-cert-request",
|
|
199
|
+
title: "Certificate signing request (PEM)",
|
|
200
|
+
description: "PKCS#10 certificate request block",
|
|
201
|
+
category: "certificate",
|
|
202
|
+
severity: "low",
|
|
203
|
+
confidence: "high",
|
|
204
|
+
algorithm: "unknown",
|
|
205
|
+
hndl: false,
|
|
206
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
207
|
+
message:
|
|
208
|
+
"Embedded PKCS#10 CSR; carries a classical public key and will be signed with classical crypto.",
|
|
209
|
+
remediation: "Re-generate with PQC keys as PQC-capable CAs mature.",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
160
212
|
];
|
|
161
213
|
|
|
162
214
|
/** Detects PEM key/certificate material in arbitrary files. */
|
package/src/detectors/python.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
24
24
|
import { PYTHON_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
25
|
-
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
25
|
+
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
26
26
|
|
|
27
27
|
/* -------------------------------------------------------------------------- */
|
|
28
28
|
/* Precompiled regexes (module scope) */
|
|
@@ -38,8 +38,15 @@ const RE_PY_RSA_ENCRYPT = /\bpadding\.OAEP\s*\(|\bPKCS1_OAEP\.new\s*\(/g;
|
|
|
38
38
|
const RE_PY_EC_KEYGEN = /\bec\.generate_private_key\s*\(|\bECC\.generate\s*\(/g;
|
|
39
39
|
// ECDSA signatures: cryptography `ec.ECDSA(`, paramiko ECDSAKey.
|
|
40
40
|
const RE_PY_ECDSA = /\bec\.ECDSA\s*\(|\bparamiko\.ECDSAKey\b|\bECDSAKey\.generate\s*\(/g;
|
|
41
|
+
// ECDH key agreement: cryptography `ec.ECDH(` — the actual harvest-now event in
|
|
42
|
+
// `private_key.exchange(ec.ECDH(), peer)`; previously missed entirely (audit F3).
|
|
43
|
+
const RE_PY_ECDH = /\bec\.ECDH\s*\(/g;
|
|
41
44
|
// DSA: PyCryptodome `DSA.generate(`, paramiko DSSKey.
|
|
42
45
|
const RE_PY_DSA = /\bDSA\.generate\s*\(|\bparamiko\.DSSKey\b|\bDSSKey\.generate\s*\(/g;
|
|
46
|
+
// hazmat DSA: cryptography `dsa.generate_private_key(` — the `cryptography` DSA
|
|
47
|
+
// keygen path (lowercase module) that RE_PY_DSA's uppercase `DSA.generate` misses
|
|
48
|
+
// (audit F9-python).
|
|
49
|
+
const RE_PY_HAZMAT_DSA = /\bdsa\.generate_private_key\s*\(/g;
|
|
43
50
|
// Finite-field Diffie-Hellman (cryptography `dh`).
|
|
44
51
|
const RE_PY_DH = /\bdh\.generate_parameters\s*\(|\bdh\.DHParameterNumbers\s*\(/g;
|
|
45
52
|
// Modern-but-classical curve primitives (cryptography). `.generate(` anchors so
|
|
@@ -48,6 +55,15 @@ const RE_PY_X25519 = /\bX25519PrivateKey\.generate\s*\(/g;
|
|
|
48
55
|
const RE_PY_X448 = /\bX448PrivateKey\.generate\s*\(/g;
|
|
49
56
|
const RE_PY_EDDSA = /\b(?:Ed25519|Ed448)PrivateKey\.generate\s*\(|\bparamiko\.Ed25519Key\b/g;
|
|
50
57
|
|
|
58
|
+
// Python TLS misconfiguration. Mirrors the JS `tlsDetector` split of source.ts:
|
|
59
|
+
// certificate-verification bypass (requests `verify=False`, `ssl.CERT_NONE`,
|
|
60
|
+
// `check_hostname=False`, `ssl._create_unverified_context(`) is the high-severity
|
|
61
|
+
// MITM surface (CWE-295); a pinned legacy protocol (`ssl.PROTOCOL_TLSv1`) is the
|
|
62
|
+
// medium-severity weak-strength surface (CWE-326).
|
|
63
|
+
const RE_PY_TLS_REJECT =
|
|
64
|
+
/\bverify\s*=\s*False\b|\bssl\.CERT_NONE\b|\bcheck_hostname\s*=\s*False\b|\bssl\._create_unverified_context\s*\(/g;
|
|
65
|
+
const RE_PY_TLS_LEGACY = /\bPROTOCOL_TLSv1\b/g;
|
|
66
|
+
|
|
51
67
|
/* -------------------------------------------------------------------------- */
|
|
52
68
|
/* Rule catalog */
|
|
53
69
|
/* -------------------------------------------------------------------------- */
|
|
@@ -106,6 +122,19 @@ const RULE_PY_ECDSA: RuleMeta = {
|
|
|
106
122
|
message: "Classical ECDSA signing (Python) is forgeable by a quantum attacker.",
|
|
107
123
|
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
108
124
|
};
|
|
125
|
+
const RULE_PY_ECDH: RuleMeta = {
|
|
126
|
+
id: "python-ecdh",
|
|
127
|
+
title: "Python ECDH key agreement",
|
|
128
|
+
description: "cryptography ec.ECDH() exchange",
|
|
129
|
+
category: "key-exchange",
|
|
130
|
+
severity: "high",
|
|
131
|
+
confidence: "high",
|
|
132
|
+
algorithm: "ECDH",
|
|
133
|
+
hndl: true,
|
|
134
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
135
|
+
message:
|
|
136
|
+
"Elliptic-curve Diffie-Hellman key agreement (Python) is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
137
|
+
};
|
|
109
138
|
const RULE_PY_DSA: RuleMeta = {
|
|
110
139
|
id: "python-dsa",
|
|
111
140
|
title: "Python DSA key/usage",
|
|
@@ -119,6 +148,20 @@ const RULE_PY_DSA: RuleMeta = {
|
|
|
119
148
|
message: "Classical DSA (Python) is deprecated and forgeable by a quantum attacker.",
|
|
120
149
|
remediation: "Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).",
|
|
121
150
|
};
|
|
151
|
+
const RULE_PY_HAZMAT_DSA: RuleMeta = {
|
|
152
|
+
id: "python-hazmat-dsa",
|
|
153
|
+
title: "Python DSA key generation (cryptography)",
|
|
154
|
+
description: "cryptography dsa.generate_private_key",
|
|
155
|
+
category: "signature",
|
|
156
|
+
severity: "high",
|
|
157
|
+
confidence: "high",
|
|
158
|
+
algorithm: "DSA",
|
|
159
|
+
hndl: false,
|
|
160
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
161
|
+
message:
|
|
162
|
+
"cryptography dsa.generate_private_key (Python) creates a classical DSA key; DSA is deprecated and forgeable by a quantum attacker.",
|
|
163
|
+
remediation: "Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).",
|
|
164
|
+
};
|
|
122
165
|
const RULE_PY_DH: RuleMeta = {
|
|
123
166
|
id: "python-dh",
|
|
124
167
|
title: "Python Diffie-Hellman key exchange",
|
|
@@ -137,7 +180,7 @@ const RULE_PY_X25519: RuleMeta = {
|
|
|
137
180
|
title: "Python X25519 key exchange",
|
|
138
181
|
description: "cryptography X25519PrivateKey.generate",
|
|
139
182
|
category: "key-exchange",
|
|
140
|
-
severity: "
|
|
183
|
+
severity: "medium",
|
|
141
184
|
confidence: "high",
|
|
142
185
|
algorithm: "X25519",
|
|
143
186
|
hndl: true,
|
|
@@ -150,7 +193,7 @@ const RULE_PY_X448: RuleMeta = {
|
|
|
150
193
|
title: "Python X448 key exchange",
|
|
151
194
|
description: "cryptography X448PrivateKey.generate",
|
|
152
195
|
category: "key-exchange",
|
|
153
|
-
severity: "
|
|
196
|
+
severity: "medium",
|
|
154
197
|
confidence: "high",
|
|
155
198
|
algorithm: "X448",
|
|
156
199
|
hndl: true,
|
|
@@ -169,6 +212,34 @@ const RULE_PY_EDDSA: RuleMeta = {
|
|
|
169
212
|
cwe: CWE_BROKEN_CRYPTO,
|
|
170
213
|
message: "Ed25519/Ed448 (Python) is a modern but still classical signature scheme.",
|
|
171
214
|
};
|
|
215
|
+
const RULE_PY_TLS_REJECT: RuleMeta = {
|
|
216
|
+
id: "python-tls-reject",
|
|
217
|
+
title: "Python TLS certificate verification disabled",
|
|
218
|
+
description:
|
|
219
|
+
"requests verify=False / ssl.CERT_NONE / check_hostname=False / _create_unverified_context",
|
|
220
|
+
category: "tls",
|
|
221
|
+
severity: "high",
|
|
222
|
+
confidence: "high",
|
|
223
|
+
hndl: false,
|
|
224
|
+
cwe: CWE_CERT_VALIDATION,
|
|
225
|
+
message:
|
|
226
|
+
"TLS certificate verification is disabled (verify=False / CERT_NONE / check_hostname=False / _create_unverified_context), which allows man-in-the-middle attacks.",
|
|
227
|
+
remediation:
|
|
228
|
+
"Enable certificate verification (verify=True, ssl.CERT_REQUIRED, check_hostname=True) and verify certificates properly.",
|
|
229
|
+
};
|
|
230
|
+
const RULE_PY_TLS_LEGACY: RuleMeta = {
|
|
231
|
+
id: "python-tls-legacy-version",
|
|
232
|
+
title: "Python legacy TLS version pinned",
|
|
233
|
+
description: "ssl.PROTOCOL_TLSv1 (TLS 1.0)",
|
|
234
|
+
category: "tls",
|
|
235
|
+
severity: "medium",
|
|
236
|
+
confidence: "high",
|
|
237
|
+
hndl: false,
|
|
238
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
239
|
+
message: "TLS 1.0 (ssl.PROTOCOL_TLSv1) is deprecated and insecure; require TLS 1.3.",
|
|
240
|
+
remediation:
|
|
241
|
+
"Use ssl.PROTOCOL_TLS_CLIENT with minimum_version = ssl.TLSVersion.TLSv1_3 and prefer PQC-hybrid key exchange.",
|
|
242
|
+
};
|
|
172
243
|
|
|
173
244
|
/** Detects classical asymmetric crypto in Python source. */
|
|
174
245
|
export const pythonDetector: Detector = {
|
|
@@ -181,11 +252,15 @@ export const pythonDetector: Detector = {
|
|
|
181
252
|
RULE_PY_RSA_ENCRYPT,
|
|
182
253
|
RULE_PY_EC_KEYGEN,
|
|
183
254
|
RULE_PY_ECDSA,
|
|
255
|
+
RULE_PY_ECDH,
|
|
184
256
|
RULE_PY_DSA,
|
|
257
|
+
RULE_PY_HAZMAT_DSA,
|
|
185
258
|
RULE_PY_DH,
|
|
186
259
|
RULE_PY_X25519,
|
|
187
260
|
RULE_PY_X448,
|
|
188
261
|
RULE_PY_EDDSA,
|
|
262
|
+
RULE_PY_TLS_REJECT,
|
|
263
|
+
RULE_PY_TLS_LEGACY,
|
|
189
264
|
],
|
|
190
265
|
appliesTo: (f) => hasExtension(f, PYTHON_EXTENSIONS),
|
|
191
266
|
detect({ file, content }): Finding[] {
|
|
@@ -201,11 +276,15 @@ export const pythonDetector: Detector = {
|
|
|
201
276
|
add(RE_PY_RSA_ENCRYPT, RULE_PY_RSA_ENCRYPT);
|
|
202
277
|
add(RE_PY_EC_KEYGEN, RULE_PY_EC_KEYGEN);
|
|
203
278
|
add(RE_PY_ECDSA, RULE_PY_ECDSA);
|
|
279
|
+
add(RE_PY_ECDH, RULE_PY_ECDH);
|
|
204
280
|
add(RE_PY_DSA, RULE_PY_DSA);
|
|
281
|
+
add(RE_PY_HAZMAT_DSA, RULE_PY_HAZMAT_DSA);
|
|
205
282
|
add(RE_PY_DH, RULE_PY_DH);
|
|
206
283
|
add(RE_PY_X25519, RULE_PY_X25519);
|
|
207
284
|
add(RE_PY_X448, RULE_PY_X448);
|
|
208
285
|
add(RE_PY_EDDSA, RULE_PY_EDDSA);
|
|
286
|
+
add(RE_PY_TLS_REJECT, RULE_PY_TLS_REJECT);
|
|
287
|
+
add(RE_PY_TLS_LEGACY, RULE_PY_TLS_LEGACY);
|
|
209
288
|
|
|
210
289
|
return findings;
|
|
211
290
|
},
|
package/src/detectors/ruby.ts
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Source-code detector for classical asymmetric cryptography in Ruby. Ruby's
|
|
3
3
|
* asymmetric crypto is almost entirely `OpenSSL::PKey::{RSA,EC,DSA,DH}`, which
|
|
4
|
-
* makes the signals precise. Lexical, same strategy as the other packs.
|
|
4
|
+
* makes the signals precise. Lexical, same strategy as the other packs. Beyond
|
|
5
|
+
* keygen it also covers RSA public-key encryption on a loaded key, DH key
|
|
6
|
+
* agreement, the type-agnostic `OpenSSL::PKey.read` loader, Ed25519 key
|
|
7
|
+
* generation, and disabled TLS peer verification (`VERIFY_NONE`).
|
|
5
8
|
*
|
|
6
9
|
* HNDL: RSA (keygen/encryption) and (EC)DH key agreement are harvest-now-
|
|
7
|
-
* decrypt-later exposed (hndl:true); DSA signatures are hndl:false but
|
|
8
|
-
* `OpenSSL::PKey::EC` is ambiguous (feeds ECDSA + ECDH), so it is
|
|
9
|
-
* conservatively as key-exchange-capable (hndl:true), like the other
|
|
10
|
-
* rules.
|
|
10
|
+
* decrypt-later exposed (hndl:true); DSA / Ed25519 signatures are hndl:false but
|
|
11
|
+
* forgeable. `OpenSSL::PKey::EC` is ambiguous (feeds ECDSA + ECDH), so it is
|
|
12
|
+
* classified conservatively as key-exchange-capable (hndl:true), like the other
|
|
13
|
+
* EC keygen rules; `OpenSSL::PKey.read` loads a key of unknown type and is
|
|
14
|
+
* likewise treated conservatively.
|
|
11
15
|
*/
|
|
12
16
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
13
17
|
import { RUBY_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
14
|
-
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
18
|
+
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION } from "../cwe.js";
|
|
15
19
|
|
|
16
20
|
const RE_RB_RSA = /\bOpenSSL::PKey::RSA\.(?:new|generate)\s*\(/g;
|
|
17
21
|
const RE_RB_EC = /\bOpenSSL::PKey::EC\.(?:new|generate)\s*\(/g;
|
|
18
22
|
const RE_RB_DSA = /\bOpenSSL::PKey::DSA\.(?:new|generate)\s*\(/g;
|
|
19
23
|
const RE_RB_DH = /\bOpenSSL::PKey::DH\.new\s*\(/g;
|
|
24
|
+
// RSA public-key encryption on an already-loaded key (audit F4-ruby / F9).
|
|
25
|
+
const RE_RB_RSA_CRYPT = /\.public_encrypt\b|\.private_decrypt\b/g;
|
|
26
|
+
// Finite-field DH shared-secret agreement.
|
|
27
|
+
const RE_RB_DH_AGREE = /\bdh_compute_key\s*\(/g;
|
|
28
|
+
// Type-agnostic key loader — could be RSA/EC/DSA/DH (conservative).
|
|
29
|
+
const RE_RB_PKEY_READ = /\bOpenSSL::PKey\.read\s*\(/g;
|
|
30
|
+
// Ed25519 signing key via the generic generate_key factory.
|
|
31
|
+
const RE_RB_ED25519 = /\bOpenSSL::PKey\.generate_key\s*\(\s*["']ED25519["']/g;
|
|
32
|
+
// `ed25519` gem — Ed25519::SigningKey / VerifyKey (signature keys).
|
|
33
|
+
const RE_RB_ED25519_GEM = /\bEd25519::(?:SigningKey|VerifyKey)\b/g;
|
|
34
|
+
// `rbnacl` (libsodium) — Curve25519/X25519 key agreement: PrivateKey, Box, GroupElement.
|
|
35
|
+
const RE_RB_RBNACL = /\bRbNaCl::(?:PrivateKey|Box|GroupElement)\b/g;
|
|
36
|
+
// TLS peer verification disabled (mirrors the JS tlsDetector rejectUnauthorized rule).
|
|
37
|
+
const RE_RB_TLS_VERIFY_NONE = /\bOpenSSL::SSL::VERIFY_NONE\b/g;
|
|
20
38
|
|
|
21
39
|
const RULE_RB_RSA: RuleMeta = {
|
|
22
40
|
id: "ruby-rsa",
|
|
@@ -71,6 +89,100 @@ const RULE_RB_DH: RuleMeta = {
|
|
|
71
89
|
message:
|
|
72
90
|
"Finite-field Diffie-Hellman (Ruby/OpenSSL) is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
73
91
|
};
|
|
92
|
+
const RULE_RB_RSA_CRYPT: RuleMeta = {
|
|
93
|
+
id: "ruby-rsa-crypt",
|
|
94
|
+
title: "Ruby RSA public-key encryption",
|
|
95
|
+
description: "OpenSSL::PKey::RSA#public_encrypt / #private_decrypt",
|
|
96
|
+
category: "kem",
|
|
97
|
+
severity: "high",
|
|
98
|
+
confidence: "high",
|
|
99
|
+
algorithm: "RSA",
|
|
100
|
+
hndl: true,
|
|
101
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
102
|
+
message:
|
|
103
|
+
"RSA public-key encryption/decryption (Ruby/OpenSSL) is harvest-now-decrypt-later exposed.",
|
|
104
|
+
};
|
|
105
|
+
const RULE_RB_DH_AGREE: RuleMeta = {
|
|
106
|
+
id: "ruby-dh-agree",
|
|
107
|
+
title: "Ruby Diffie-Hellman key agreement",
|
|
108
|
+
description: "OpenSSL DH compute_key shared-secret agreement",
|
|
109
|
+
category: "key-exchange",
|
|
110
|
+
severity: "high",
|
|
111
|
+
confidence: "high",
|
|
112
|
+
algorithm: "DH",
|
|
113
|
+
hndl: true,
|
|
114
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
115
|
+
message:
|
|
116
|
+
"Finite-field Diffie-Hellman key agreement (Ruby/OpenSSL) is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
117
|
+
};
|
|
118
|
+
const RULE_RB_PKEY_READ: RuleMeta = {
|
|
119
|
+
id: "ruby-pkey-read",
|
|
120
|
+
title: "Ruby PKey loaded from serialized key",
|
|
121
|
+
description: "OpenSSL::PKey.read (type-agnostic key loader)",
|
|
122
|
+
category: "key-exchange",
|
|
123
|
+
severity: "high",
|
|
124
|
+
confidence: "medium",
|
|
125
|
+
algorithm: "unknown",
|
|
126
|
+
hndl: true,
|
|
127
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
128
|
+
message:
|
|
129
|
+
"Loads a classical asymmetric key of unknown type (RSA/EC/DSA/DH) via OpenSSL::PKey.read. Treated conservatively as key-exchange-capable (harvest-now-decrypt-later).",
|
|
130
|
+
remediation:
|
|
131
|
+
"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
132
|
+
};
|
|
133
|
+
const RULE_RB_ED25519: RuleMeta = {
|
|
134
|
+
id: "ruby-ed25519",
|
|
135
|
+
title: "Ruby Ed25519 key generation",
|
|
136
|
+
description: 'OpenSSL::PKey.generate_key("ED25519")',
|
|
137
|
+
category: "signature",
|
|
138
|
+
severity: "low",
|
|
139
|
+
confidence: "high",
|
|
140
|
+
algorithm: "EdDSA",
|
|
141
|
+
hndl: false,
|
|
142
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
143
|
+
message:
|
|
144
|
+
"Generates an Ed25519 signing key (Ruby/OpenSSL) — modern but classical, and forgeable by a quantum attacker.",
|
|
145
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
146
|
+
};
|
|
147
|
+
const RULE_RB_ED25519_GEM: RuleMeta = {
|
|
148
|
+
id: "ruby-ed25519-gem",
|
|
149
|
+
title: "Ruby Ed25519 signature (ed25519 gem)",
|
|
150
|
+
description: "ed25519 gem Ed25519::SigningKey / Ed25519::VerifyKey",
|
|
151
|
+
category: "signature",
|
|
152
|
+
severity: "low",
|
|
153
|
+
confidence: "high",
|
|
154
|
+
algorithm: "EdDSA",
|
|
155
|
+
hndl: false,
|
|
156
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
157
|
+
message:
|
|
158
|
+
"Ed25519 signing/verification via the `ed25519` gem — modern but classical, and forgeable by a quantum attacker.",
|
|
159
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
|
|
160
|
+
};
|
|
161
|
+
const RULE_RB_RBNACL: RuleMeta = {
|
|
162
|
+
id: "ruby-rbnacl",
|
|
163
|
+
title: "Ruby X25519 key agreement (rbnacl)",
|
|
164
|
+
description: "rbnacl (libsodium) RbNaCl::PrivateKey / Box / GroupElement",
|
|
165
|
+
category: "key-exchange",
|
|
166
|
+
severity: "medium",
|
|
167
|
+
confidence: "high",
|
|
168
|
+
algorithm: "X25519",
|
|
169
|
+
hndl: true,
|
|
170
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
171
|
+
message:
|
|
172
|
+
"Curve25519/X25519 key agreement via the `rbnacl` gem (libsodium) — modern but classical key agreement, harvest-now-decrypt-later exposed.",
|
|
173
|
+
};
|
|
174
|
+
const RULE_RB_TLS_VERIFY_NONE: RuleMeta = {
|
|
175
|
+
id: "ruby-tls-verify-none",
|
|
176
|
+
title: "Ruby TLS certificate verification disabled",
|
|
177
|
+
description: "OpenSSL::SSL::VERIFY_NONE",
|
|
178
|
+
category: "tls",
|
|
179
|
+
severity: "high",
|
|
180
|
+
confidence: "high",
|
|
181
|
+
hndl: false,
|
|
182
|
+
cwe: CWE_CERT_VALIDATION,
|
|
183
|
+
message: "OpenSSL::SSL::VERIFY_NONE disables TLS peer certificate verification (MITM risk).",
|
|
184
|
+
remediation: "Use OpenSSL::SSL::VERIFY_PEER and verify the certificate chain.",
|
|
185
|
+
};
|
|
74
186
|
|
|
75
187
|
/** Detects classical asymmetric crypto in Ruby (OpenSSL::PKey). */
|
|
76
188
|
export const rubyDetector: Detector = {
|
|
@@ -78,7 +190,19 @@ export const rubyDetector: Detector = {
|
|
|
78
190
|
description: "Classical asymmetric crypto in Ruby (OpenSSL::PKey::{RSA,EC,DSA,DH})",
|
|
79
191
|
scope: "source",
|
|
80
192
|
language: "ruby",
|
|
81
|
-
rules: [
|
|
193
|
+
rules: [
|
|
194
|
+
RULE_RB_RSA,
|
|
195
|
+
RULE_RB_EC,
|
|
196
|
+
RULE_RB_DSA,
|
|
197
|
+
RULE_RB_DH,
|
|
198
|
+
RULE_RB_RSA_CRYPT,
|
|
199
|
+
RULE_RB_DH_AGREE,
|
|
200
|
+
RULE_RB_PKEY_READ,
|
|
201
|
+
RULE_RB_ED25519,
|
|
202
|
+
RULE_RB_ED25519_GEM,
|
|
203
|
+
RULE_RB_RBNACL,
|
|
204
|
+
RULE_RB_TLS_VERIFY_NONE,
|
|
205
|
+
],
|
|
82
206
|
appliesTo: (f) => hasExtension(f, RUBY_EXTENSIONS),
|
|
83
207
|
detect({ file, content }): Finding[] {
|
|
84
208
|
const findings: Finding[] = [];
|
|
@@ -92,6 +216,13 @@ export const rubyDetector: Detector = {
|
|
|
92
216
|
add(RE_RB_EC, RULE_RB_EC);
|
|
93
217
|
add(RE_RB_DSA, RULE_RB_DSA);
|
|
94
218
|
add(RE_RB_DH, RULE_RB_DH);
|
|
219
|
+
add(RE_RB_RSA_CRYPT, RULE_RB_RSA_CRYPT);
|
|
220
|
+
add(RE_RB_DH_AGREE, RULE_RB_DH_AGREE);
|
|
221
|
+
add(RE_RB_PKEY_READ, RULE_RB_PKEY_READ);
|
|
222
|
+
add(RE_RB_ED25519, RULE_RB_ED25519);
|
|
223
|
+
add(RE_RB_ED25519_GEM, RULE_RB_ED25519_GEM);
|
|
224
|
+
add(RE_RB_RBNACL, RULE_RB_RBNACL);
|
|
225
|
+
add(RE_RB_TLS_VERIFY_NONE, RULE_RB_TLS_VERIFY_NONE);
|
|
95
226
|
return findings;
|
|
96
227
|
},
|
|
97
228
|
};
|