@quantakrypto/core 0.4.4 → 0.6.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/README.md +76 -27
- package/dist/cbom-merge.d.ts.map +1 -1
- package/dist/cbom-merge.js +30 -8
- package/dist/cbom-merge.js.map +1 -1
- package/dist/cbom.d.ts +5 -0
- package/dist/cbom.d.ts.map +1 -1
- package/dist/cbom.js +132 -37
- package/dist/cbom.js.map +1 -1
- package/dist/comments.d.ts +2 -23
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +24 -5
- package/dist/comments.js.map +1 -1
- package/dist/crypto-agility.d.ts +158 -0
- package/dist/crypto-agility.d.ts.map +1 -0
- package/dist/crypto-agility.js +285 -0
- package/dist/crypto-agility.js.map +1 -0
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +180 -18
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +55 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +133 -7
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/age.d.ts +16 -0
- package/dist/detectors/age.d.ts.map +1 -0
- package/dist/detectors/age.js +40 -0
- package/dist/detectors/age.js.map +1 -0
- package/dist/detectors/ansible.d.ts +13 -0
- package/dist/detectors/ansible.d.ts.map +1 -0
- package/dist/detectors/ansible.js +108 -0
- package/dist/detectors/ansible.js.map +1 -0
- package/dist/detectors/bicep.d.ts +20 -0
- package/dist/detectors/bicep.d.ts.map +1 -0
- package/dist/detectors/bicep.js +74 -0
- package/dist/detectors/bicep.js.map +1 -0
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +30 -3
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts.map +1 -1
- package/dist/detectors/cicd.js +18 -5
- package/dist/detectors/cicd.js.map +1 -1
- package/dist/detectors/cloud-kms.d.ts +8 -5
- package/dist/detectors/cloud-kms.d.ts.map +1 -1
- package/dist/detectors/cloud-kms.js +62 -20
- package/dist/detectors/cloud-kms.js.map +1 -1
- package/dist/detectors/cloudformation.d.ts +8 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -1
- package/dist/detectors/cloudformation.js +41 -10
- package/dist/detectors/cloudformation.js.map +1 -1
- package/dist/detectors/codesign.d.ts +50 -0
- package/dist/detectors/codesign.d.ts.map +1 -0
- package/dist/detectors/codesign.js +143 -0
- package/dist/detectors/codesign.js.map +1 -0
- package/dist/detectors/csharp.d.ts.map +1 -1
- package/dist/detectors/csharp.js +13 -3
- package/dist/detectors/csharp.js.map +1 -1
- package/dist/detectors/dart.d.ts +35 -0
- package/dist/detectors/dart.d.ts.map +1 -0
- package/dist/detectors/dart.js +177 -0
- package/dist/detectors/dart.js.map +1 -0
- package/dist/detectors/database.d.ts +5 -3
- package/dist/detectors/database.d.ts.map +1 -1
- package/dist/detectors/database.js +45 -8
- package/dist/detectors/database.js.map +1 -1
- package/dist/detectors/dkim.d.ts +54 -0
- package/dist/detectors/dkim.d.ts.map +1 -0
- package/dist/detectors/dkim.js +92 -0
- package/dist/detectors/dkim.js.map +1 -0
- package/dist/detectors/dnssec.d.ts.map +1 -1
- package/dist/detectors/dnssec.js +19 -10
- package/dist/detectors/dnssec.js.map +1 -1
- package/dist/detectors/elixir.d.ts.map +1 -1
- package/dist/detectors/elixir.js +34 -2
- package/dist/detectors/elixir.js.map +1 -1
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +33 -3
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts.map +1 -1
- package/dist/detectors/jose.js +13 -3
- package/dist/detectors/jose.js.map +1 -1
- package/dist/detectors/jwk.d.ts +7 -7
- package/dist/detectors/jwk.d.ts.map +1 -1
- package/dist/detectors/jwk.js +44 -8
- package/dist/detectors/jwk.js.map +1 -1
- package/dist/detectors/k8s.d.ts.map +1 -1
- package/dist/detectors/k8s.js +5 -2
- package/dist/detectors/k8s.js.map +1 -1
- package/dist/detectors/keystore.d.ts +19 -0
- package/dist/detectors/keystore.d.ts.map +1 -0
- package/dist/detectors/keystore.js +76 -0
- package/dist/detectors/keystore.js.map +1 -0
- package/dist/detectors/mesh.d.ts +7 -8
- package/dist/detectors/mesh.d.ts.map +1 -1
- package/dist/detectors/mesh.js +17 -35
- package/dist/detectors/mesh.js.map +1 -1
- package/dist/detectors/messaging.d.ts +2 -2
- package/dist/detectors/messaging.d.ts.map +1 -1
- package/dist/detectors/messaging.js +19 -12
- package/dist/detectors/messaging.js.map +1 -1
- package/dist/detectors/objc.d.ts +41 -0
- package/dist/detectors/objc.d.ts.map +1 -0
- package/dist/detectors/objc.js +146 -0
- package/dist/detectors/objc.js.map +1 -0
- package/dist/detectors/openpgp.d.ts +20 -0
- package/dist/detectors/openpgp.d.ts.map +1 -0
- package/dist/detectors/openpgp.js +151 -0
- package/dist/detectors/openpgp.js.map +1 -0
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +32 -0
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +2 -1
- package/dist/detectors/php.d.ts.map +1 -1
- package/dist/detectors/php.js +7 -3
- package/dist/detectors/php.js.map +1 -1
- package/dist/detectors/pkcs11.d.ts +17 -0
- package/dist/detectors/pkcs11.d.ts.map +1 -0
- package/dist/detectors/pkcs11.js +87 -0
- package/dist/detectors/pkcs11.js.map +1 -0
- package/dist/detectors/proxy.d.ts +52 -0
- package/dist/detectors/proxy.d.ts.map +1 -0
- package/dist/detectors/proxy.js +157 -0
- package/dist/detectors/proxy.js.map +1 -0
- package/dist/detectors/pulumi.d.ts +19 -0
- package/dist/detectors/pulumi.d.ts.map +1 -0
- package/dist/detectors/pulumi.js +80 -0
- package/dist/detectors/pulumi.js.map +1 -0
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +5 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +4 -1
- package/dist/detectors/secrets.d.ts.map +1 -1
- package/dist/detectors/secrets.js +5 -21
- package/dist/detectors/secrets.js.map +1 -1
- package/dist/detectors/solidity.d.ts +50 -0
- package/dist/detectors/solidity.d.ts.map +1 -0
- package/dist/detectors/solidity.js +108 -0
- package/dist/detectors/solidity.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +105 -6
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/spire.d.ts +49 -0
- package/dist/detectors/spire.d.ts.map +1 -0
- package/dist/detectors/spire.js +77 -0
- package/dist/detectors/spire.js.map +1 -0
- package/dist/detectors/ssh-ca.d.ts +72 -0
- package/dist/detectors/ssh-ca.d.ts.map +1 -0
- package/dist/detectors/ssh-ca.js +119 -0
- package/dist/detectors/ssh-ca.js.map +1 -0
- package/dist/detectors/supply-chain.d.ts +17 -0
- package/dist/detectors/supply-chain.d.ts.map +1 -0
- package/dist/detectors/supply-chain.js +89 -0
- package/dist/detectors/supply-chain.js.map +1 -0
- package/dist/detectors/swift.d.ts +22 -0
- package/dist/detectors/swift.d.ts.map +1 -0
- package/dist/detectors/swift.js +120 -0
- package/dist/detectors/swift.js.map +1 -0
- package/dist/detectors/terraform.d.ts.map +1 -1
- package/dist/detectors/terraform.js +35 -8
- package/dist/detectors/terraform.js.map +1 -1
- package/dist/detectors/vault.d.ts +15 -0
- package/dist/detectors/vault.d.ts.map +1 -0
- package/dist/detectors/vault.js +97 -0
- package/dist/detectors/vault.js.map +1 -0
- package/dist/detectors/vpn.d.ts +8 -8
- package/dist/detectors/vpn.d.ts.map +1 -1
- package/dist/detectors/vpn.js +12 -38
- package/dist/detectors/vpn.js.map +1 -1
- package/dist/detectors/weak-hash.d.ts +71 -0
- package/dist/detectors/weak-hash.d.ts.map +1 -0
- package/dist/detectors/weak-hash.js +116 -0
- package/dist/detectors/weak-hash.js.map +1 -0
- package/dist/detectors/webauthn.d.ts +61 -0
- package/dist/detectors/webauthn.d.ts.map +1 -0
- package/dist/detectors/webauthn.js +109 -0
- package/dist/detectors/webauthn.js.map +1 -0
- package/dist/detectors/xmldsig.d.ts +23 -0
- package/dist/detectors/xmldsig.d.ts.map +1 -0
- package/dist/detectors/xmldsig.js +84 -0
- package/dist/detectors/xmldsig.js.map +1 -0
- package/dist/evidence.d.ts +74 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +71 -0
- package/dist/evidence.js.map +1 -1
- package/dist/hndl.d.ts +241 -0
- package/dist/hndl.d.ts.map +1 -0
- package/dist/hndl.js +752 -0
- package/dist/hndl.js.map +1 -0
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -11
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +0 -2
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +15 -6
- package/dist/inventory.js.map +1 -1
- package/dist/parallel.d.ts.map +1 -1
- package/dist/parallel.js +22 -23
- package/dist/parallel.js.map +1 -1
- package/dist/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +51 -4
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +0 -3
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +1 -1
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/remediation.d.ts +10 -0
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +32 -0
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +17 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +106 -20
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +5 -3
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +35 -6
- package/dist/scan.js.map +1 -1
- package/dist/standards-profiles.d.ts +58 -0
- package/dist/standards-profiles.d.ts.map +1 -0
- package/dist/standards-profiles.js +100 -0
- package/dist/standards-profiles.js.map +1 -0
- package/dist/types.d.ts +7 -6
- 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/dist/vex.d.ts +39 -0
- package/dist/vex.d.ts.map +1 -0
- package/dist/vex.js +87 -0
- package/dist/vex.js.map +1 -0
- package/dist/walk.d.ts +39 -4
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +62 -10
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +38 -8
- package/src/cbom.ts +153 -39
- package/src/comments.ts +25 -6
- package/src/crypto-agility.ts +407 -0
- package/src/dependencies.ts +182 -16
- package/src/detect-utils.ts +133 -11
- package/src/detectors/age.ts +60 -0
- package/src/detectors/ansible.ts +140 -0
- package/src/detectors/bicep.ts +107 -0
- package/src/detectors/c.ts +31 -3
- package/src/detectors/cicd.ts +18 -5
- package/src/detectors/cloud-kms.ts +79 -25
- package/src/detectors/cloudformation.ts +45 -10
- package/src/detectors/codesign.ts +214 -0
- package/src/detectors/csharp.ts +13 -3
- package/src/detectors/dart.ts +237 -0
- package/src/detectors/database.ts +64 -11
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +19 -10
- package/src/detectors/elixir.ts +33 -2
- package/src/detectors/java.ts +37 -3
- package/src/detectors/jose.ts +19 -3
- package/src/detectors/jwk.ts +59 -17
- package/src/detectors/k8s.ts +5 -2
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +21 -45
- package/src/detectors/messaging.ts +22 -14
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +30 -0
- package/src/detectors/php.ts +9 -4
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/rust.ts +5 -0
- package/src/detectors/secrets.ts +15 -26
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +110 -5
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +42 -8
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +20 -48
- package/src/detectors/weak-hash.ts +217 -0
- package/src/detectors/webauthn.ts +205 -0
- package/src/detectors/xmldsig.ts +115 -0
- package/src/evidence.ts +127 -4
- package/src/hndl.ts +1012 -0
- package/src/index.ts +93 -13
- package/src/inventory.ts +15 -7
- package/src/parallel.ts +21 -20
- package/src/registry.ts +51 -4
- package/src/remediate-pipeline.ts +1 -1
- package/src/remediation.ts +39 -0
- package/src/report.ts +120 -20
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/types.ts +18 -5
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +80 -12
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension, maskBlockComments, maskCommentLines, DART_EXTENSIONS, } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
// RSA key generation / raw RSA encryption engine (pointycastle). Both are
|
|
4
|
+
// confidentiality-capable (a keypair can decrypt; the engine encrypts) → kem.
|
|
5
|
+
const RE_DART_RSA_KEYGEN = /\b(?:RSAKeyGenerator|RSAEngine)\b/g;
|
|
6
|
+
// RSA signature schemes: pointycastle `RSASigner`/`PSSSigner`, cryptography
|
|
7
|
+
// `RsaPss`/`RsaSsaPkcs1v15`. Distinct casing keeps these off the keygen rule.
|
|
8
|
+
const RE_DART_RSA_SIGN = /\b(?:RSASigner|PSSSigner|RsaPss|RsaSsaPkcs1v15)\b/g;
|
|
9
|
+
// ECDSA SIGNING: pointycastle `ECDSASigner`, cryptography `Ecdsa`. `\bEcdsa\b`
|
|
10
|
+
// requires a boundary, so `EcdsaP256`-style concatenations (rare) and the all-caps
|
|
11
|
+
// `ECDSASigner` don't double-fire here. (EC keygen is a SEPARATE, HNDL-exposed rule.)
|
|
12
|
+
const RE_DART_ECDSA = /\b(?:ECDSASigner|Ecdsa)\b/g;
|
|
13
|
+
// EC KEY GENERATION (pointycastle `ECKeyGenerator`) is ambiguous — an EC key can
|
|
14
|
+
// feed ECDSA signing OR ECDH agreement — so it is classified key-exchange/ECDH/
|
|
15
|
+
// hndl:true (the fleet's HNDL-safe convention), NOT folded into the signing rule.
|
|
16
|
+
const RE_DART_EC_KEYGEN = /\bECKeyGenerator\b/g;
|
|
17
|
+
// ECDH key agreement: pointycastle `ECDHBasicAgreement`, cryptography `Ecdh`.
|
|
18
|
+
// `\bEcdh\b` cannot match inside `Ecdsa` (different letters), so no overlap.
|
|
19
|
+
const RE_DART_ECDH = /\b(?:ECDHBasicAgreement|Ecdh)\b/g;
|
|
20
|
+
// Ed25519 signer (both packages spell it `Ed25519`).
|
|
21
|
+
const RE_DART_ED25519 = /\bEd25519\b/g;
|
|
22
|
+
// X25519 key agreement (cryptography `X25519`).
|
|
23
|
+
const RE_DART_X25519 = /\bX25519\b/g;
|
|
24
|
+
// Cheap fast-reject: distinctive substrings that gate the (precise) regex work.
|
|
25
|
+
// If none is present the file cannot contain any of the tracked identifiers.
|
|
26
|
+
const DART_FAST_REJECT = [
|
|
27
|
+
"RSA",
|
|
28
|
+
"Rsa",
|
|
29
|
+
"ECDSA",
|
|
30
|
+
"Ecdsa",
|
|
31
|
+
"ECDH",
|
|
32
|
+
"Ecdh",
|
|
33
|
+
"ECKeyGenerator",
|
|
34
|
+
"PSSSigner",
|
|
35
|
+
"Ed25519",
|
|
36
|
+
"X25519",
|
|
37
|
+
];
|
|
38
|
+
const RULE_DART_RSA_KEYGEN = {
|
|
39
|
+
id: "dart-rsa-keygen",
|
|
40
|
+
title: "Dart RSA key generation / encryption",
|
|
41
|
+
description: "pointycastle RSAKeyGenerator / RSAEngine",
|
|
42
|
+
category: "kem",
|
|
43
|
+
severity: "high",
|
|
44
|
+
confidence: "high",
|
|
45
|
+
algorithm: "RSA",
|
|
46
|
+
hndl: true,
|
|
47
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
48
|
+
message: "Classical RSA key generation / encryption (Dart, pointycastle) is not quantum-safe and RSA encryption is harvest-now-decrypt-later exposed.",
|
|
49
|
+
remediation: "Migrate to PQC (ML-KEM-768 for encryption / key transport) as Dart crypto packages add support.",
|
|
50
|
+
};
|
|
51
|
+
const RULE_DART_RSA_SIGN = {
|
|
52
|
+
id: "dart-rsa-sign",
|
|
53
|
+
title: "Dart RSA signature",
|
|
54
|
+
description: "pointycastle RSASigner/PSSSigner, cryptography RsaPss/RsaSsaPkcs1v15",
|
|
55
|
+
category: "signature",
|
|
56
|
+
// `high`, consistent with every sibling pack's signature severity (go/java/php/
|
|
57
|
+
// objc/elixir RSA-sign) — the same primitive must not flip CI exit codes by language.
|
|
58
|
+
severity: "high",
|
|
59
|
+
confidence: "high",
|
|
60
|
+
algorithm: "RSA",
|
|
61
|
+
hndl: false,
|
|
62
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
63
|
+
message: "Classical RSA signature (Dart) is forgeable by a quantum attacker.",
|
|
64
|
+
remediation: "Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.",
|
|
65
|
+
};
|
|
66
|
+
const RULE_DART_ECDSA = {
|
|
67
|
+
id: "dart-ecdsa",
|
|
68
|
+
title: "Dart ECDSA signature",
|
|
69
|
+
description: "pointycastle ECDSASigner, cryptography Ecdsa",
|
|
70
|
+
category: "signature",
|
|
71
|
+
severity: "high",
|
|
72
|
+
confidence: "high",
|
|
73
|
+
algorithm: "ECDSA",
|
|
74
|
+
hndl: false,
|
|
75
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
76
|
+
message: "Classical ECDSA signing (Dart) is forgeable by a quantum attacker.",
|
|
77
|
+
remediation: "Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.",
|
|
78
|
+
};
|
|
79
|
+
const RULE_DART_EC_KEYGEN = {
|
|
80
|
+
id: "dart-ec-keygen",
|
|
81
|
+
title: "Dart EC key generation",
|
|
82
|
+
description: "pointycastle ECKeyGenerator (ambiguous EC key: ECDSA or ECDH)",
|
|
83
|
+
// Ambiguous EC keygen → key-exchange/ECDH/hndl:true (fleet HNDL-safe convention),
|
|
84
|
+
// since the key may feed ECDH agreement (harvest-now-decrypt-later exposed).
|
|
85
|
+
category: "key-exchange",
|
|
86
|
+
severity: "high",
|
|
87
|
+
confidence: "high",
|
|
88
|
+
algorithm: "ECDH",
|
|
89
|
+
hndl: true,
|
|
90
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
91
|
+
message: "EC key generation (Dart, pointycastle); an EC key can feed ECDH key agreement (harvest-now-decrypt-later exposed) as well as ECDSA signing, and is not quantum-safe.",
|
|
92
|
+
remediation: "For key agreement migrate to hybrid X25519MLKEM768 (ML-KEM-768); for signatures ML-DSA-65 — as Dart crypto packages add support.",
|
|
93
|
+
};
|
|
94
|
+
const RULE_DART_ECDH = {
|
|
95
|
+
id: "dart-ecdh",
|
|
96
|
+
title: "Dart ECDH key agreement",
|
|
97
|
+
description: "pointycastle ECDHBasicAgreement, cryptography Ecdh",
|
|
98
|
+
category: "key-exchange",
|
|
99
|
+
severity: "high",
|
|
100
|
+
confidence: "high",
|
|
101
|
+
algorithm: "ECDH",
|
|
102
|
+
hndl: true,
|
|
103
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
104
|
+
message: "Elliptic-curve Diffie-Hellman key agreement (Dart) is broken by Shor's algorithm (harvest-now-decrypt-later).",
|
|
105
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768) as Dart crypto packages add support.",
|
|
106
|
+
};
|
|
107
|
+
const RULE_DART_ED25519 = {
|
|
108
|
+
id: "dart-ed25519",
|
|
109
|
+
title: "Dart Ed25519 signature",
|
|
110
|
+
description: "pointycastle / cryptography Ed25519 signer",
|
|
111
|
+
category: "signature",
|
|
112
|
+
// `low`, aligned with Ed25519 across the other source packs (swift/rust/go/…) —
|
|
113
|
+
// the same primitive must not flip CI exit codes based on which language wrote it.
|
|
114
|
+
severity: "low",
|
|
115
|
+
confidence: "high",
|
|
116
|
+
algorithm: "EdDSA",
|
|
117
|
+
hndl: false,
|
|
118
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
119
|
+
message: "Ed25519 (Dart) is a modern but still classical signature scheme, forgeable by a quantum attacker.",
|
|
120
|
+
remediation: "Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.",
|
|
121
|
+
};
|
|
122
|
+
const RULE_DART_X25519 = {
|
|
123
|
+
id: "dart-x25519",
|
|
124
|
+
title: "Dart X25519 key agreement",
|
|
125
|
+
description: "cryptography X25519 key agreement",
|
|
126
|
+
category: "key-exchange",
|
|
127
|
+
severity: "medium",
|
|
128
|
+
confidence: "high",
|
|
129
|
+
algorithm: "X25519",
|
|
130
|
+
hndl: true,
|
|
131
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
132
|
+
message: "X25519 (Dart) is modern but still classical key agreement — harvest-now-decrypt-later exposed.",
|
|
133
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768) as Dart crypto packages add support.",
|
|
134
|
+
};
|
|
135
|
+
/** Detects classical asymmetric crypto in Dart / Flutter (pointycastle, cryptography). */
|
|
136
|
+
export const dartDetector = {
|
|
137
|
+
id: "dart-crypto",
|
|
138
|
+
description: "Classical asymmetric crypto in Dart / Flutter (pointycastle, cryptography)",
|
|
139
|
+
scope: "source",
|
|
140
|
+
language: "dart",
|
|
141
|
+
rules: [
|
|
142
|
+
RULE_DART_RSA_KEYGEN,
|
|
143
|
+
RULE_DART_RSA_SIGN,
|
|
144
|
+
RULE_DART_ECDSA,
|
|
145
|
+
RULE_DART_EC_KEYGEN,
|
|
146
|
+
RULE_DART_ECDH,
|
|
147
|
+
RULE_DART_ED25519,
|
|
148
|
+
RULE_DART_X25519,
|
|
149
|
+
],
|
|
150
|
+
appliesTo: (f) => hasExtension(f, DART_EXTENSIONS),
|
|
151
|
+
detect({ file, content }) {
|
|
152
|
+
// Fast-reject: nothing to do unless a distinctive token appears.
|
|
153
|
+
if (!DART_FAST_REJECT.some((t) => content.includes(t)))
|
|
154
|
+
return [];
|
|
155
|
+
// Suppress C-style comments so commented-out crypto isn't reported. Offsets
|
|
156
|
+
// are preserved (chars → spaces), so finding line/column stay exact.
|
|
157
|
+
const masked = maskCommentLines(maskBlockComments(content), ["//"]);
|
|
158
|
+
const findings = [];
|
|
159
|
+
// Scan the masked text, but build the finding (its snippet) from the ORIGINAL
|
|
160
|
+
// `content` so a line with a trailing comment renders live, not blanked.
|
|
161
|
+
const add = (re, rule) => eachMatch(re, masked, (m) => findings.push(findingFromRule(rule, {
|
|
162
|
+
file,
|
|
163
|
+
content,
|
|
164
|
+
index: m.index,
|
|
165
|
+
matchLength: m[0].length,
|
|
166
|
+
})));
|
|
167
|
+
add(RE_DART_RSA_KEYGEN, RULE_DART_RSA_KEYGEN);
|
|
168
|
+
add(RE_DART_RSA_SIGN, RULE_DART_RSA_SIGN);
|
|
169
|
+
add(RE_DART_ECDSA, RULE_DART_ECDSA);
|
|
170
|
+
add(RE_DART_EC_KEYGEN, RULE_DART_EC_KEYGEN);
|
|
171
|
+
add(RE_DART_ECDH, RULE_DART_ECDH);
|
|
172
|
+
add(RE_DART_ED25519, RULE_DART_ED25519);
|
|
173
|
+
add(RE_DART_X25519, RULE_DART_X25519);
|
|
174
|
+
return findings;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=dart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dart.js","sourceRoot":"","sources":["../../src/detectors/dart.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Source detector for classical asymmetric cryptography in Dart / Flutter.\n *\n * Dart has no asymmetric crypto in its standard library, so real-world code\n * reaches for one of the two dominant pub.dev packages, and both expose their\n * primitives through distinctive class names — which makes a lexical scan (the\n * same zero-dependency strategy as every other language pack here) precise:\n *\n * - package:pointycastle (a Bouncy Castle port). RSA via `RSAKeyGenerator`,\n * `RSAEngine`, `RSASigner`, `PSSSigner`; EC via `ECKeyGenerator`,\n * `ECDSASigner`, `ECDHBasicAgreement`; Edwards via `Ed25519`.\n * - package:cryptography (Dart-native). RSA via `RsaPss`, `RsaSsaPkcs1v15`;\n * EC via `Ecdsa`, `Ecdh`; modern curves via `Ed25519`, `X25519`.\n *\n * HNDL (harvest-now-decrypt-later) reasoning — the field that drives urgency:\n * - RSA encryption (`RSAEngine`) and RSA key generation (`RSAKeyGenerator`,\n * which yields keys that can decrypt) are confidentiality surfaces → hndl:true.\n * - (EC)DH key agreement (`ECDHBasicAgreement`, `Ecdh`, `X25519`) is the classic\n * HNDL target — recorded ciphertext is decryptable once the curve falls → hndl:true.\n * - Signatures (`RSASigner`/`PSSSigner`/`RsaPss`/`RsaSsaPkcs1v15`, `ECDSASigner`/\n * `Ecdsa`, `Ed25519`) are forgeable by a quantum attacker but not retroactively\n * breakable → hndl:false.\n * - `ECKeyGenerator` is ambiguous (an EC key can feed either ECDSA or ECDH); it is\n * classified conservatively as an ECDSA signing key (hndl:false), the Dart default.\n *\n * Comment suppression is C-style (`//` line + `/* … *\\/` block), matching Dart's\n * lexer, so commented-out crypto is not reported. The regexes are `\\b`-anchored on\n * the library-specific identifiers above; none matches a bare `RSA`/`EC` substring,\n * keeping false positives low. A cheap substring fast-reject skips the regex work on\n * the overwhelming majority of `.dart` files that touch no asymmetric crypto.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n eachMatch,\n findingFromRule,\n hasExtension,\n maskBlockComments,\n maskCommentLines,\n DART_EXTENSIONS,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// RSA key generation / raw RSA encryption engine (pointycastle). Both are\n// confidentiality-capable (a keypair can decrypt; the engine encrypts) → kem.\nconst RE_DART_RSA_KEYGEN = /\\b(?:RSAKeyGenerator|RSAEngine)\\b/g;\n// RSA signature schemes: pointycastle `RSASigner`/`PSSSigner`, cryptography\n// `RsaPss`/`RsaSsaPkcs1v15`. Distinct casing keeps these off the keygen rule.\nconst RE_DART_RSA_SIGN = /\\b(?:RSASigner|PSSSigner|RsaPss|RsaSsaPkcs1v15)\\b/g;\n// ECDSA SIGNING: pointycastle `ECDSASigner`, cryptography `Ecdsa`. `\\bEcdsa\\b`\n// requires a boundary, so `EcdsaP256`-style concatenations (rare) and the all-caps\n// `ECDSASigner` don't double-fire here. (EC keygen is a SEPARATE, HNDL-exposed rule.)\nconst RE_DART_ECDSA = /\\b(?:ECDSASigner|Ecdsa)\\b/g;\n// EC KEY GENERATION (pointycastle `ECKeyGenerator`) is ambiguous — an EC key can\n// feed ECDSA signing OR ECDH agreement — so it is classified key-exchange/ECDH/\n// hndl:true (the fleet's HNDL-safe convention), NOT folded into the signing rule.\nconst RE_DART_EC_KEYGEN = /\\bECKeyGenerator\\b/g;\n// ECDH key agreement: pointycastle `ECDHBasicAgreement`, cryptography `Ecdh`.\n// `\\bEcdh\\b` cannot match inside `Ecdsa` (different letters), so no overlap.\nconst RE_DART_ECDH = /\\b(?:ECDHBasicAgreement|Ecdh)\\b/g;\n// Ed25519 signer (both packages spell it `Ed25519`).\nconst RE_DART_ED25519 = /\\bEd25519\\b/g;\n// X25519 key agreement (cryptography `X25519`).\nconst RE_DART_X25519 = /\\bX25519\\b/g;\n\n// Cheap fast-reject: distinctive substrings that gate the (precise) regex work.\n// If none is present the file cannot contain any of the tracked identifiers.\nconst DART_FAST_REJECT: readonly string[] = [\n \"RSA\",\n \"Rsa\",\n \"ECDSA\",\n \"Ecdsa\",\n \"ECDH\",\n \"Ecdh\",\n \"ECKeyGenerator\",\n \"PSSSigner\",\n \"Ed25519\",\n \"X25519\",\n];\n\nconst RULE_DART_RSA_KEYGEN: RuleMeta = {\n id: \"dart-rsa-keygen\",\n title: \"Dart RSA key generation / encryption\",\n description: \"pointycastle RSAKeyGenerator / RSAEngine\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Classical RSA key generation / encryption (Dart, pointycastle) is not quantum-safe and RSA encryption is harvest-now-decrypt-later exposed.\",\n remediation:\n \"Migrate to PQC (ML-KEM-768 for encryption / key transport) as Dart crypto packages add support.\",\n};\nconst RULE_DART_RSA_SIGN: RuleMeta = {\n id: \"dart-rsa-sign\",\n title: \"Dart RSA signature\",\n description: \"pointycastle RSASigner/PSSSigner, cryptography RsaPss/RsaSsaPkcs1v15\",\n category: \"signature\",\n // `high`, consistent with every sibling pack's signature severity (go/java/php/\n // objc/elixir RSA-sign) — the same primitive must not flip CI exit codes by language.\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical RSA signature (Dart) is forgeable by a quantum attacker.\",\n remediation:\n \"Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.\",\n};\nconst RULE_DART_ECDSA: RuleMeta = {\n id: \"dart-ecdsa\",\n title: \"Dart ECDSA signature\",\n description: \"pointycastle ECDSASigner, cryptography Ecdsa\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical ECDSA signing (Dart) is forgeable by a quantum attacker.\",\n remediation:\n \"Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.\",\n};\nconst RULE_DART_EC_KEYGEN: RuleMeta = {\n id: \"dart-ec-keygen\",\n title: \"Dart EC key generation\",\n description: \"pointycastle ECKeyGenerator (ambiguous EC key: ECDSA or ECDH)\",\n // Ambiguous EC keygen → key-exchange/ECDH/hndl:true (fleet HNDL-safe convention),\n // since the key may feed ECDH agreement (harvest-now-decrypt-later exposed).\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"EC key generation (Dart, pointycastle); an EC key can feed ECDH key agreement (harvest-now-decrypt-later exposed) as well as ECDSA signing, and is not quantum-safe.\",\n remediation:\n \"For key agreement migrate to hybrid X25519MLKEM768 (ML-KEM-768); for signatures ML-DSA-65 — as Dart crypto packages add support.\",\n};\nconst RULE_DART_ECDH: RuleMeta = {\n id: \"dart-ecdh\",\n title: \"Dart ECDH key agreement\",\n description: \"pointycastle ECDHBasicAgreement, cryptography Ecdh\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Elliptic-curve Diffie-Hellman key agreement (Dart) is broken by Shor's algorithm (harvest-now-decrypt-later).\",\n remediation:\n \"Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768) as Dart crypto packages add support.\",\n};\nconst RULE_DART_ED25519: RuleMeta = {\n id: \"dart-ed25519\",\n title: \"Dart Ed25519 signature\",\n description: \"pointycastle / cryptography Ed25519 signer\",\n category: \"signature\",\n // `low`, aligned with Ed25519 across the other source packs (swift/rust/go/…) —\n // the same primitive must not flip CI exit codes based on which language wrote it.\n severity: \"low\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Ed25519 (Dart) is a modern but still classical signature scheme, forgeable by a quantum attacker.\",\n remediation:\n \"Migrate to PQC signatures (ML-DSA-65, FIPS 204) as Dart crypto packages add support.\",\n};\nconst RULE_DART_X25519: RuleMeta = {\n id: \"dart-x25519\",\n title: \"Dart X25519 key agreement\",\n description: \"cryptography X25519 key agreement\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"X25519 (Dart) is modern but still classical key agreement — harvest-now-decrypt-later exposed.\",\n remediation:\n \"Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768) as Dart crypto packages add support.\",\n};\n\n/** Detects classical asymmetric crypto in Dart / Flutter (pointycastle, cryptography). */\nexport const dartDetector: Detector = {\n id: \"dart-crypto\",\n description: \"Classical asymmetric crypto in Dart / Flutter (pointycastle, cryptography)\",\n scope: \"source\",\n language: \"dart\",\n rules: [\n RULE_DART_RSA_KEYGEN,\n RULE_DART_RSA_SIGN,\n RULE_DART_ECDSA,\n RULE_DART_EC_KEYGEN,\n RULE_DART_ECDH,\n RULE_DART_ED25519,\n RULE_DART_X25519,\n ],\n appliesTo: (f) => hasExtension(f, DART_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Fast-reject: nothing to do unless a distinctive token appears.\n if (!DART_FAST_REJECT.some((t) => content.includes(t))) return [];\n\n // Suppress C-style comments so commented-out crypto isn't reported. Offsets\n // are preserved (chars → spaces), so finding line/column stay exact.\n const masked = maskCommentLines(maskBlockComments(content), [\"//\"]);\n\n const findings: Finding[] = [];\n // Scan the masked text, but build the finding (its snippet) from the ORIGINAL\n // `content` so a line with a trailing comment renders live, not blanked.\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, masked, (m) =>\n findings.push(\n findingFromRule(rule, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n add(RE_DART_RSA_KEYGEN, RULE_DART_RSA_KEYGEN);\n add(RE_DART_RSA_SIGN, RULE_DART_RSA_SIGN);\n add(RE_DART_ECDSA, RULE_DART_ECDSA);\n add(RE_DART_EC_KEYGEN, RULE_DART_EC_KEYGEN);\n add(RE_DART_ECDH, RULE_DART_ECDH);\n add(RE_DART_ED25519, RULE_DART_ED25519);\n add(RE_DART_X25519, RULE_DART_X25519);\n return findings;\n },\n};\n"]}
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* Covered:
|
|
8
8
|
* - pgcrypto `pgp_pub_encrypt` / `pgp_pub_decrypt` in `.sql` (RSA/ElGamal public-key
|
|
9
9
|
* encryption of column data) → `hndl:true`.
|
|
10
|
-
* - libpq `sslmode =
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* - libpq `sslmode = allow | prefer | require` in any config/connection file — a
|
|
11
|
+
* mode that does NOT verify the server certificate, so the classical TLS session
|
|
12
|
+
* it negotiates is both MITM-able and harvestable. `disable` is excluded: it
|
|
13
|
+
* negotiates no TLS at all, so there is no key exchange to harvest (a plaintext
|
|
14
|
+
* concern, out of scope for a PQC-readiness scanner).
|
|
13
15
|
*/
|
|
14
16
|
import type { Detector } from "../types.js";
|
|
15
17
|
/** Detects classical database at-rest / transport crypto. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/detectors/database.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/detectors/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAoE/D,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,EAAE,QAwD9B,CAAC"}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskCommentLines, } from "../detect-utils.js";
|
|
2
2
|
import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION } from "../cwe.js";
|
|
3
3
|
const RE_PGCRYPTO = /\bpgp_pub_(?:encrypt|decrypt)\b/g;
|
|
4
|
-
|
|
4
|
+
// Matches the no-verification TLS modes across libpq (`sslmode=require`), the
|
|
5
|
+
// `PGSSLMODE` env var, MySQL (`ssl-mode=PREFERRED`/`REQUIRED`, hyphenated), and YAML
|
|
6
|
+
// connection config (`sslmode: require`) — i.e. an optional `pg` prefix, `-`/`_` or no
|
|
7
|
+
// separator, `:` or `=`, and the libpq/MySQL value spellings.
|
|
8
|
+
const RE_WEAK_SSLMODE = /\b(?:pg)?ssl[-_]?mode\s*[:=]\s*["']?(?:allow|prefer(?:red)?|require[d]?)\b/gi;
|
|
9
|
+
// SQL Server TDE: the database encryption key is protected by a classical RSA
|
|
10
|
+
// asymmetric key / certificate. `CREATE ASYMMETRIC KEY … WITH ALGORITHM = RSA_2048`
|
|
11
|
+
// is the distinctive form; the span between the two clauses is lazily bounded to keep
|
|
12
|
+
// matching linear.
|
|
13
|
+
const RE_MSSQL_TDE_RSA = /\bCREATE\s+ASYMMETRIC\s+KEY\b[\s\S]{0,300}?\bALGORITHM\s*=\s*RSA_\d+/gi;
|
|
5
14
|
const RULE_PGCRYPTO = {
|
|
6
15
|
id: "db-pgcrypto-pubkey",
|
|
7
16
|
title: "pgcrypto public-key encryption",
|
|
@@ -15,10 +24,23 @@ const RULE_PGCRYPTO = {
|
|
|
15
24
|
message: "Column data is encrypted with pgcrypto public-key crypto (classical RSA/ElGamal); stored ciphertext is harvest-now-decrypt-later exposed.",
|
|
16
25
|
remediation: "Plan migration to a post-quantum KEM (ML-KEM-768) envelope for at-rest data; re-encrypt long-lived rows.",
|
|
17
26
|
};
|
|
27
|
+
const RULE_MSSQL_TDE_RSA = {
|
|
28
|
+
id: "db-tde-rsa",
|
|
29
|
+
title: "SQL Server TDE RSA key",
|
|
30
|
+
description: "SQL Server CREATE ASYMMETRIC KEY ... WITH ALGORITHM = RSA_* (TDE key protection)",
|
|
31
|
+
category: "kem",
|
|
32
|
+
severity: "high",
|
|
33
|
+
confidence: "high",
|
|
34
|
+
algorithm: "RSA",
|
|
35
|
+
hndl: true,
|
|
36
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
37
|
+
message: "Transparent Data Encryption protects the database encryption key with a classical RSA asymmetric key; the at-rest data is harvest-now-decrypt-later exposed once the RSA key falls.",
|
|
38
|
+
remediation: "Plan migration to a PQC KEM (ML-KEM-768) for TDE key protection as the database engine adds support; re-key long-lived encrypted databases.",
|
|
39
|
+
};
|
|
18
40
|
const RULE_WEAK_SSLMODE = {
|
|
19
41
|
id: "db-weak-sslmode",
|
|
20
42
|
title: "Database sslmode without verification",
|
|
21
|
-
description: "libpq sslmode is
|
|
43
|
+
description: "libpq sslmode is allow/prefer/require (no certificate verification)",
|
|
22
44
|
category: "tls",
|
|
23
45
|
severity: "medium",
|
|
24
46
|
confidence: "high",
|
|
@@ -30,24 +52,39 @@ const RULE_WEAK_SSLMODE = {
|
|
|
30
52
|
/** Detects classical database at-rest / transport crypto. */
|
|
31
53
|
export const databaseDetector = {
|
|
32
54
|
id: "database-crypto",
|
|
33
|
-
description: "Classical crypto in database usage (pgcrypto public-key, weak client sslmode)",
|
|
55
|
+
description: "Classical crypto in database usage (pgcrypto public-key, SQL Server TDE, weak client sslmode)",
|
|
34
56
|
scope: "config",
|
|
35
57
|
language: "any",
|
|
36
|
-
rules: [RULE_PGCRYPTO, RULE_WEAK_SSLMODE],
|
|
58
|
+
rules: [RULE_PGCRYPTO, RULE_MSSQL_TDE_RSA, RULE_WEAK_SSLMODE],
|
|
37
59
|
// Skip prose/docs: a README showing `sslmode=require` is not a live connection string.
|
|
38
60
|
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
39
61
|
detect({ file, content }) {
|
|
40
62
|
const findings = [];
|
|
63
|
+
// Mask SQL (`--`, single-line `/* … */`), shell/ini (`#`), and ini (`;`) line
|
|
64
|
+
// comments: a commented `-- pgp_pub_encrypt` or `; sslmode=require` is not active.
|
|
65
|
+
const scan = maskCommentLines(content, ["#", "--", ";", "/*"]);
|
|
41
66
|
if (file.toLowerCase().endsWith(".sql") && content.includes("pgp_pub_")) {
|
|
42
|
-
eachMatch(RE_PGCRYPTO,
|
|
67
|
+
eachMatch(RE_PGCRYPTO, scan, (m) => findings.push(findingFromRule(RULE_PGCRYPTO, {
|
|
68
|
+
file,
|
|
69
|
+
content,
|
|
70
|
+
index: m.index,
|
|
71
|
+
matchLength: m[0].length,
|
|
72
|
+
})));
|
|
73
|
+
}
|
|
74
|
+
const lc = content.toLowerCase();
|
|
75
|
+
// Cheap prefilter; matches the separators the RE_WEAK_SSLMODE regex accepts
|
|
76
|
+
// (`sslmode`, `ssl-mode`, `ssl_mode`) so no supported form is gated out.
|
|
77
|
+
if (lc.includes("sslmode") || lc.includes("ssl-mode") || lc.includes("ssl_mode")) {
|
|
78
|
+
eachMatch(RE_WEAK_SSLMODE, scan, (m) => findings.push(findingFromRule(RULE_WEAK_SSLMODE, {
|
|
43
79
|
file,
|
|
44
80
|
content,
|
|
45
81
|
index: m.index,
|
|
46
82
|
matchLength: m[0].length,
|
|
47
83
|
})));
|
|
48
84
|
}
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
// SQL Server TDE: a classical RSA asymmetric key protecting the DEK.
|
|
86
|
+
if (lc.includes("asymmetric key")) {
|
|
87
|
+
eachMatch(RE_MSSQL_TDE_RSA, scan, (m) => findings.push(findingFromRule(RULE_MSSQL_TDE_RSA, {
|
|
51
88
|
file,
|
|
52
89
|
content,
|
|
53
90
|
index: m.index,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/detectors/database.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/detectors/database.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical crypto in database usage — public-key encryption of\n * stored data (pgcrypto) and weak client TLS posture. Data-at-rest is the sharpest\n * harvest-now-decrypt-later target: a column encrypted to an RSA key today is a\n * time capsule an adversary can hold until key recovery is cheap.\n *\n * Covered:\n * - pgcrypto `pgp_pub_encrypt` / `pgp_pub_decrypt` in `.sql` (RSA/ElGamal public-key\n * encryption of column data) → `hndl:true`.\n * - libpq `sslmode = allow | prefer | require` in any config/connection file — a\n * mode that does NOT verify the server certificate, so the classical TLS session\n * it negotiates is both MITM-able and harvestable. `disable` is excluded: it\n * negotiates no TLS at all, so there is no key exchange to harvest (a plaintext\n * concern, out of scope for a PQC-readiness scanner).\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION } from \"../cwe.js\";\n\nconst RE_PGCRYPTO = /\\bpgp_pub_(?:encrypt|decrypt)\\b/g;\n// Matches the no-verification TLS modes across libpq (`sslmode=require`), the\n// `PGSSLMODE` env var, MySQL (`ssl-mode=PREFERRED`/`REQUIRED`, hyphenated), and YAML\n// connection config (`sslmode: require`) — i.e. an optional `pg` prefix, `-`/`_` or no\n// separator, `:` or `=`, and the libpq/MySQL value spellings.\nconst RE_WEAK_SSLMODE =\n /\\b(?:pg)?ssl[-_]?mode\\s*[:=]\\s*[\"']?(?:allow|prefer(?:red)?|require[d]?)\\b/gi;\n// SQL Server TDE: the database encryption key is protected by a classical RSA\n// asymmetric key / certificate. `CREATE ASYMMETRIC KEY … WITH ALGORITHM = RSA_2048`\n// is the distinctive form; the span between the two clauses is lazily bounded to keep\n// matching linear.\nconst RE_MSSQL_TDE_RSA = /\\bCREATE\\s+ASYMMETRIC\\s+KEY\\b[\\s\\S]{0,300}?\\bALGORITHM\\s*=\\s*RSA_\\d+/gi;\n\nconst RULE_PGCRYPTO: RuleMeta = {\n id: \"db-pgcrypto-pubkey\",\n title: \"pgcrypto public-key encryption\",\n description: \"Postgres pgcrypto pgp_pub_encrypt / pgp_pub_decrypt (RSA/ElGamal) on stored data\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Column data is encrypted with pgcrypto public-key crypto (classical RSA/ElGamal); stored ciphertext is harvest-now-decrypt-later exposed.\",\n remediation:\n \"Plan migration to a post-quantum KEM (ML-KEM-768) envelope for at-rest data; re-encrypt long-lived rows.\",\n};\nconst RULE_MSSQL_TDE_RSA: RuleMeta = {\n id: \"db-tde-rsa\",\n title: \"SQL Server TDE RSA key\",\n description: \"SQL Server CREATE ASYMMETRIC KEY ... WITH ALGORITHM = RSA_* (TDE key protection)\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Transparent Data Encryption protects the database encryption key with a classical RSA asymmetric key; the at-rest data is harvest-now-decrypt-later exposed once the RSA key falls.\",\n remediation:\n \"Plan migration to a PQC KEM (ML-KEM-768) for TDE key protection as the database engine adds support; re-key long-lived encrypted databases.\",\n};\nconst RULE_WEAK_SSLMODE: RuleMeta = {\n id: \"db-weak-sslmode\",\n title: \"Database sslmode without verification\",\n description: \"libpq sslmode is allow/prefer/require (no certificate verification)\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_CERT_VALIDATION,\n message:\n \"Database sslmode does not verify the server certificate; the classical TLS session is MITM-able and its key exchange is harvestable.\",\n remediation:\n \"Use sslmode=verify-full and TLS 1.3; track PQC-hybrid KEX (X25519MLKEM768) for database transport.\",\n};\n\n/** Detects classical database at-rest / transport crypto. */\nexport const databaseDetector: Detector = {\n id: \"database-crypto\",\n description:\n \"Classical crypto in database usage (pgcrypto public-key, SQL Server TDE, weak client sslmode)\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_PGCRYPTO, RULE_MSSQL_TDE_RSA, RULE_WEAK_SSLMODE],\n // Skip prose/docs: a README showing `sslmode=require` is not a live connection string.\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n // Mask SQL (`--`, single-line `/* … */`), shell/ini (`#`), and ini (`;`) line\n // comments: a commented `-- pgp_pub_encrypt` or `; sslmode=require` is not active.\n const scan = maskCommentLines(content, [\"#\", \"--\", \";\", \"/*\"]);\n if (file.toLowerCase().endsWith(\".sql\") && content.includes(\"pgp_pub_\")) {\n eachMatch(RE_PGCRYPTO, scan, (m) =>\n findings.push(\n findingFromRule(RULE_PGCRYPTO, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n const lc = content.toLowerCase();\n // Cheap prefilter; matches the separators the RE_WEAK_SSLMODE regex accepts\n // (`sslmode`, `ssl-mode`, `ssl_mode`) so no supported form is gated out.\n if (lc.includes(\"sslmode\") || lc.includes(\"ssl-mode\") || lc.includes(\"ssl_mode\")) {\n eachMatch(RE_WEAK_SSLMODE, scan, (m) =>\n findings.push(\n findingFromRule(RULE_WEAK_SSLMODE, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n // SQL Server TDE: a classical RSA asymmetric key protecting the DEK.\n if (lc.includes(\"asymmetric key\")) {\n eachMatch(RE_MSSQL_TDE_RSA, scan, (m) =>\n findings.push(\n findingFromRule(RULE_MSSQL_TDE_RSA, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical DKIM email-signing keys and algorithms in DNS
|
|
3
|
+
* zone files, mail-server signer config (OpenDKIM), and captured
|
|
4
|
+
* `DKIM-Signature:` header fields (RFC 6376 + RFC 8463).
|
|
5
|
+
*
|
|
6
|
+
* DKIM (DomainKeys Identified Mail) authenticates the *origin* of email: the
|
|
7
|
+
* sending MTA signs selected header/body content with a private key, publishes
|
|
8
|
+
* the matching public key as a DNS TXT record at
|
|
9
|
+
* `<selector>._domainkey.<domain>`, and receivers verify the `DKIM-Signature`
|
|
10
|
+
* header against that published key. Like DNSSEC (see `dnssec.ts`), this is a
|
|
11
|
+
* pure *signature* surface: there is no confidentiality to harvest, so a DKIM
|
|
12
|
+
* key is NOT harvest-now-decrypt-later exposed (`hndl: false`). What breaks
|
|
13
|
+
* once a cryptographically-relevant quantum computer (CRQC) exists is
|
|
14
|
+
* unforgeability — an attacker who can derive the private key from the
|
|
15
|
+
* published public key can mint valid `DKIM-Signature` headers and spoof mail
|
|
16
|
+
* that passes DKIM/DMARC. So these findings are `category: "signature"`.
|
|
17
|
+
*
|
|
18
|
+
* Crypto surface, mapped to {@link AlgorithmFamily}:
|
|
19
|
+
* - `k=rsa` DNS key-type tag / `a=rsa-sha256` / `a=rsa-sha1` (DKIM-Signature
|
|
20
|
+
* algorithm tag) / OpenDKIM `SigningAlgorithm rsa-sha256|rsa-sha1` → RSA
|
|
21
|
+
* - `k=ed25519` DNS key-type tag / `a=ed25519-sha256` / OpenDKIM
|
|
22
|
+
* `SigningAlgorithm ed25519-sha256` (RFC 8463) → EdDSA
|
|
23
|
+
*
|
|
24
|
+
* Ed25519 DKIM (RFC 8463) is MODERN but still CLASSICAL: rotating an RSA DKIM
|
|
25
|
+
* key to Ed25519 shrinks the DNS record and is good hygiene, but it is NOT a
|
|
26
|
+
* post-quantum fix — an Ed25519 signature is just as forgeable under a CRQC.
|
|
27
|
+
* The remediation text says so honestly: there is no standardized PQC DKIM yet;
|
|
28
|
+
* the only real action is to track IETF work.
|
|
29
|
+
*
|
|
30
|
+
* Two match shapes are covered per family, both gated to a small config
|
|
31
|
+
* extension set AND to a file-level DKIM marker (see `hasDkimMarker`) so the
|
|
32
|
+
* short tag tokens (`k=rsa`, `a=rsa-sha256`) can't fire on unrelated config:
|
|
33
|
+
*
|
|
34
|
+
* 1. DNS KEY-TYPE tag — `k=rsa` / `k=ed25519` inside a published DKIM TXT
|
|
35
|
+
* record (`v=DKIM1; k=rsa; p=MIGf...`). Whitespace around `=` is tolerated.
|
|
36
|
+
* 2. SIGNING-ALGORITHM tag — the `a=` tag of a `DKIM-Signature` header
|
|
37
|
+
* (`a=rsa-sha256`, `a=ed25519-sha256`) and the OpenDKIM `SigningAlgorithm`
|
|
38
|
+
* directive (`SigningAlgorithm rsa-sha256`). The RSA variant deliberately
|
|
39
|
+
* also matches the legacy `rsa-sha1`.
|
|
40
|
+
*
|
|
41
|
+
* Fast reject: `detect()` bails unless the file carries a DKIM-specific marker
|
|
42
|
+
* (`v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, an OpenDKIM
|
|
43
|
+
* `SigningAlgorithm` directive, or a bare `k=rsa`/`k=ed25519` key-type tag).
|
|
44
|
+
* This stops the generic-looking `k=rsa` / `a=rsa-sha256` tokens from firing on
|
|
45
|
+
* an unrelated `.conf` that happens to contain an `a=`/`k=` assignment for its
|
|
46
|
+
* own reasons. Prose is excluded twice over: `appliesTo` restricts to config
|
|
47
|
+
* extensions (README/`.md` never applies), and `detect()` re-guards against
|
|
48
|
+
* {@link DOC_EXTENSIONS} defensively so a docs file that slipped through a
|
|
49
|
+
* broader gate could never be flagged for mentioning `k=rsa` in a sentence.
|
|
50
|
+
*/
|
|
51
|
+
import type { Detector } from "../types.js";
|
|
52
|
+
/** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */
|
|
53
|
+
export declare const dkimDetector: Detector;
|
|
54
|
+
//# sourceMappingURL=dkim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dkim.d.ts","sourceRoot":"","sources":["../../src/detectors/dkim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA2F/D,2FAA2F;AAC3F,eAAO,MAAM,YAAY,EAAE,QA6B1B,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskCommentLines, } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
// DKIM records live in DNS zone files (`.zone`/`.db`), mail-server signer config
|
|
4
|
+
// (`.conf`), and captured header/TXT fixtures (`.txt`). Gated further by
|
|
5
|
+
// `hasDkimMarker` so the short tag tokens can't fire on unrelated config.
|
|
6
|
+
const DKIM_EXTENSIONS = [".zone", ".db", ".conf", ".txt"];
|
|
7
|
+
// --- RSA (k=rsa / rsa-sha256 / rsa-sha1) ---
|
|
8
|
+
// DNS key-type tag: `k=rsa` in a published DKIM TXT record. Whitespace tolerated
|
|
9
|
+
// around `=`; case-insensitive since tags are conventionally lowercase but not
|
|
10
|
+
// required to be. `\b` after `rsa` keeps it from matching `rsa-...` substrings.
|
|
11
|
+
const RE_RSA_KEYTAG = /\bk\s*=\s*rsa\b/gi;
|
|
12
|
+
// DKIM-Signature `a=` tag: `a=rsa-sha256` / `a=rsa-sha1`.
|
|
13
|
+
const RE_RSA_ALGTAG = /\ba\s*=\s*rsa-sha(?:256|1)\b/gi;
|
|
14
|
+
// OpenDKIM signer directive: `SigningAlgorithm rsa-sha256` / `rsa-sha1`.
|
|
15
|
+
const RE_RSA_OPENDKIM = /\bSigningAlgorithm\s+rsa-sha(?:256|1)\b/gi;
|
|
16
|
+
// --- EdDSA (k=ed25519 / ed25519-sha256, RFC 8463) ---
|
|
17
|
+
const RE_EDDSA_KEYTAG = /\bk\s*=\s*ed25519\b/gi;
|
|
18
|
+
const RE_EDDSA_ALGTAG = /\ba\s*=\s*ed25519-sha256\b/gi;
|
|
19
|
+
const RE_EDDSA_OPENDKIM = /\bSigningAlgorithm\s+ed25519-sha256\b/gi;
|
|
20
|
+
const RULE_DKIM_RSA = {
|
|
21
|
+
id: "dkim-rsa-key",
|
|
22
|
+
title: "DKIM RSA signing key/algorithm",
|
|
23
|
+
description: "DKIM email signing configured with a classical RSA key/algorithm (k=rsa, a=rsa-sha256/rsa-sha1)",
|
|
24
|
+
category: "signature",
|
|
25
|
+
severity: "medium",
|
|
26
|
+
confidence: "high",
|
|
27
|
+
algorithm: "RSA",
|
|
28
|
+
hndl: false,
|
|
29
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
30
|
+
message: "DKIM email signing uses a classical RSA key/algorithm (k=rsa / rsa-sha256 / rsa-sha1); DKIM-Signature headers become forgeable once a CRQC exists, allowing DKIM/DMARC-passing spoofed mail.",
|
|
31
|
+
remediation: "There is no standardized post-quantum DKIM algorithm yet — track IETF work; note that rotating RSA to Ed25519 (RFC 8463) is good hygiene but NOT a PQC fix, since Ed25519 is still classical and equally forgeable under a CRQC.",
|
|
32
|
+
};
|
|
33
|
+
const RULE_DKIM_EDDSA = {
|
|
34
|
+
id: "dkim-ed25519-key",
|
|
35
|
+
title: "DKIM Ed25519 signing key/algorithm",
|
|
36
|
+
description: "DKIM email signing configured with a classical Ed25519 key/algorithm (k=ed25519, a=ed25519-sha256, RFC 8463)",
|
|
37
|
+
category: "signature",
|
|
38
|
+
severity: "medium",
|
|
39
|
+
confidence: "high",
|
|
40
|
+
algorithm: "EdDSA",
|
|
41
|
+
hndl: false,
|
|
42
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
43
|
+
message: "DKIM email signing uses an Ed25519 key/algorithm (k=ed25519 / ed25519-sha256, RFC 8463) — modern but still classical; DKIM-Signature headers become forgeable once a CRQC exists.",
|
|
44
|
+
remediation: "There is no standardized post-quantum DKIM algorithm yet — track IETF work. Ed25519 is already the smaller/modern DKIM choice but is NOT post-quantum: it stays forgeable under a CRQC, so no rotation today resolves the quantum exposure.",
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* True when `content` carries a DKIM-specific marker. Deliberately does NOT accept
|
|
48
|
+
* a bare `k=rsa` / `k=ed25519` tag as its own marker: that tag is exactly what the
|
|
49
|
+
* RSA/EdDSA rules match, so treating it as the gate would be self-satisfying and
|
|
50
|
+
* fire on unrelated prose ("set `k=rsa` for the legacy importer"). A real DKIM
|
|
51
|
+
* record always also carries `v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, or
|
|
52
|
+
* the OpenDKIM `SigningAlgorithm` directive — one of those must be present.
|
|
53
|
+
*/
|
|
54
|
+
function hasDkimMarker(content) {
|
|
55
|
+
return (/\bDKIM1\b/i.test(content) ||
|
|
56
|
+
content.includes("_domainkey") ||
|
|
57
|
+
/DKIM-Signature/i.test(content) ||
|
|
58
|
+
/\bSigningAlgorithm\b/i.test(content));
|
|
59
|
+
}
|
|
60
|
+
const DKIM_RULES = [
|
|
61
|
+
{ meta: RULE_DKIM_RSA, res: [RE_RSA_KEYTAG, RE_RSA_ALGTAG, RE_RSA_OPENDKIM] },
|
|
62
|
+
{ meta: RULE_DKIM_EDDSA, res: [RE_EDDSA_KEYTAG, RE_EDDSA_ALGTAG, RE_EDDSA_OPENDKIM] },
|
|
63
|
+
];
|
|
64
|
+
/** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */
|
|
65
|
+
export const dkimDetector = {
|
|
66
|
+
id: "dkim-crypto",
|
|
67
|
+
description: "Classical DKIM email signing keys/algorithms in zone files / mail signer config",
|
|
68
|
+
scope: "config",
|
|
69
|
+
language: "any",
|
|
70
|
+
rules: DKIM_RULES.map((r) => r.meta),
|
|
71
|
+
appliesTo: (f) => hasExtension(f, DKIM_EXTENSIONS) && !hasExtension(f, DOC_EXTENSIONS),
|
|
72
|
+
detect({ file, content }) {
|
|
73
|
+
// Defensive doc guard: even if a broader gate ever let a prose file through,
|
|
74
|
+
// a README mentioning `k=rsa` in a sentence must never be flagged.
|
|
75
|
+
if (hasExtension(file, DOC_EXTENSIONS))
|
|
76
|
+
return [];
|
|
77
|
+
if (!hasDkimMarker(content))
|
|
78
|
+
return [];
|
|
79
|
+
// Mask whole comment lines so a commented-out record can't fire: zone files
|
|
80
|
+
// use `;`, mail/OpenDKIM config uses `#` and `//`. Offsets are preserved so
|
|
81
|
+
// finding line/column/snippet stay exact for the live lines that remain.
|
|
82
|
+
const scan = maskCommentLines(content, [";", "#", "//"]);
|
|
83
|
+
const findings = [];
|
|
84
|
+
for (const { meta, res } of DKIM_RULES) {
|
|
85
|
+
for (const re of res) {
|
|
86
|
+
eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return findings;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=dkim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dkim.js","sourceRoot":"","sources":["../../src/detectors/dkim.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical DKIM email-signing keys and algorithms in DNS\n * zone files, mail-server signer config (OpenDKIM), and captured\n * `DKIM-Signature:` header fields (RFC 6376 + RFC 8463).\n *\n * DKIM (DomainKeys Identified Mail) authenticates the *origin* of email: the\n * sending MTA signs selected header/body content with a private key, publishes\n * the matching public key as a DNS TXT record at\n * `<selector>._domainkey.<domain>`, and receivers verify the `DKIM-Signature`\n * header against that published key. Like DNSSEC (see `dnssec.ts`), this is a\n * pure *signature* surface: there is no confidentiality to harvest, so a DKIM\n * key is NOT harvest-now-decrypt-later exposed (`hndl: false`). What breaks\n * once a cryptographically-relevant quantum computer (CRQC) exists is\n * unforgeability — an attacker who can derive the private key from the\n * published public key can mint valid `DKIM-Signature` headers and spoof mail\n * that passes DKIM/DMARC. So these findings are `category: \"signature\"`.\n *\n * Crypto surface, mapped to {@link AlgorithmFamily}:\n * - `k=rsa` DNS key-type tag / `a=rsa-sha256` / `a=rsa-sha1` (DKIM-Signature\n * algorithm tag) / OpenDKIM `SigningAlgorithm rsa-sha256|rsa-sha1` → RSA\n * - `k=ed25519` DNS key-type tag / `a=ed25519-sha256` / OpenDKIM\n * `SigningAlgorithm ed25519-sha256` (RFC 8463) → EdDSA\n *\n * Ed25519 DKIM (RFC 8463) is MODERN but still CLASSICAL: rotating an RSA DKIM\n * key to Ed25519 shrinks the DNS record and is good hygiene, but it is NOT a\n * post-quantum fix — an Ed25519 signature is just as forgeable under a CRQC.\n * The remediation text says so honestly: there is no standardized PQC DKIM yet;\n * the only real action is to track IETF work.\n *\n * Two match shapes are covered per family, both gated to a small config\n * extension set AND to a file-level DKIM marker (see `hasDkimMarker`) so the\n * short tag tokens (`k=rsa`, `a=rsa-sha256`) can't fire on unrelated config:\n *\n * 1. DNS KEY-TYPE tag — `k=rsa` / `k=ed25519` inside a published DKIM TXT\n * record (`v=DKIM1; k=rsa; p=MIGf...`). Whitespace around `=` is tolerated.\n * 2. SIGNING-ALGORITHM tag — the `a=` tag of a `DKIM-Signature` header\n * (`a=rsa-sha256`, `a=ed25519-sha256`) and the OpenDKIM `SigningAlgorithm`\n * directive (`SigningAlgorithm rsa-sha256`). The RSA variant deliberately\n * also matches the legacy `rsa-sha1`.\n *\n * Fast reject: `detect()` bails unless the file carries a DKIM-specific marker\n * (`v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, an OpenDKIM\n * `SigningAlgorithm` directive, or a bare `k=rsa`/`k=ed25519` key-type tag).\n * This stops the generic-looking `k=rsa` / `a=rsa-sha256` tokens from firing on\n * an unrelated `.conf` that happens to contain an `a=`/`k=` assignment for its\n * own reasons. Prose is excluded twice over: `appliesTo` restricts to config\n * extensions (README/`.md` never applies), and `detect()` re-guards against\n * {@link DOC_EXTENSIONS} defensively so a docs file that slipped through a\n * broader gate could never be flagged for mentioning `k=rsa` in a sentence.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// DKIM records live in DNS zone files (`.zone`/`.db`), mail-server signer config\n// (`.conf`), and captured header/TXT fixtures (`.txt`). Gated further by\n// `hasDkimMarker` so the short tag tokens can't fire on unrelated config.\nconst DKIM_EXTENSIONS: readonly string[] = [\".zone\", \".db\", \".conf\", \".txt\"];\n\n// --- RSA (k=rsa / rsa-sha256 / rsa-sha1) ---\n// DNS key-type tag: `k=rsa` in a published DKIM TXT record. Whitespace tolerated\n// around `=`; case-insensitive since tags are conventionally lowercase but not\n// required to be. `\\b` after `rsa` keeps it from matching `rsa-...` substrings.\nconst RE_RSA_KEYTAG = /\\bk\\s*=\\s*rsa\\b/gi;\n// DKIM-Signature `a=` tag: `a=rsa-sha256` / `a=rsa-sha1`.\nconst RE_RSA_ALGTAG = /\\ba\\s*=\\s*rsa-sha(?:256|1)\\b/gi;\n// OpenDKIM signer directive: `SigningAlgorithm rsa-sha256` / `rsa-sha1`.\nconst RE_RSA_OPENDKIM = /\\bSigningAlgorithm\\s+rsa-sha(?:256|1)\\b/gi;\n\n// --- EdDSA (k=ed25519 / ed25519-sha256, RFC 8463) ---\nconst RE_EDDSA_KEYTAG = /\\bk\\s*=\\s*ed25519\\b/gi;\nconst RE_EDDSA_ALGTAG = /\\ba\\s*=\\s*ed25519-sha256\\b/gi;\nconst RE_EDDSA_OPENDKIM = /\\bSigningAlgorithm\\s+ed25519-sha256\\b/gi;\n\nconst RULE_DKIM_RSA: RuleMeta = {\n id: \"dkim-rsa-key\",\n title: \"DKIM RSA signing key/algorithm\",\n description:\n \"DKIM email signing configured with a classical RSA key/algorithm (k=rsa, a=rsa-sha256/rsa-sha1)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DKIM email signing uses a classical RSA key/algorithm (k=rsa / rsa-sha256 / rsa-sha1); DKIM-Signature headers become forgeable once a CRQC exists, allowing DKIM/DMARC-passing spoofed mail.\",\n remediation:\n \"There is no standardized post-quantum DKIM algorithm yet — track IETF work; note that rotating RSA to Ed25519 (RFC 8463) is good hygiene but NOT a PQC fix, since Ed25519 is still classical and equally forgeable under a CRQC.\",\n};\n\nconst RULE_DKIM_EDDSA: RuleMeta = {\n id: \"dkim-ed25519-key\",\n title: \"DKIM Ed25519 signing key/algorithm\",\n description:\n \"DKIM email signing configured with a classical Ed25519 key/algorithm (k=ed25519, a=ed25519-sha256, RFC 8463)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DKIM email signing uses an Ed25519 key/algorithm (k=ed25519 / ed25519-sha256, RFC 8463) — modern but still classical; DKIM-Signature headers become forgeable once a CRQC exists.\",\n remediation:\n \"There is no standardized post-quantum DKIM algorithm yet — track IETF work. Ed25519 is already the smaller/modern DKIM choice but is NOT post-quantum: it stays forgeable under a CRQC, so no rotation today resolves the quantum exposure.\",\n};\n\n/**\n * True when `content` carries a DKIM-specific marker. Deliberately does NOT accept\n * a bare `k=rsa` / `k=ed25519` tag as its own marker: that tag is exactly what the\n * RSA/EdDSA rules match, so treating it as the gate would be self-satisfying and\n * fire on unrelated prose (\"set `k=rsa` for the legacy importer\"). A real DKIM\n * record always also carries `v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, or\n * the OpenDKIM `SigningAlgorithm` directive — one of those must be present.\n */\nfunction hasDkimMarker(content: string): boolean {\n return (\n /\\bDKIM1\\b/i.test(content) ||\n content.includes(\"_domainkey\") ||\n /DKIM-Signature/i.test(content) ||\n /\\bSigningAlgorithm\\b/i.test(content)\n );\n}\n\ninterface DkimRule {\n meta: RuleMeta;\n res: readonly RegExp[];\n}\n\nconst DKIM_RULES: readonly DkimRule[] = [\n { meta: RULE_DKIM_RSA, res: [RE_RSA_KEYTAG, RE_RSA_ALGTAG, RE_RSA_OPENDKIM] },\n { meta: RULE_DKIM_EDDSA, res: [RE_EDDSA_KEYTAG, RE_EDDSA_ALGTAG, RE_EDDSA_OPENDKIM] },\n];\n\n/** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */\nexport const dkimDetector: Detector = {\n id: \"dkim-crypto\",\n description: \"Classical DKIM email signing keys/algorithms in zone files / mail signer config\",\n scope: \"config\",\n language: \"any\",\n rules: DKIM_RULES.map((r) => r.meta),\n appliesTo: (f) => hasExtension(f, DKIM_EXTENSIONS) && !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Defensive doc guard: even if a broader gate ever let a prose file through,\n // a README mentioning `k=rsa` in a sentence must never be flagged.\n if (hasExtension(file, DOC_EXTENSIONS)) return [];\n if (!hasDkimMarker(content)) return [];\n\n // Mask whole comment lines so a commented-out record can't fire: zone files\n // use `;`, mail/OpenDKIM config uses `#` and `//`. Offsets are preserved so\n // finding line/column/snippet stay exact for the live lines that remain.\n const scan = maskCommentLines(content, [\";\", \"#\", \"//\"]);\n const findings: Finding[] = [];\n for (const { meta, res } of DKIM_RULES) {\n for (const re of res) {\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n }\n }\n return findings;\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dnssec.d.ts","sourceRoot":"","sources":["../../src/detectors/dnssec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dnssec.d.ts","sourceRoot":"","sources":["../../src/detectors/dnssec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAoH/D,mFAAmF;AACnF,eAAO,MAAM,cAAc,EAAE,QA0B5B,CAAC"}
|
package/dist/detectors/dnssec.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
2
2
|
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
3
|
const DNSSEC_EXTENSIONS = [".zone", ".db", ".conf"];
|
|
4
4
|
// IANA DNSSEC algorithm numbers, grouped by family.
|
|
@@ -7,18 +7,23 @@ const NUM_ECDSA = "13|14"; // ECDSAP256SHA256, ECDSAP384SHA384
|
|
|
7
7
|
const NUM_EDDSA = "15|16"; // ED25519, ED448
|
|
8
8
|
const NUM_DSA = "3|6"; // DSA, DSA-NSEC3-SHA1
|
|
9
9
|
// --- NAMED form: mnemonic algorithm names in signer/policy config or CLI args. ---
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Case-insensitive: BIND `dnssec-policy` and Knot config conventionally write the
|
|
11
|
+
// mnemonics in lowercase (`algorithm rsasha256;`, `algorithm ed25519;`), so a
|
|
12
|
+
// case-sensitive match would miss the primary real-world signer-config syntax. Still
|
|
13
|
+
// gated by `hasDnssecMarker` + the DNSSEC extensions, so generic tokens can't fire.
|
|
14
|
+
const RE_NAMED_RSA = /\bRSASHA(?:256|512|1(?:-NSEC3-SHA1)?)\b/gi;
|
|
15
|
+
const RE_NAMED_ECDSA = /\bECDSAP(?:256SHA256|384SHA384)\b/gi;
|
|
16
|
+
const RE_NAMED_EDDSA = /\bED(?:25519|448)\b/gi;
|
|
13
17
|
// Bare "DSA" is too generic to key off alone; require it right after an
|
|
14
18
|
// `algorithm` keyword (Knot/BIND config / dnssec-policy style), optionally
|
|
15
19
|
// quoted or separated by `:`/`=`.
|
|
16
20
|
const RE_NAMED_DSA = /\balgorithm\s*[:=]?\s*"?DSA(?:-NSEC3-SHA1)?"?\b/gi;
|
|
17
|
-
// --- STRUCTURAL form: `DNSKEY <flags> 3 <algorithm>` presentation RDATA (RFC 4034
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
21
|
+
// --- STRUCTURAL form: `[C]DNSKEY <flags> 3 <algorithm>` presentation RDATA (RFC 4034
|
|
22
|
+
// §2). `C?` also matches CDNSKEY (child-zone key with identical RDATA, RFC 7344). ---
|
|
23
|
+
const RE_DNSKEY_RSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_RSA})\\b`, "g");
|
|
24
|
+
const RE_DNSKEY_ECDSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_ECDSA})\\b`, "g");
|
|
25
|
+
const RE_DNSKEY_EDDSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_EDDSA})\\b`, "g");
|
|
26
|
+
const RE_DNSKEY_DSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_DSA})\\b`, "g");
|
|
22
27
|
const RULE_DNSSEC_RSA = {
|
|
23
28
|
id: "dnssec-rsa-sig",
|
|
24
29
|
title: "DNSSEC RSA signing algorithm",
|
|
@@ -95,10 +100,14 @@ export const dnssecDetector = {
|
|
|
95
100
|
detect({ file, content }) {
|
|
96
101
|
if (!hasDnssecMarker(content))
|
|
97
102
|
return [];
|
|
103
|
+
// Mask whole comment lines so a commented-out algorithm can't fire: zone files
|
|
104
|
+
// use `;`, named.conf uses `#` and `//`. These extensions are not centrally
|
|
105
|
+
// stripped; offsets are preserved so finding locations stay exact.
|
|
106
|
+
const scan = maskCommentLines(content, [";", "#", "//"]);
|
|
98
107
|
const findings = [];
|
|
99
108
|
for (const { meta, res } of DNSSEC_RULES) {
|
|
100
109
|
for (const re of res) {
|
|
101
|
-
eachMatch(re,
|
|
110
|
+
eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
return findings;
|