@quantakrypto/core 0.4.3 → 0.5.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 +9 -0
- package/dist/cbom-merge.d.ts.map +1 -0
- package/dist/cbom-merge.js +121 -0
- package/dist/cbom-merge.js.map +1 -0
- 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 +6 -26
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +77 -38
- package/dist/comments.js.map +1 -1
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +1 -1
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +59 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +143 -8
- 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 +4 -2
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +222 -4
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts +27 -0
- package/dist/detectors/cicd.d.ts.map +1 -0
- package/dist/detectors/cicd.js +129 -0
- package/dist/detectors/cicd.js.map +1 -0
- package/dist/detectors/cloud-kms.d.ts +24 -0
- package/dist/detectors/cloud-kms.d.ts.map +1 -0
- package/dist/detectors/cloud-kms.js +99 -0
- package/dist/detectors/cloud-kms.js.map +1 -0
- package/dist/detectors/cloudformation.d.ts +38 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -0
- package/dist/detectors/cloudformation.js +201 -0
- package/dist/detectors/cloudformation.js.map +1 -0
- 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 +19 -0
- package/dist/detectors/database.d.ts.map +1 -0
- package/dist/detectors/database.js +97 -0
- package/dist/detectors/database.js.map +1 -0
- 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 +55 -0
- package/dist/detectors/dnssec.d.ts.map +1 -0
- package/dist/detectors/dnssec.js +116 -0
- package/dist/detectors/dnssec.js.map +1 -0
- package/dist/detectors/elixir.d.ts +20 -0
- package/dist/detectors/elixir.d.ts.map +1 -0
- package/dist/detectors/elixir.js +227 -0
- package/dist/detectors/elixir.js.map +1 -0
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +34 -4
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts +20 -0
- package/dist/detectors/jose.d.ts.map +1 -0
- package/dist/detectors/jose.js +72 -0
- package/dist/detectors/jose.js.map +1 -0
- package/dist/detectors/jwk.d.ts +25 -0
- package/dist/detectors/jwk.d.ts.map +1 -0
- package/dist/detectors/jwk.js +131 -0
- package/dist/detectors/jwk.js.map +1 -0
- package/dist/detectors/k8s.d.ts +17 -0
- package/dist/detectors/k8s.d.ts.map +1 -0
- package/dist/detectors/k8s.js +88 -0
- package/dist/detectors/k8s.js.map +1 -0
- 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 +34 -0
- package/dist/detectors/mesh.d.ts.map +1 -0
- package/dist/detectors/mesh.js +91 -0
- package/dist/detectors/mesh.js.map +1 -0
- package/dist/detectors/messaging.d.ts +16 -0
- package/dist/detectors/messaging.d.ts.map +1 -0
- package/dist/detectors/messaging.js +79 -0
- package/dist/detectors/messaging.js.map +1 -0
- 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 +33 -1
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +25 -0
- package/dist/detectors/php.d.ts.map +1 -0
- package/dist/detectors/php.js +171 -0
- package/dist/detectors/php.js.map +1 -0
- 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/python.d.ts.map +1 -1
- package/dist/detectors/python.js +70 -0
- package/dist/detectors/python.js.map +1 -1
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +73 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +24 -0
- package/dist/detectors/secrets.d.ts.map +1 -0
- package/dist/detectors/secrets.js +63 -0
- package/dist/detectors/secrets.js.map +1 -0
- 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 +243 -53
- 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/stateful-hbs.d.ts.map +1 -1
- package/dist/detectors/stateful-hbs.js +9 -6
- package/dist/detectors/stateful-hbs.js.map +1 -1
- package/dist/detectors/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 +21 -0
- package/dist/detectors/terraform.d.ts.map +1 -0
- package/dist/detectors/terraform.js +150 -0
- package/dist/detectors/terraform.js.map +1 -0
- 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 +22 -0
- package/dist/detectors/vpn.d.ts.map +1 -0
- package/dist/detectors/vpn.js +73 -0
- package/dist/detectors/vpn.js.map +1 -0
- 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 +79 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +76 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +17 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -9
- 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/policy.d.ts +67 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +81 -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 +23 -2
- 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 +9 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +38 -1
- 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/standards.d.ts +96 -0
- package/dist/standards.d.ts.map +1 -0
- package/dist/standards.js +107 -0
- package/dist/standards.js.map +1 -0
- package/dist/types.d.ts +6 -5
- 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 +8 -3
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +32 -1
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +146 -0
- package/src/cbom.ts +153 -39
- package/src/comments.ts +84 -39
- package/src/dependencies.ts +2 -1
- package/src/detect-utils.ts +146 -12
- 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 +240 -6
- package/src/detectors/cicd.ts +171 -0
- package/src/detectors/cloud-kms.ts +141 -0
- package/src/detectors/cloudformation.ts +250 -0
- 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 +141 -0
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +195 -0
- package/src/detectors/elixir.ts +268 -0
- package/src/detectors/java.ts +39 -4
- package/src/detectors/jose.ts +110 -0
- package/src/detectors/jwk.ts +170 -0
- package/src/detectors/k8s.ts +115 -0
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +130 -0
- package/src/detectors/messaging.ts +106 -0
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +31 -1
- package/src/detectors/php.ts +230 -0
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/python.ts +71 -0
- package/src/detectors/rust.ts +76 -0
- package/src/detectors/secrets.ts +103 -0
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +283 -73
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/stateful-hbs.ts +9 -6
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +188 -0
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +103 -0
- 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 +138 -4
- package/src/index.ts +50 -12
- package/src/inventory.ts +15 -7
- package/src/policy.ts +180 -0
- package/src/registry.ts +81 -4
- package/src/remediate-pipeline.ts +26 -3
- package/src/remediation.ts +39 -0
- package/src/report.ts +43 -1
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/standards.ts +186 -0
- package/src/types.ts +11 -4
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +33 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical SSH **certificate-authority** cryptography — the
|
|
3
|
+
* OpenSSH certificate mechanism, in which a CA key signs host/user certificates
|
|
4
|
+
* (`*-cert-v01@openssh.com`) that servers and clients then trust in place of
|
|
5
|
+
* bare public keys.
|
|
6
|
+
*
|
|
7
|
+
* This is DIFFERENT from the `ssh-kex-classical` rule in `source.ts`, and from
|
|
8
|
+
* the `ssh-public-key` rule there — deliberately non-overlapping:
|
|
9
|
+
* - `ssh-kex-classical` is about the SSH *key-exchange* (finite-field DH, ECDH,
|
|
10
|
+
* curve25519): session-key *agreement*, `category: "key-exchange"`,
|
|
11
|
+
* `hndl: true` — a passively-recorded handshake is decryptable once a CRQC
|
|
12
|
+
* exists (harvest-now-decrypt-later confidentiality loss).
|
|
13
|
+
* - `ssh-public-key` flags bare `ssh-rsa AAAA…` / `ecdsa-sha2-* …` key lines.
|
|
14
|
+
* - THIS detector flags the SSH **certificate / CA signing** surface: the
|
|
15
|
+
* `*-cert-v01@openssh.com` cert key types, the `@cert-authority` known_hosts
|
|
16
|
+
* marker, the `TrustedUserCAKeys` / `HostCertificate` sshd directives, and
|
|
17
|
+
* `ssh-keygen -s` (the command that signs a certificate with a CA key).
|
|
18
|
+
*
|
|
19
|
+
* WHY it matters for PQC: an SSH certificate is a *signature* made by a CA key
|
|
20
|
+
* over a principal's public key + validity constraints. There is no
|
|
21
|
+
* confidentiality to harvest (so `hndl: false`), but every such signature
|
|
22
|
+
* becomes FORGEABLE the moment a cryptographically-relevant quantum computer
|
|
23
|
+
* (CRQC) can recover the classical CA private key from its public key. Forging a
|
|
24
|
+
* CA signature lets an attacker mint host or user certificates that every relying
|
|
25
|
+
* party accepts — a full trust-root compromise. SSH CA keys are also unusually
|
|
26
|
+
* long-lived (they are the anchor an entire fleet trusts and are rotated
|
|
27
|
+
* rarely), so the exposure window is large. Hence `category: "signature"`,
|
|
28
|
+
* `severity: "medium"`, `confidence: "high"`.
|
|
29
|
+
*
|
|
30
|
+
* Two match shapes:
|
|
31
|
+
* 1. The cert key type, which NAMES the signing algorithm → an accurate
|
|
32
|
+
* {@link AlgorithmFamily}:
|
|
33
|
+
* - `ssh-rsa-cert-v01@openssh.com`, `rsa-sha2-256/512-cert-v01@openssh.com` → RSA
|
|
34
|
+
* - `ecdsa-sha2-nistp256/384/521-cert-v01@openssh.com` → ECDSA
|
|
35
|
+
* - `ssh-ed25519-cert-v01@openssh.com` → EdDSA
|
|
36
|
+
* 2. The CA *deployment* directives — `TrustedUserCAKeys` / `HostCertificate`
|
|
37
|
+
* (sshd_config) and `ssh-keygen -s` (the CA signing command). These are the
|
|
38
|
+
* MOST COMMON way an SSH CA is configured, but they don't name the algorithm
|
|
39
|
+
* (that lives in the referenced key file), so they emit the `ssh-ca-config`
|
|
40
|
+
* rule with `algorithm: "unknown"` and `confidence: "medium"`.
|
|
41
|
+
*
|
|
42
|
+
* Precision:
|
|
43
|
+
* - Fast reject: `detect()` bails unless the file carries an SSH-CA marker
|
|
44
|
+
* (`cert-v01@openssh.com`, `@cert-authority`, `TrustedUserCAKeys`,
|
|
45
|
+
* `HostCertificate`, or `ssh-keygen -s`) — so the algorithm tokens can't be
|
|
46
|
+
* reached on unrelated config.
|
|
47
|
+
* - The cert-type tokens are the full `*-cert-v01@openssh.com` strings. The
|
|
48
|
+
* trailing `-cert-v01@openssh.com` suffix distinguishes a CA certificate type
|
|
49
|
+
* from the plain `ssh-rsa` / `ssh-ed25519` public-key tokens handled by
|
|
50
|
+
* `ssh-public-key`, so a non-cert `ssh-ed25519 AAAA…` line NEVER fires a cert
|
|
51
|
+
* rule here.
|
|
52
|
+
* - Comment lines (`#`) are masked with {@link maskCommentLines} (offsets
|
|
53
|
+
* preserved) so a commented-out cert line can't fire. Known_hosts
|
|
54
|
+
* `@cert-authority` lines start with `@`, not `#`, so masking leaves them
|
|
55
|
+
* live — exactly what we want.
|
|
56
|
+
* - Doc/prose files are excluded ({@link DOC_EXTENSIONS}), and so are program
|
|
57
|
+
* SOURCE files ({@link ANALYZABLE_SOURCE_EXTENSIONS}) — a vendored SSH library
|
|
58
|
+
* or constants table that spells a cert type as a string literal is not live
|
|
59
|
+
* CA config. SSH CA config lives in extensionless / `.conf` / `.pub` files.
|
|
60
|
+
*
|
|
61
|
+
* The matched line frequently carries the CA public key's base64 blob (a
|
|
62
|
+
* `@cert-authority` known_hosts entry), so findings are marked `sensitive` and
|
|
63
|
+
* reporters drop the snippet, consistent with `ssh-public-key`.
|
|
64
|
+
*/
|
|
65
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
66
|
+
import {
|
|
67
|
+
eachMatch,
|
|
68
|
+
findingFromRule,
|
|
69
|
+
hasExtension,
|
|
70
|
+
maskCommentLines,
|
|
71
|
+
DOC_EXTENSIONS,
|
|
72
|
+
ANALYZABLE_SOURCE_EXTENSIONS,
|
|
73
|
+
} from "../detect-utils.js";
|
|
74
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
75
|
+
|
|
76
|
+
// --- OpenSSH certificate key types (`*-cert-v01@openssh.com`), by signing family. ---
|
|
77
|
+
// The `\b` after `.com` is a real boundary (`m` is a word char); the leading `\b`
|
|
78
|
+
// keeps `ssh-rsa-cert-v01@…` from matching inside a longer token. RSA covers the
|
|
79
|
+
// legacy `ssh-rsa` cert type plus the SHA-2 RSA cert types (`rsa-sha2-256/512`)
|
|
80
|
+
// OpenSSH emits for RSA CA/host certificates.
|
|
81
|
+
const RE_CERT_RSA = /\b(?:ssh-rsa|rsa-sha2-(?:256|512))-cert-v01@openssh\.com\b/g;
|
|
82
|
+
const RE_CERT_ECDSA = /\becdsa-sha2-nistp(?:256|384|521)-cert-v01@openssh\.com\b/g;
|
|
83
|
+
const RE_CERT_EDDSA = /\bssh-ed25519-cert-v01@openssh\.com\b/g;
|
|
84
|
+
// The canonical CA *deployment* directives — the most common way an SSH CA is
|
|
85
|
+
// configured (sshd_config `TrustedUserCAKeys` / `HostCertificate`, and the CA
|
|
86
|
+
// signing command `ssh-keygen -s`). These name the CA usage but not the algorithm
|
|
87
|
+
// (that lives in the referenced key file), so the finding is `algorithm: "unknown"`.
|
|
88
|
+
const RE_CA_DIRECTIVE = /\bTrustedUserCAKeys\b|\bHostCertificate\b|\bssh-keygen\s+-s\b/g;
|
|
89
|
+
|
|
90
|
+
const REMEDIATION =
|
|
91
|
+
"No post-quantum SSH certificate format is standardized yet — track OpenSSH release notes and IETF work on PQC signatures for SSH. SSH CA keys are long-lived trust roots, so plan for their rotation to a PQC signing algorithm (e.g. ML-DSA) as soon as a cert format lands, and keep validity periods short in the interim.";
|
|
92
|
+
|
|
93
|
+
const RULE_CA_RSA: RuleMeta = {
|
|
94
|
+
id: "ssh-ca-rsa-cert",
|
|
95
|
+
title: "SSH certificate authority — RSA signing",
|
|
96
|
+
description: "OpenSSH RSA certificate key type (ssh-rsa/rsa-sha2-*-cert-v01@openssh.com)",
|
|
97
|
+
category: "signature",
|
|
98
|
+
severity: "medium",
|
|
99
|
+
confidence: "high",
|
|
100
|
+
algorithm: "RSA",
|
|
101
|
+
hndl: false,
|
|
102
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
103
|
+
sensitive: true,
|
|
104
|
+
message:
|
|
105
|
+
"SSH certificate uses a classical RSA key type (ssh-rsa/rsa-sha2-*-cert-v01@openssh.com); the CA signature is forgeable once a CRQC can recover the RSA CA key.",
|
|
106
|
+
remediation: REMEDIATION,
|
|
107
|
+
};
|
|
108
|
+
const RULE_CA_ECDSA: RuleMeta = {
|
|
109
|
+
id: "ssh-ca-ecdsa-cert",
|
|
110
|
+
title: "SSH certificate authority — ECDSA signing",
|
|
111
|
+
description: "OpenSSH ECDSA certificate key type (ecdsa-sha2-nistp*-cert-v01@openssh.com)",
|
|
112
|
+
category: "signature",
|
|
113
|
+
severity: "medium",
|
|
114
|
+
confidence: "high",
|
|
115
|
+
algorithm: "ECDSA",
|
|
116
|
+
hndl: false,
|
|
117
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
118
|
+
sensitive: true,
|
|
119
|
+
message:
|
|
120
|
+
"SSH certificate uses a classical ECDSA key type (ecdsa-sha2-nistp*-cert-v01@openssh.com); the CA signature is forgeable once a CRQC can recover the ECDSA CA key.",
|
|
121
|
+
remediation: REMEDIATION,
|
|
122
|
+
};
|
|
123
|
+
const RULE_CA_EDDSA: RuleMeta = {
|
|
124
|
+
id: "ssh-ca-ed25519-cert",
|
|
125
|
+
title: "SSH certificate authority — EdDSA signing",
|
|
126
|
+
description: "OpenSSH Ed25519 certificate key type (ssh-ed25519-cert-v01@openssh.com)",
|
|
127
|
+
category: "signature",
|
|
128
|
+
severity: "medium",
|
|
129
|
+
confidence: "high",
|
|
130
|
+
algorithm: "EdDSA",
|
|
131
|
+
hndl: false,
|
|
132
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
133
|
+
sensitive: true,
|
|
134
|
+
message:
|
|
135
|
+
"SSH certificate uses a classical Ed25519 key type (ssh-ed25519-cert-v01@openssh.com); modern but still classical — the CA signature is forgeable once a CRQC can recover the Ed25519 CA key.",
|
|
136
|
+
remediation: REMEDIATION,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const RULE_CA_CONFIG: RuleMeta = {
|
|
140
|
+
id: "ssh-ca-config",
|
|
141
|
+
title: "SSH certificate authority configured",
|
|
142
|
+
description:
|
|
143
|
+
"OpenSSH CA deployment directive (TrustedUserCAKeys / HostCertificate / ssh-keygen -s)",
|
|
144
|
+
category: "signature",
|
|
145
|
+
severity: "medium",
|
|
146
|
+
// The directive names CA usage, not the algorithm — hence a lower confidence and
|
|
147
|
+
// an unknown family; the CA key file itself carries the specific algorithm.
|
|
148
|
+
confidence: "medium",
|
|
149
|
+
algorithm: "unknown",
|
|
150
|
+
hndl: false,
|
|
151
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
152
|
+
message:
|
|
153
|
+
"SSH certificate authority is configured (TrustedUserCAKeys / HostCertificate / ssh-keygen -s); the CA signing key is a long-lived classical trust root whose signatures become forgeable once a CRQC exists — verify the CA key's algorithm.",
|
|
154
|
+
remediation: REMEDIATION,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
interface SshCaRule {
|
|
158
|
+
meta: RuleMeta;
|
|
159
|
+
re: RegExp;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const SSH_CA_RULES: readonly SshCaRule[] = [
|
|
163
|
+
{ meta: RULE_CA_RSA, re: RE_CERT_RSA },
|
|
164
|
+
{ meta: RULE_CA_ECDSA, re: RE_CERT_ECDSA },
|
|
165
|
+
{ meta: RULE_CA_EDDSA, re: RE_CERT_EDDSA },
|
|
166
|
+
{ meta: RULE_CA_CONFIG, re: RE_CA_DIRECTIVE },
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
/** True when `content` carries some SSH-CA-specific marker (not just a bare token). */
|
|
170
|
+
function hasSshCaMarker(content: string): boolean {
|
|
171
|
+
return (
|
|
172
|
+
content.includes("cert-v01@openssh.com") ||
|
|
173
|
+
content.includes("@cert-authority") ||
|
|
174
|
+
content.includes("TrustedUserCAKeys") ||
|
|
175
|
+
content.includes("HostCertificate") ||
|
|
176
|
+
content.includes("ssh-keygen -s")
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Detects classical SSH certificate-authority signing (OpenSSH cert key types)
|
|
182
|
+
* in SSH config, sshd_config, and known_hosts files. Distinct from SSH
|
|
183
|
+
* key-exchange (`ssh-kex-classical`): this is the certificate SIGNATURE surface.
|
|
184
|
+
*/
|
|
185
|
+
export const sshCaDetector: Detector = {
|
|
186
|
+
id: "ssh-ca",
|
|
187
|
+
description: "Classical SSH certificate-authority signing (OpenSSH *-cert-v01@openssh.com)",
|
|
188
|
+
scope: "config",
|
|
189
|
+
language: "any",
|
|
190
|
+
rules: SSH_CA_RULES.map((r) => r.meta),
|
|
191
|
+
// Apply broadly (SSH CA config lives in many differently-named / extensionless
|
|
192
|
+
// files: sshd_config, known_hosts, *.pub) but never on prose/docs OR on program
|
|
193
|
+
// SOURCE files — a vendored SSH library or a constants table that spells out
|
|
194
|
+
// `ssh-ed25519-cert-v01@openssh.com` as a string literal is not a live CA config.
|
|
195
|
+
// The strict fast-reject in detect() is the remaining gate.
|
|
196
|
+
appliesTo: (f) =>
|
|
197
|
+
!hasExtension(f, DOC_EXTENSIONS) && !hasExtension(f, ANALYZABLE_SOURCE_EXTENSIONS),
|
|
198
|
+
detect({ file, content }): Finding[] {
|
|
199
|
+
if (!hasSshCaMarker(content)) return [];
|
|
200
|
+
|
|
201
|
+
// Mask `#` comment lines so a commented-out cert line can't fire; offsets are
|
|
202
|
+
// preserved. `@cert-authority` known_hosts lines start with `@`, not `#`, so
|
|
203
|
+
// they are intentionally left live.
|
|
204
|
+
const scan = maskCommentLines(content, ["#"]);
|
|
205
|
+
const findings: Finding[] = [];
|
|
206
|
+
for (const { meta, re } of SSH_CA_RULES) {
|
|
207
|
+
eachMatch(re, scan, (m) =>
|
|
208
|
+
findings.push(
|
|
209
|
+
findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return findings;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
@@ -39,8 +39,9 @@ interface HbsRule {
|
|
|
39
39
|
|
|
40
40
|
const HBS_RULES: HbsRule[] = [
|
|
41
41
|
{
|
|
42
|
-
// LMS parameter set, e.g. LMS_SHA256_M32_H10
|
|
43
|
-
|
|
42
|
+
// LMS parameter set, e.g. LMS_SHA256_M32_H10 / LMS_SHAKE_M24_H10 (SP 800-208
|
|
43
|
+
// adds SHAKE256 and the 192-bit M24/N24 sets to RFC 8554's SHA-256 sets).
|
|
44
|
+
re: /\bLMS_(?:SHA256|SHAKE(?:256)?)_[MN]\d+_[HW]\d+\b/g,
|
|
44
45
|
meta: {
|
|
45
46
|
id: "stateful-hbs-lms-param",
|
|
46
47
|
title: "LMS parameter set (stateful hash-based signature)",
|
|
@@ -93,8 +94,9 @@ const HBS_RULES: HbsRule[] = [
|
|
|
93
94
|
},
|
|
94
95
|
},
|
|
95
96
|
{
|
|
96
|
-
// XMSS parameter set, e.g. XMSS-SHA2_10_256
|
|
97
|
-
|
|
97
|
+
// XMSS parameter set, e.g. XMSS-SHA2_10_256 / XMSS-SHAKE256_10_192 (SP 800-208
|
|
98
|
+
// adds the SHAKE256 and 192-bit variants to RFC 8391's SHA-2/256 sets).
|
|
99
|
+
re: /\bXMSS-(?:SHA2|SHAKE(?:256)?)_\d+_(?:192|256)\b/g,
|
|
98
100
|
meta: {
|
|
99
101
|
id: "stateful-hbs-xmss-param",
|
|
100
102
|
title: "XMSS parameter set (stateful hash-based signature)",
|
|
@@ -111,8 +113,9 @@ const HBS_RULES: HbsRule[] = [
|
|
|
111
113
|
},
|
|
112
114
|
},
|
|
113
115
|
{
|
|
114
|
-
// XMSSMT (multi-tree XMSS) parameter set, e.g. XMSSMT-SHA2_20/2_256
|
|
115
|
-
|
|
116
|
+
// XMSSMT (multi-tree XMSS) parameter set, e.g. XMSSMT-SHA2_20/2_256 or the
|
|
117
|
+
// SP 800-208 SHAKE256 variant XMSSMT-SHAKE256_20/2_256.
|
|
118
|
+
re: /\bXMSSMT-(?:SHA2|SHAKE(?:256)?)_\d+\b/g,
|
|
116
119
|
meta: {
|
|
117
120
|
id: "stateful-hbs-xmssmt-param",
|
|
118
121
|
title: "XMSSMT parameter set (stateful hash-based signature)",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical supply-chain / artifact signing beyond the cosign +
|
|
3
|
+
* GPG cases the `cicd` detector covers — Docker Content Trust (Notary v1 / TUF),
|
|
4
|
+
* CNCF Notation, and in-toto. These sign container images, OCI artifacts, and
|
|
5
|
+
* build provenance with classical keys (ECDSA / RSA / Ed25519). Like all signing,
|
|
6
|
+
* this is the signature-side analogue of harvest-now-decrypt-later: a signature
|
|
7
|
+
* produced today is forgeable once a CRQC exists, and the signed artifact keeps
|
|
8
|
+
* verifying against that classical key for years — so these are `hndl:false` but
|
|
9
|
+
* real quantum-migration debt.
|
|
10
|
+
*
|
|
11
|
+
* Gated to CI pipeline files, Dockerfiles, and shell scripts, over comment-masked
|
|
12
|
+
* content, and keyed on distinctive command/env tokens to keep false positives low.
|
|
13
|
+
*/
|
|
14
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
15
|
+
import { eachMatch, findingFromRule, maskCommentLines } from "../detect-utils.js";
|
|
16
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
17
|
+
|
|
18
|
+
/** CI pipeline files, Dockerfiles, and shell scripts — where signing runs. */
|
|
19
|
+
function isSigningContext(filePath: string): boolean {
|
|
20
|
+
const lower = filePath.toLowerCase();
|
|
21
|
+
const base = lower.split("/").pop() ?? lower;
|
|
22
|
+
return (
|
|
23
|
+
(lower.includes(".github/workflows/") && (lower.endsWith(".yml") || lower.endsWith(".yaml"))) ||
|
|
24
|
+
base === ".gitlab-ci.yml" ||
|
|
25
|
+
lower.endsWith(".gitlab-ci.yml") ||
|
|
26
|
+
base === "jenkinsfile" ||
|
|
27
|
+
lower.endsWith(".jenkinsfile") ||
|
|
28
|
+
base === "dockerfile" ||
|
|
29
|
+
base.startsWith("dockerfile.") ||
|
|
30
|
+
lower.endsWith(".dockerfile") ||
|
|
31
|
+
lower.endsWith(".sh") ||
|
|
32
|
+
lower.endsWith(".bash")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ScRule {
|
|
37
|
+
re: RegExp;
|
|
38
|
+
meta: RuleMeta;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const SC_RULES: ScRule[] = [
|
|
42
|
+
{
|
|
43
|
+
re: /\bDOCKER_CONTENT_TRUST\s*[:=]\s*["']?1\b|\bdocker\s+trust\s+sign\b/g,
|
|
44
|
+
meta: {
|
|
45
|
+
id: "sc-docker-content-trust",
|
|
46
|
+
title: "Docker Content Trust signing (Notary v1)",
|
|
47
|
+
description: "Docker Content Trust / docker trust sign — Notary v1 (TUF) classical keys",
|
|
48
|
+
category: "signature",
|
|
49
|
+
severity: "medium",
|
|
50
|
+
confidence: "high",
|
|
51
|
+
algorithm: "ECDSA",
|
|
52
|
+
hndl: false,
|
|
53
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
54
|
+
message:
|
|
55
|
+
"Docker Content Trust signs images with Notary v1 (TUF) classical keys (ECDSA/Ed25519); signatures are forgeable once a CRQC exists.",
|
|
56
|
+
remediation: "Track sigstore/Notary v2 (Notation) PQC roadmap; plan hybrid image signing.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
re: /\bnotation\s+(?:sign|key\s+generate|cert\s+generate)\b/g,
|
|
61
|
+
meta: {
|
|
62
|
+
id: "sc-notation-sign",
|
|
63
|
+
title: "Notation artifact signing",
|
|
64
|
+
description: "CNCF Notation signing of OCI artifacts with classical keys",
|
|
65
|
+
category: "signature",
|
|
66
|
+
severity: "medium",
|
|
67
|
+
confidence: "high",
|
|
68
|
+
algorithm: "RSA",
|
|
69
|
+
hndl: false,
|
|
70
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
71
|
+
message:
|
|
72
|
+
"Notation signs OCI artifacts with a classical key (RSA/ECDSA); signatures are forgeable once a CRQC exists.",
|
|
73
|
+
remediation: "Track Notation's PQC signature support (ML-DSA) and plan migration.",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
re: /\bin[-_]toto[-_]?run\b|\bin-toto\b/g,
|
|
78
|
+
meta: {
|
|
79
|
+
id: "sc-in-toto",
|
|
80
|
+
title: "in-toto supply-chain signing",
|
|
81
|
+
description: "in-toto build provenance signed with classical keys",
|
|
82
|
+
category: "signature",
|
|
83
|
+
severity: "medium",
|
|
84
|
+
confidence: "high",
|
|
85
|
+
algorithm: "RSA",
|
|
86
|
+
hndl: false,
|
|
87
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
88
|
+
message:
|
|
89
|
+
"in-toto signs build provenance with a classical key (RSA/Ed25519); the attestation is forgeable once a CRQC exists.",
|
|
90
|
+
remediation: "Track in-toto/DSSE PQC signature support and plan migration.",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/** Detects classical Docker Content Trust / Notation / in-toto signing. */
|
|
96
|
+
export const supplyChainDetector: Detector = {
|
|
97
|
+
id: "supply-chain-signing",
|
|
98
|
+
description: "Classical container/artifact signing (Docker Content Trust, Notation, in-toto)",
|
|
99
|
+
scope: "config",
|
|
100
|
+
language: "any",
|
|
101
|
+
rules: SC_RULES.map((r) => r.meta),
|
|
102
|
+
appliesTo: isSigningContext,
|
|
103
|
+
detect({ file, content }): Finding[] {
|
|
104
|
+
// `#` covers YAML/Dockerfile/shell; `//` covers Jenkinsfile (Groovy), which has no
|
|
105
|
+
// extension so it is never centrally comment-stripped.
|
|
106
|
+
const scan = maskCommentLines(content, ["#", "//"]);
|
|
107
|
+
const findings: Finding[] = [];
|
|
108
|
+
for (const rule of SC_RULES) {
|
|
109
|
+
eachMatch(rule.re, scan, (m) => {
|
|
110
|
+
findings.push(
|
|
111
|
+
findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return findings;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source detector for classical asymmetric cryptography in Swift (Apple platforms) —
|
|
3
|
+
* Apple CryptoKit and the older Security framework (`SecKey*`). Lexical, same strategy
|
|
4
|
+
* as the other language packs; the API names are distinctive enough to keep the
|
|
5
|
+
* false-positive risk low. Comment/string suppression is handled centrally
|
|
6
|
+
* (`.swift` is a C-style comment language).
|
|
7
|
+
*
|
|
8
|
+
* CryptoKit:
|
|
9
|
+
* - `P256|P384|P521.Signing.PrivateKey` → ECDSA signature (hndl:false)
|
|
10
|
+
* - `P256|P384|P521.KeyAgreement.PrivateKey` → ECDH key agreement (hndl:true)
|
|
11
|
+
* - `Curve25519.Signing.PrivateKey` → Ed25519 signature (hndl:false)
|
|
12
|
+
* - `Curve25519.KeyAgreement.PrivateKey` → X25519 key agreement (hndl:true)
|
|
13
|
+
* (`SecureEnclave.P256.…` forms are covered by the same P-curve regexes.)
|
|
14
|
+
*
|
|
15
|
+
* Security framework:
|
|
16
|
+
* - `SecKeyCreateRandomKey` with `kSecAttrKeyTypeRSA` → RSA
|
|
17
|
+
* - … with `kSecAttrKeyTypeEC` / `kSecAttrKeyTypeECSECPrimeRandom` → EC
|
|
18
|
+
*/
|
|
19
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
20
|
+
import { SWIFT_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
21
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
22
|
+
|
|
23
|
+
// CryptoKit P-curve keys, split by usage (Signing vs KeyAgreement). `SecureEnclave.`
|
|
24
|
+
// may prefix the P-curve type, so it is allowed optionally.
|
|
25
|
+
const RE_SWIFT_P_SIGN = /\b(?:SecureEnclave\.)?P(?:256|384|521)\.Signing\.PrivateKey\b/g;
|
|
26
|
+
const RE_SWIFT_P_KEX = /\b(?:SecureEnclave\.)?P(?:256|384|521)\.KeyAgreement\.PrivateKey\b/g;
|
|
27
|
+
// CryptoKit Curve25519: Signing = Ed25519 (EdDSA); KeyAgreement = X25519.
|
|
28
|
+
const RE_SWIFT_ED25519 = /\bCurve25519\.Signing\.PrivateKey\b/g;
|
|
29
|
+
const RE_SWIFT_X25519 = /\bCurve25519\.KeyAgreement\.PrivateKey\b/g;
|
|
30
|
+
// Security framework key-type attributes (used with SecKeyCreateRandomKey).
|
|
31
|
+
const RE_SWIFT_SEC_RSA = /\bkSecAttrKeyTypeRSA\b/g;
|
|
32
|
+
const RE_SWIFT_SEC_EC = /\bkSecAttrKeyType(?:EC|ECSECPrimeRandom)\b/g;
|
|
33
|
+
|
|
34
|
+
const RULE_SWIFT_ECDSA: RuleMeta = {
|
|
35
|
+
id: "swift-ecdsa",
|
|
36
|
+
title: "Swift CryptoKit ECDSA signing key",
|
|
37
|
+
description: "CryptoKit P256/P384/P521 Signing.PrivateKey",
|
|
38
|
+
category: "signature",
|
|
39
|
+
severity: "high",
|
|
40
|
+
confidence: "high",
|
|
41
|
+
algorithm: "ECDSA",
|
|
42
|
+
hndl: false,
|
|
43
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
44
|
+
message: "CryptoKit P-curve ECDSA signing (Swift) is forgeable by a quantum attacker.",
|
|
45
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
46
|
+
};
|
|
47
|
+
const RULE_SWIFT_ECDH: RuleMeta = {
|
|
48
|
+
id: "swift-ecdh",
|
|
49
|
+
title: "Swift CryptoKit ECDH key agreement",
|
|
50
|
+
description: "CryptoKit P256/P384/P521 KeyAgreement.PrivateKey",
|
|
51
|
+
category: "key-exchange",
|
|
52
|
+
severity: "high",
|
|
53
|
+
confidence: "high",
|
|
54
|
+
algorithm: "ECDH",
|
|
55
|
+
hndl: true,
|
|
56
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
57
|
+
message: "CryptoKit P-curve ECDH key agreement (Swift) is harvest-now-decrypt-later exposed.",
|
|
58
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
|
|
59
|
+
};
|
|
60
|
+
const RULE_SWIFT_ED25519: RuleMeta = {
|
|
61
|
+
id: "swift-ed25519",
|
|
62
|
+
title: "Swift CryptoKit Ed25519 signing key",
|
|
63
|
+
description: "CryptoKit Curve25519.Signing.PrivateKey (Ed25519)",
|
|
64
|
+
category: "signature",
|
|
65
|
+
// `low`, aligned with Ed25519 in every other source pack (go/rust/ruby/python/node)
|
|
66
|
+
// — the same primitive must not flip CI exit codes based on which language wrote it.
|
|
67
|
+
severity: "low",
|
|
68
|
+
confidence: "high",
|
|
69
|
+
algorithm: "EdDSA",
|
|
70
|
+
hndl: false,
|
|
71
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
72
|
+
message: "CryptoKit Ed25519 signing (Swift) is classical and forgeable by a quantum attacker.",
|
|
73
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
74
|
+
};
|
|
75
|
+
const RULE_SWIFT_X25519: RuleMeta = {
|
|
76
|
+
id: "swift-x25519",
|
|
77
|
+
title: "Swift CryptoKit X25519 key agreement",
|
|
78
|
+
description: "CryptoKit Curve25519.KeyAgreement.PrivateKey (X25519)",
|
|
79
|
+
category: "key-exchange",
|
|
80
|
+
severity: "medium",
|
|
81
|
+
confidence: "high",
|
|
82
|
+
algorithm: "X25519",
|
|
83
|
+
hndl: true,
|
|
84
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
85
|
+
message:
|
|
86
|
+
"CryptoKit X25519 key agreement (Swift) is classical and harvest-now-decrypt-later exposed.",
|
|
87
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
|
|
88
|
+
};
|
|
89
|
+
const RULE_SWIFT_RSA: RuleMeta = {
|
|
90
|
+
id: "swift-rsa",
|
|
91
|
+
title: "Swift Security-framework RSA key",
|
|
92
|
+
description: "SecKeyCreateRandomKey with kSecAttrKeyTypeRSA",
|
|
93
|
+
category: "kem",
|
|
94
|
+
severity: "high",
|
|
95
|
+
confidence: "high",
|
|
96
|
+
algorithm: "RSA",
|
|
97
|
+
hndl: true,
|
|
98
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
99
|
+
message: "Security-framework RSA key (Swift) is classical and not quantum-safe.",
|
|
100
|
+
remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
|
|
101
|
+
};
|
|
102
|
+
const RULE_SWIFT_SEC_EC: RuleMeta = {
|
|
103
|
+
id: "swift-sec-ec",
|
|
104
|
+
title: "Swift Security-framework EC key",
|
|
105
|
+
description: "SecKeyCreateRandomKey with kSecAttrKeyTypeEC",
|
|
106
|
+
category: "key-exchange",
|
|
107
|
+
severity: "high",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
algorithm: "ECDH",
|
|
110
|
+
hndl: true,
|
|
111
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
112
|
+
message:
|
|
113
|
+
"Security-framework EC key (Swift); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
114
|
+
remediation:
|
|
115
|
+
"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** Detects classical asymmetric crypto in Swift (CryptoKit + Security framework). */
|
|
119
|
+
export const swiftDetector: Detector = {
|
|
120
|
+
id: "swift-crypto",
|
|
121
|
+
description: "Classical asymmetric crypto in Swift (CryptoKit, Security framework)",
|
|
122
|
+
scope: "source",
|
|
123
|
+
language: "swift",
|
|
124
|
+
rules: [
|
|
125
|
+
RULE_SWIFT_ECDSA,
|
|
126
|
+
RULE_SWIFT_ECDH,
|
|
127
|
+
RULE_SWIFT_ED25519,
|
|
128
|
+
RULE_SWIFT_X25519,
|
|
129
|
+
RULE_SWIFT_RSA,
|
|
130
|
+
RULE_SWIFT_SEC_EC,
|
|
131
|
+
],
|
|
132
|
+
appliesTo: (f) => hasExtension(f, SWIFT_EXTENSIONS),
|
|
133
|
+
detect({ file, content }): Finding[] {
|
|
134
|
+
const findings: Finding[] = [];
|
|
135
|
+
const add = (re: RegExp, rule: RuleMeta) =>
|
|
136
|
+
eachMatch(re, content, (m) =>
|
|
137
|
+
findings.push(
|
|
138
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
add(RE_SWIFT_P_SIGN, RULE_SWIFT_ECDSA);
|
|
142
|
+
add(RE_SWIFT_P_KEX, RULE_SWIFT_ECDH);
|
|
143
|
+
add(RE_SWIFT_ED25519, RULE_SWIFT_ED25519);
|
|
144
|
+
add(RE_SWIFT_X25519, RULE_SWIFT_X25519);
|
|
145
|
+
add(RE_SWIFT_SEC_RSA, RULE_SWIFT_RSA);
|
|
146
|
+
add(RE_SWIFT_SEC_EC, RULE_SWIFT_SEC_EC);
|
|
147
|
+
return findings;
|
|
148
|
+
},
|
|
149
|
+
};
|