@quantakrypto/core 0.4.4 → 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.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/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 +53 -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/index.d.ts +14 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -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/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 +9 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +31 -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/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 +38 -8
- package/src/cbom.ts +153 -39
- package/src/comments.ts +25 -6
- 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 +48 -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/index.ts +41 -13
- package/src/inventory.ts +15 -7
- package/src/registry.ts +51 -4
- package/src/remediate-pipeline.ts +1 -1
- package/src/remediation.ts +39 -0
- package/src/report.ts +36 -1
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/types.ts +9 -4
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +33 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical asymmetric signature algorithms pinned by
|
|
3
|
+
* WebAuthn / FIDO2 / passkey attestation & assertion, expressed as COSE
|
|
4
|
+
* algorithm identifiers in relying-party code and config
|
|
5
|
+
* (@simplewebauthn/server, py_webauthn, go-webauthn, webauthn4j).
|
|
6
|
+
*
|
|
7
|
+
* WHY WebAuthn is PQC-relevant: a WebAuthn credential is an asymmetric key
|
|
8
|
+
* pair whose PUBLIC key the authenticator returns (COSE-encoded) at
|
|
9
|
+
* registration; every subsequent assertion is a classical signature over the
|
|
10
|
+
* authentication challenge, and attestation statements are classical
|
|
11
|
+
* signatures over the credential. These are AUTHENTICATION signatures, not
|
|
12
|
+
* confidentiality — there is nothing to "harvest now" — but the moment a
|
|
13
|
+
* cryptographically-relevant quantum computer (CRQC) exists, an attacker who
|
|
14
|
+
* has a user's registered public key (or an attestation CA's key) can FORGE
|
|
15
|
+
* assertions/attestations and impersonate the user or a genuine authenticator.
|
|
16
|
+
* So every finding here is `category: "signature"`, `hndl: false`.
|
|
17
|
+
*
|
|
18
|
+
* The relying party pins which algorithms it will accept: `pubKeyCredParams`
|
|
19
|
+
* (registration) and `supportedAlgorithmIDs` (verification) enumerate COSE
|
|
20
|
+
* `alg` values, and the classical ones — ES256 (COSE -7, ECDSA P-256),
|
|
21
|
+
* RS256 (-257, RSA), EdDSA (-8) and their P-384/P-521/RSA-2048+ siblings — are
|
|
22
|
+
* exactly the ones a PQC migration must inventory and replace once FIDO/COSE
|
|
23
|
+
* standardise post-quantum algorithm identifiers.
|
|
24
|
+
*
|
|
25
|
+
* WHY numeric COSE ids need their own detector: the existing JWT/JOSE rule
|
|
26
|
+
* only matches QUOTED alg strings (`"ES256"`, `"RS256"`). WebAuthn libraries
|
|
27
|
+
* almost never write those strings — they use the NUMERIC COSE ids
|
|
28
|
+
* (`alg: -7`, `supportedAlgorithmIDs: [-7, -257]`) and language-level enum
|
|
29
|
+
* identifiers (`COSEAlgorithmIdentifier.ES256`, `webauthncose.AlgES256`,
|
|
30
|
+
* `COSEAlgorithmIdentifier.EDDSA`). None of those carry the quote characters
|
|
31
|
+
* the JWT rule keys off, so the entire WebAuthn surface is invisible today.
|
|
32
|
+
*
|
|
33
|
+
* Two independent match shapes per family, both gated by a file-level WebAuthn
|
|
34
|
+
* marker (see `hasWebauthnMarker`) so the generic numeric `alg: -7` form can't
|
|
35
|
+
* fire on unrelated code that happens to assign a small negative number:
|
|
36
|
+
*
|
|
37
|
+
* 1. NUMERIC form — the COSE `alg` value as an integer, either as an object
|
|
38
|
+
* member (`alg: -7`, `alg = -257`) or inside a `supportedAlgorithmIDs`
|
|
39
|
+
* array (`supportedAlgorithmIDs: [-7, -257]`). Restricted to the classical
|
|
40
|
+
* COSE numbers only; symmetric / KDF / "direct" ids (e.g. -6, HS*) are
|
|
41
|
+
* never in the alternation, so they cannot match.
|
|
42
|
+
* 2. ENUM form — the COSE algorithm's identifier as written in each library:
|
|
43
|
+
* `COSEAlgorithmIdentifier.ES256|RS256|EdDSA|ECDSA_SHA_256` (webauthn4j,
|
|
44
|
+
* py_webauthn) and Go's `webauthncose.AlgES256` / `AlgRS256` / `AlgEdDSA`
|
|
45
|
+
* constants.
|
|
46
|
+
*
|
|
47
|
+
* COSE algorithm numbers → {@link AlgorithmFamily} (per the COSE registry, using
|
|
48
|
+
* the mapping this detector is specified against):
|
|
49
|
+
* - ES256 (-7), ES384 (-35), ES512 (-36) → ECDSA
|
|
50
|
+
* - RS256 (-257), RS384 (-258), RS512 (-259) → RSA
|
|
51
|
+
* - EdDSA (-8) → EdDSA
|
|
52
|
+
*
|
|
53
|
+
* Fast reject: `detect()` bails unless the file carries a distinctive WebAuthn
|
|
54
|
+
* API token (`pubKeyCredParams`, `PublicKeyCredential`, `webauthn`,
|
|
55
|
+
* `COSEAlgorithm`, `supportedAlgorithmIDs`, `navigator.credentials`). Those
|
|
56
|
+
* tokens are the marker gate ONLY — they never produce a standalone finding.
|
|
57
|
+
*/
|
|
58
|
+
import type { Detector } from "../types.js";
|
|
59
|
+
/** Detects classical COSE signature algorithms pinned by WebAuthn/FIDO2 relying parties. */
|
|
60
|
+
export declare const webauthnDetector: Detector;
|
|
61
|
+
//# sourceMappingURL=webauthn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn.d.ts","sourceRoot":"","sources":["../../src/detectors/webauthn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAwH/D,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,EAAE,QA0B9B,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskBlockComments, maskCommentLines, } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
// Classical COSE algorithm numbers, grouped by family (see module docstring).
|
|
4
|
+
const NUM_ECDSA = "7|35|36"; // ES256, ES384, ES512
|
|
5
|
+
const NUM_RSA = "257|258|259"; // RS256, RS384, RS512
|
|
6
|
+
const NUM_EDDSA = "8"; // EdDSA
|
|
7
|
+
// --- NUMERIC form: a COSE `alg` value written as an integer. ---
|
|
8
|
+
// (a) object member: `alg: -7`, `alg = -257` (JS/JSON, Python `alg=-7`).
|
|
9
|
+
// `["']?alg["']?` tolerates the JSON/serialized form `"alg": -7` (the WebAuthn
|
|
10
|
+
// options object is JSON-serializable and often stored/transmitted/tested as JSON
|
|
11
|
+
// with a quoted key), as well as the unquoted `alg: -7` / `alg = -7` (audit M2).
|
|
12
|
+
const RE_NUM_ECDSA = new RegExp(`["']?\\balg\\b["']?\\s*[:=]\\s*-(?:${NUM_ECDSA})\\b`, "g");
|
|
13
|
+
const RE_NUM_RSA = new RegExp(`["']?\\balg\\b["']?\\s*[:=]\\s*-(?:${NUM_RSA})\\b`, "g");
|
|
14
|
+
const RE_NUM_EDDSA = new RegExp(`["']?\\balg\\b["']?\\s*[:=]\\s*-(?:${NUM_EDDSA})\\b`, "g");
|
|
15
|
+
// (b) `supportedAlgorithmIDs: [-7, -257]` array (@simplewebauthn verify option).
|
|
16
|
+
// `[^\]]*` stays inside a single array literal; one match per family even when
|
|
17
|
+
// the array lists several ids of that family.
|
|
18
|
+
const RE_IDS_ECDSA = new RegExp(`supportedAlgorithmIDs\\s*[:=]\\s*\\[[^\\]]*-(?:${NUM_ECDSA})\\b`, "g");
|
|
19
|
+
const RE_IDS_RSA = new RegExp(`supportedAlgorithmIDs\\s*[:=]\\s*\\[[^\\]]*-(?:${NUM_RSA})\\b`, "g");
|
|
20
|
+
const RE_IDS_EDDSA = new RegExp(`supportedAlgorithmIDs\\s*[:=]\\s*\\[[^\\]]*-(?:${NUM_EDDSA})\\b`, "g");
|
|
21
|
+
// --- ENUM form: the COSE algorithm's language-level identifier. ---
|
|
22
|
+
// webauthn4j / py_webauthn: `COSEAlgorithmIdentifier.<name>`; go-webauthn:
|
|
23
|
+
// `webauthncose.Alg<name>` (matched bare as `Alg<name>`).
|
|
24
|
+
const RE_ENUM_ECDSA = /\bCOSEAlgorithmIdentifier\.(?:ES(?:256|384|512)|ECDSA_SHA_(?:256|384|512))\b|\bAlgES(?:256|384|512)\b/g;
|
|
25
|
+
const RE_ENUM_RSA = /\bCOSEAlgorithmIdentifier\.(?:RS(?:256|384|512)|RSASSA_[A-Za-z0-9_]+)\b|\bAlgRS(?:256|384|512)\b/g;
|
|
26
|
+
const RE_ENUM_EDDSA = /\bCOSEAlgorithmIdentifier\.(?:EdDSA|EDDSA)\b|\bAlgEdDSA\b/g;
|
|
27
|
+
const REMEDIATION = "WebAuthn/FIDO2 is standardizing PQC COSE algorithms; inventory the classical attestation/assertion algs now and plan migration.";
|
|
28
|
+
const RULE_WEBAUTHN_ECDSA = {
|
|
29
|
+
id: "webauthn-ecdsa",
|
|
30
|
+
title: "WebAuthn ECDSA COSE algorithm",
|
|
31
|
+
description: "WebAuthn/FIDO2 credential pins a classical ECDSA COSE algorithm (ES256/ES384/ES512)",
|
|
32
|
+
category: "signature",
|
|
33
|
+
severity: "medium",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
algorithm: "ECDSA",
|
|
36
|
+
hndl: false,
|
|
37
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
38
|
+
message: "WebAuthn/FIDO2 relying party accepts a classical ECDSA COSE algorithm (ES256/ES384/ES512); attestation/assertion signatures become forgeable once a CRQC exists.",
|
|
39
|
+
remediation: REMEDIATION,
|
|
40
|
+
};
|
|
41
|
+
const RULE_WEBAUTHN_RSA = {
|
|
42
|
+
id: "webauthn-rsa",
|
|
43
|
+
title: "WebAuthn RSA COSE algorithm",
|
|
44
|
+
description: "WebAuthn/FIDO2 credential pins a classical RSA COSE algorithm (RS256/RS384/RS512)",
|
|
45
|
+
category: "signature",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
confidence: "high",
|
|
48
|
+
algorithm: "RSA",
|
|
49
|
+
hndl: false,
|
|
50
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
51
|
+
message: "WebAuthn/FIDO2 relying party accepts a classical RSA COSE algorithm (RS256/RS384/RS512); attestation/assertion signatures become forgeable once a CRQC exists.",
|
|
52
|
+
remediation: REMEDIATION,
|
|
53
|
+
};
|
|
54
|
+
const RULE_WEBAUTHN_EDDSA = {
|
|
55
|
+
id: "webauthn-eddsa",
|
|
56
|
+
title: "WebAuthn EdDSA COSE algorithm",
|
|
57
|
+
description: "WebAuthn/FIDO2 credential pins the classical EdDSA COSE algorithm (COSE -8)",
|
|
58
|
+
category: "signature",
|
|
59
|
+
severity: "medium",
|
|
60
|
+
confidence: "high",
|
|
61
|
+
algorithm: "EdDSA",
|
|
62
|
+
hndl: false,
|
|
63
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
64
|
+
message: "WebAuthn/FIDO2 relying party accepts the classical EdDSA COSE algorithm (Ed25519); modern but still classical — attestation/assertion signatures become forgeable once a CRQC exists.",
|
|
65
|
+
remediation: REMEDIATION,
|
|
66
|
+
};
|
|
67
|
+
const WEBAUTHN_RULES = [
|
|
68
|
+
{ meta: RULE_WEBAUTHN_ECDSA, res: [RE_NUM_ECDSA, RE_IDS_ECDSA, RE_ENUM_ECDSA] },
|
|
69
|
+
{ meta: RULE_WEBAUTHN_RSA, res: [RE_NUM_RSA, RE_IDS_RSA, RE_ENUM_RSA] },
|
|
70
|
+
{ meta: RULE_WEBAUTHN_EDDSA, res: [RE_NUM_EDDSA, RE_IDS_EDDSA, RE_ENUM_EDDSA] },
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* True when `content` carries a distinctive WebAuthn API token. Used ONLY as
|
|
74
|
+
* the fast-reject marker gate — none of these tokens produce a finding — so the
|
|
75
|
+
* generic numeric `alg: -7` form can't fire on unrelated code.
|
|
76
|
+
*/
|
|
77
|
+
function hasWebauthnMarker(content) {
|
|
78
|
+
return (content.includes("pubKeyCredParams") ||
|
|
79
|
+
content.includes("PublicKeyCredential") ||
|
|
80
|
+
content.includes("COSEAlgorithm") ||
|
|
81
|
+
content.includes("supportedAlgorithmIDs") ||
|
|
82
|
+
content.includes("navigator.credentials") ||
|
|
83
|
+
/webauthn/i.test(content));
|
|
84
|
+
}
|
|
85
|
+
/** Detects classical COSE signature algorithms pinned by WebAuthn/FIDO2 relying parties. */
|
|
86
|
+
export const webauthnDetector = {
|
|
87
|
+
id: "webauthn-crypto",
|
|
88
|
+
description: "Classical COSE signature algorithms in WebAuthn/FIDO2/passkey relying-party code",
|
|
89
|
+
scope: "config",
|
|
90
|
+
language: "any",
|
|
91
|
+
rules: WEBAUTHN_RULES.map((r) => r.meta),
|
|
92
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
93
|
+
detect({ file, content }) {
|
|
94
|
+
if (!hasWebauthnMarker(content))
|
|
95
|
+
return [];
|
|
96
|
+
// Mask C-style (`//`, `/* */`) and hash (`#`) comments so a commented-out
|
|
97
|
+
// options object can't fire. Offsets are preserved so finding locations stay
|
|
98
|
+
// exact.
|
|
99
|
+
const scan = maskCommentLines(maskBlockComments(content), ["//", "#"]);
|
|
100
|
+
const findings = [];
|
|
101
|
+
for (const { meta, res } of WEBAUTHN_RULES) {
|
|
102
|
+
for (const re of res) {
|
|
103
|
+
eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return findings;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=webauthn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn.js","sourceRoot":"","sources":["../../src/detectors/webauthn.ts"],"names":[],"mappings":"AA0DA,OAAO,EACL,cAAc,EACd,SAAS,EACT,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,8EAA8E;AAC9E,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,sBAAsB;AACnD,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,sBAAsB;AACrD,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,QAAQ;AAE/B,kEAAkE;AAClE,yEAAyE;AACzE,+EAA+E;AAC/E,kFAAkF;AAClF,iFAAiF;AACjF,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,sCAAsC,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5F,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,sCAAsC,OAAO,MAAM,EAAE,GAAG,CAAC,CAAC;AACxF,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,sCAAsC,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5F,iFAAiF;AACjF,+EAA+E;AAC/E,8CAA8C;AAC9C,MAAM,YAAY,GAAG,IAAI,MAAM,CAC7B,kDAAkD,SAAS,MAAM,EACjE,GAAG,CACJ,CAAC;AACF,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,kDAAkD,OAAO,MAAM,EAAE,GAAG,CAAC,CAAC;AACpG,MAAM,YAAY,GAAG,IAAI,MAAM,CAC7B,kDAAkD,SAAS,MAAM,EACjE,GAAG,CACJ,CAAC;AAEF,qEAAqE;AACrE,2EAA2E;AAC3E,0DAA0D;AAC1D,MAAM,aAAa,GACjB,wGAAwG,CAAC;AAC3G,MAAM,WAAW,GACf,mGAAmG,CAAC;AACtG,MAAM,aAAa,GAAG,4DAA4D,CAAC;AAEnF,MAAM,WAAW,GACf,iIAAiI,CAAC;AAEpI,MAAM,mBAAmB,GAAa;IACpC,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,+BAA+B;IACtC,WAAW,EACT,qFAAqF;IACvF,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,kKAAkK;IACpK,WAAW,EAAE,WAAW;CACzB,CAAC;AACF,MAAM,iBAAiB,GAAa;IAClC,EAAE,EAAE,cAAc;IAClB,KAAK,EAAE,6BAA6B;IACpC,WAAW,EAAE,mFAAmF;IAChG,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,gKAAgK;IAClK,WAAW,EAAE,WAAW;CACzB,CAAC;AACF,MAAM,mBAAmB,GAAa;IACpC,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,+BAA+B;IACtC,WAAW,EAAE,6EAA6E;IAC1F,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,uLAAuL;IACzL,WAAW,EAAE,WAAW;CACzB,CAAC;AAOF,MAAM,cAAc,GAA4B;IAC9C,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE;IAC/E,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE;IACvE,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE;CAChF,CAAC;AAEF;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,OAAe;IACxC,OAAO,CACL,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACpC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;QACjC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACzC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACzC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAC1B,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,gBAAgB,GAAa;IACxC,EAAE,EAAE,iBAAiB;IACrB,WAAW,EAAE,kFAAkF;IAC/F,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC;IAClD,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACtB,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAE3C,0EAA0E;QAC1E,6EAA6E;QAC7E,SAAS;QACT,MAAM,IAAI,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,cAAc,EAAE,CAAC;YAC3C,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CACxB,QAAQ,CAAC,IAAI,CACX,eAAe,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CACnF,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC","sourcesContent":["/**\n * Config detector: classical asymmetric signature algorithms pinned by\n * WebAuthn / FIDO2 / passkey attestation & assertion, expressed as COSE\n * algorithm identifiers in relying-party code and config\n * (@simplewebauthn/server, py_webauthn, go-webauthn, webauthn4j).\n *\n * WHY WebAuthn is PQC-relevant: a WebAuthn credential is an asymmetric key\n * pair whose PUBLIC key the authenticator returns (COSE-encoded) at\n * registration; every subsequent assertion is a classical signature over the\n * authentication challenge, and attestation statements are classical\n * signatures over the credential. These are AUTHENTICATION signatures, not\n * confidentiality — there is nothing to \"harvest now\" — but the moment a\n * cryptographically-relevant quantum computer (CRQC) exists, an attacker who\n * has a user's registered public key (or an attestation CA's key) can FORGE\n * assertions/attestations and impersonate the user or a genuine authenticator.\n * So every finding here is `category: \"signature\"`, `hndl: false`.\n *\n * The relying party pins which algorithms it will accept: `pubKeyCredParams`\n * (registration) and `supportedAlgorithmIDs` (verification) enumerate COSE\n * `alg` values, and the classical ones — ES256 (COSE -7, ECDSA P-256),\n * RS256 (-257, RSA), EdDSA (-8) and their P-384/P-521/RSA-2048+ siblings — are\n * exactly the ones a PQC migration must inventory and replace once FIDO/COSE\n * standardise post-quantum algorithm identifiers.\n *\n * WHY numeric COSE ids need their own detector: the existing JWT/JOSE rule\n * only matches QUOTED alg strings (`\"ES256\"`, `\"RS256\"`). WebAuthn libraries\n * almost never write those strings — they use the NUMERIC COSE ids\n * (`alg: -7`, `supportedAlgorithmIDs: [-7, -257]`) and language-level enum\n * identifiers (`COSEAlgorithmIdentifier.ES256`, `webauthncose.AlgES256`,\n * `COSEAlgorithmIdentifier.EDDSA`). None of those carry the quote characters\n * the JWT rule keys off, so the entire WebAuthn surface is invisible today.\n *\n * Two independent match shapes per family, both gated by a file-level WebAuthn\n * marker (see `hasWebauthnMarker`) so the generic numeric `alg: -7` form can't\n * fire on unrelated code that happens to assign a small negative number:\n *\n * 1. NUMERIC form — the COSE `alg` value as an integer, either as an object\n * member (`alg: -7`, `alg = -257`) or inside a `supportedAlgorithmIDs`\n * array (`supportedAlgorithmIDs: [-7, -257]`). Restricted to the classical\n * COSE numbers only; symmetric / KDF / \"direct\" ids (e.g. -6, HS*) are\n * never in the alternation, so they cannot match.\n * 2. ENUM form — the COSE algorithm's identifier as written in each library:\n * `COSEAlgorithmIdentifier.ES256|RS256|EdDSA|ECDSA_SHA_256` (webauthn4j,\n * py_webauthn) and Go's `webauthncose.AlgES256` / `AlgRS256` / `AlgEdDSA`\n * constants.\n *\n * COSE algorithm numbers → {@link AlgorithmFamily} (per the COSE registry, using\n * the mapping this detector is specified against):\n * - ES256 (-7), ES384 (-35), ES512 (-36) → ECDSA\n * - RS256 (-257), RS384 (-258), RS512 (-259) → RSA\n * - EdDSA (-8) → EdDSA\n *\n * Fast reject: `detect()` bails unless the file carries a distinctive WebAuthn\n * API token (`pubKeyCredParams`, `PublicKeyCredential`, `webauthn`,\n * `COSEAlgorithm`, `supportedAlgorithmIDs`, `navigator.credentials`). Those\n * tokens are the marker gate ONLY — they never produce a standalone finding.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskBlockComments,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// Classical COSE algorithm numbers, grouped by family (see module docstring).\nconst NUM_ECDSA = \"7|35|36\"; // ES256, ES384, ES512\nconst NUM_RSA = \"257|258|259\"; // RS256, RS384, RS512\nconst NUM_EDDSA = \"8\"; // EdDSA\n\n// --- NUMERIC form: a COSE `alg` value written as an integer. ---\n// (a) object member: `alg: -7`, `alg = -257` (JS/JSON, Python `alg=-7`).\n// `[\"']?alg[\"']?` tolerates the JSON/serialized form `\"alg\": -7` (the WebAuthn\n// options object is JSON-serializable and often stored/transmitted/tested as JSON\n// with a quoted key), as well as the unquoted `alg: -7` / `alg = -7` (audit M2).\nconst RE_NUM_ECDSA = new RegExp(`[\"']?\\\\balg\\\\b[\"']?\\\\s*[:=]\\\\s*-(?:${NUM_ECDSA})\\\\b`, \"g\");\nconst RE_NUM_RSA = new RegExp(`[\"']?\\\\balg\\\\b[\"']?\\\\s*[:=]\\\\s*-(?:${NUM_RSA})\\\\b`, \"g\");\nconst RE_NUM_EDDSA = new RegExp(`[\"']?\\\\balg\\\\b[\"']?\\\\s*[:=]\\\\s*-(?:${NUM_EDDSA})\\\\b`, \"g\");\n// (b) `supportedAlgorithmIDs: [-7, -257]` array (@simplewebauthn verify option).\n// `[^\\]]*` stays inside a single array literal; one match per family even when\n// the array lists several ids of that family.\nconst RE_IDS_ECDSA = new RegExp(\n `supportedAlgorithmIDs\\\\s*[:=]\\\\s*\\\\[[^\\\\]]*-(?:${NUM_ECDSA})\\\\b`,\n \"g\",\n);\nconst RE_IDS_RSA = new RegExp(`supportedAlgorithmIDs\\\\s*[:=]\\\\s*\\\\[[^\\\\]]*-(?:${NUM_RSA})\\\\b`, \"g\");\nconst RE_IDS_EDDSA = new RegExp(\n `supportedAlgorithmIDs\\\\s*[:=]\\\\s*\\\\[[^\\\\]]*-(?:${NUM_EDDSA})\\\\b`,\n \"g\",\n);\n\n// --- ENUM form: the COSE algorithm's language-level identifier. ---\n// webauthn4j / py_webauthn: `COSEAlgorithmIdentifier.<name>`; go-webauthn:\n// `webauthncose.Alg<name>` (matched bare as `Alg<name>`).\nconst RE_ENUM_ECDSA =\n /\\bCOSEAlgorithmIdentifier\\.(?:ES(?:256|384|512)|ECDSA_SHA_(?:256|384|512))\\b|\\bAlgES(?:256|384|512)\\b/g;\nconst RE_ENUM_RSA =\n /\\bCOSEAlgorithmIdentifier\\.(?:RS(?:256|384|512)|RSASSA_[A-Za-z0-9_]+)\\b|\\bAlgRS(?:256|384|512)\\b/g;\nconst RE_ENUM_EDDSA = /\\bCOSEAlgorithmIdentifier\\.(?:EdDSA|EDDSA)\\b|\\bAlgEdDSA\\b/g;\n\nconst REMEDIATION =\n \"WebAuthn/FIDO2 is standardizing PQC COSE algorithms; inventory the classical attestation/assertion algs now and plan migration.\";\n\nconst RULE_WEBAUTHN_ECDSA: RuleMeta = {\n id: \"webauthn-ecdsa\",\n title: \"WebAuthn ECDSA COSE algorithm\",\n description:\n \"WebAuthn/FIDO2 credential pins a classical ECDSA COSE algorithm (ES256/ES384/ES512)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"WebAuthn/FIDO2 relying party accepts a classical ECDSA COSE algorithm (ES256/ES384/ES512); attestation/assertion signatures become forgeable once a CRQC exists.\",\n remediation: REMEDIATION,\n};\nconst RULE_WEBAUTHN_RSA: RuleMeta = {\n id: \"webauthn-rsa\",\n title: \"WebAuthn RSA COSE algorithm\",\n description: \"WebAuthn/FIDO2 credential pins a classical RSA COSE algorithm (RS256/RS384/RS512)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"WebAuthn/FIDO2 relying party accepts a classical RSA COSE algorithm (RS256/RS384/RS512); attestation/assertion signatures become forgeable once a CRQC exists.\",\n remediation: REMEDIATION,\n};\nconst RULE_WEBAUTHN_EDDSA: RuleMeta = {\n id: \"webauthn-eddsa\",\n title: \"WebAuthn EdDSA COSE algorithm\",\n description: \"WebAuthn/FIDO2 credential pins the classical EdDSA COSE algorithm (COSE -8)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"WebAuthn/FIDO2 relying party accepts the classical EdDSA COSE algorithm (Ed25519); modern but still classical — attestation/assertion signatures become forgeable once a CRQC exists.\",\n remediation: REMEDIATION,\n};\n\ninterface WebauthnRule {\n meta: RuleMeta;\n res: readonly RegExp[];\n}\n\nconst WEBAUTHN_RULES: readonly WebauthnRule[] = [\n { meta: RULE_WEBAUTHN_ECDSA, res: [RE_NUM_ECDSA, RE_IDS_ECDSA, RE_ENUM_ECDSA] },\n { meta: RULE_WEBAUTHN_RSA, res: [RE_NUM_RSA, RE_IDS_RSA, RE_ENUM_RSA] },\n { meta: RULE_WEBAUTHN_EDDSA, res: [RE_NUM_EDDSA, RE_IDS_EDDSA, RE_ENUM_EDDSA] },\n];\n\n/**\n * True when `content` carries a distinctive WebAuthn API token. Used ONLY as\n * the fast-reject marker gate — none of these tokens produce a finding — so the\n * generic numeric `alg: -7` form can't fire on unrelated code.\n */\nfunction hasWebauthnMarker(content: string): boolean {\n return (\n content.includes(\"pubKeyCredParams\") ||\n content.includes(\"PublicKeyCredential\") ||\n content.includes(\"COSEAlgorithm\") ||\n content.includes(\"supportedAlgorithmIDs\") ||\n content.includes(\"navigator.credentials\") ||\n /webauthn/i.test(content)\n );\n}\n\n/** Detects classical COSE signature algorithms pinned by WebAuthn/FIDO2 relying parties. */\nexport const webauthnDetector: Detector = {\n id: \"webauthn-crypto\",\n description: \"Classical COSE signature algorithms in WebAuthn/FIDO2/passkey relying-party code\",\n scope: \"config\",\n language: \"any\",\n rules: WEBAUTHN_RULES.map((r) => r.meta),\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n if (!hasWebauthnMarker(content)) return [];\n\n // Mask C-style (`//`, `/* */`) and hash (`#`) comments so a commented-out\n // options object can't fire. Offsets are preserved so finding locations stay\n // exact.\n const scan = maskCommentLines(maskBlockComments(content), [\"//\", \"#\"]);\n const findings: Finding[] = [];\n for (const { meta, res } of WEBAUTHN_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"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical crypto in XML Digital Signature / XML Encryption
|
|
3
|
+
* (W3C XML-DSig / XML-Enc), the algorithm layer under SAML SSO, WS-Security /
|
|
4
|
+
* SOAP, and signed XML documents. Enterprise identity is ubiquitous and its IdP
|
|
5
|
+
* signing keys are long-lived, so a forged SAML assertion is a prime quantum
|
|
6
|
+
* forgery surface; an encrypted SAML assertion (RSA key transport) is
|
|
7
|
+
* harvest-now-decrypt-later exposed.
|
|
8
|
+
*
|
|
9
|
+
* Detection keys off the W3C algorithm URIs, which are globally-unique constant
|
|
10
|
+
* strings (RFC-precise, so a lexical match is exact):
|
|
11
|
+
* - `…xmldsig#rsa-sha1` / `…xmldsig-more#rsa-sha256` … → RSA signature (forgeable)
|
|
12
|
+
* - `…xmldsig11#dsa-sha256` / `…xmldsig#dsa-sha1` → DSA signature
|
|
13
|
+
* - `…xmldsig-more#ecdsa-sha256` … → ECDSA signature
|
|
14
|
+
* - `…xmlenc#rsa-oaep(-mgf1p)` / `…xmlenc#rsa-1_5` → RSA key transport (HNDL)
|
|
15
|
+
*
|
|
16
|
+
* These URIs appear both in XML config (`.xml`) and as string constants in SAML
|
|
17
|
+
* library code (python3-saml, xml-crypto, OpenSAML), so the detector runs on any
|
|
18
|
+
* non-doc file.
|
|
19
|
+
*/
|
|
20
|
+
import type { Detector } from "../types.js";
|
|
21
|
+
/** Detects classical XML-DSig / XML-Enc algorithms (SAML, WS-Security, signed XML). */
|
|
22
|
+
export declare const xmldsigDetector: Detector;
|
|
23
|
+
//# sourceMappingURL=xmldsig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xmldsig.d.ts","sourceRoot":"","sources":["../../src/detectors/xmldsig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAsE/D,uFAAuF;AACvF,eAAO,MAAM,eAAe,EAAE,QAwB7B,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
// The `#fragment` of the W3C XML-DSig / XML-Enc algorithm URIs. Anchored on the
|
|
4
|
+
// namespace fragment (`xmldsig…#` / `xmlenc#`) so a bare `rsa-sha256` elsewhere
|
|
5
|
+
// doesn't misfire.
|
|
6
|
+
const RE_XMLDSIG_RSA = /\bxmldsig(?:-more|11)?#rsa-sha(?:1|224|256|384|512)\b/g;
|
|
7
|
+
const RE_XMLDSIG_DSA = /\bxmldsig(?:11)?#dsa-sha(?:1|256)\b/g;
|
|
8
|
+
const RE_XMLDSIG_ECDSA = /\bxmldsig-more#ecdsa-sha(?:1|224|256|384|512)\b/g;
|
|
9
|
+
const RE_XMLENC_RSA = /\bxmlenc#rsa-(?:oaep(?:-mgf1p)?|1_5)\b/g;
|
|
10
|
+
const RULE_XMLDSIG_RSA = {
|
|
11
|
+
id: "xmldsig-rsa-sign",
|
|
12
|
+
title: "XML-DSig RSA signature",
|
|
13
|
+
description: "XML Digital Signature with an RSA-SHA* algorithm (SAML/WS-Security)",
|
|
14
|
+
category: "signature",
|
|
15
|
+
severity: "medium",
|
|
16
|
+
confidence: "high",
|
|
17
|
+
algorithm: "RSA",
|
|
18
|
+
hndl: false,
|
|
19
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
20
|
+
message: "XML signatures (SAML / WS-Security) use classical RSA (rsa-sha*); a quantum attacker could forge assertions signed with this key.",
|
|
21
|
+
remediation: "Track PQC XML-DSig / SAML profiles (ML-DSA); rotate to a PQC signing key as tooling and IdPs add support.",
|
|
22
|
+
};
|
|
23
|
+
const RULE_XMLDSIG_DSA = {
|
|
24
|
+
id: "xmldsig-dsa-sign",
|
|
25
|
+
title: "XML-DSig DSA signature",
|
|
26
|
+
description: "XML Digital Signature with a DSA-SHA* algorithm",
|
|
27
|
+
category: "signature",
|
|
28
|
+
severity: "medium",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
algorithm: "DSA",
|
|
31
|
+
hndl: false,
|
|
32
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
33
|
+
message: "XML signatures use classical DSA (dsa-sha*) — deprecated and quantum-forgeable.",
|
|
34
|
+
remediation: "Migrate off DSA now; track PQC XML-DSig profiles (ML-DSA).",
|
|
35
|
+
};
|
|
36
|
+
const RULE_XMLDSIG_ECDSA = {
|
|
37
|
+
id: "xmldsig-ecdsa-sign",
|
|
38
|
+
title: "XML-DSig ECDSA signature",
|
|
39
|
+
description: "XML Digital Signature with an ECDSA-SHA* algorithm",
|
|
40
|
+
category: "signature",
|
|
41
|
+
severity: "medium",
|
|
42
|
+
confidence: "high",
|
|
43
|
+
algorithm: "ECDSA",
|
|
44
|
+
hndl: false,
|
|
45
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
46
|
+
message: "XML signatures use classical ECDSA (ecdsa-sha*); forgeable by a quantum attacker.",
|
|
47
|
+
remediation: "Track PQC XML-DSig / SAML profiles (ML-DSA); rotate the signing key when supported.",
|
|
48
|
+
};
|
|
49
|
+
const RULE_XMLENC_RSA = {
|
|
50
|
+
id: "xmlenc-rsa-keytransport",
|
|
51
|
+
title: "XML-Enc RSA key transport",
|
|
52
|
+
description: "XML Encryption with RSA-OAEP / RSA-1_5 key transport (encrypted SAML assertions)",
|
|
53
|
+
category: "kem",
|
|
54
|
+
severity: "high",
|
|
55
|
+
confidence: "high",
|
|
56
|
+
algorithm: "RSA",
|
|
57
|
+
hndl: true,
|
|
58
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
59
|
+
message: "XML Encryption wraps the content key with classical RSA (rsa-oaep / rsa-1_5); encrypted XML (e.g. SAML assertions) is harvest-now-decrypt-later exposed.",
|
|
60
|
+
remediation: "Plan migration to a post-quantum KEM (ML-KEM-768) for key transport as XML-Enc / SAML PQC profiles mature.",
|
|
61
|
+
};
|
|
62
|
+
/** Detects classical XML-DSig / XML-Enc algorithms (SAML, WS-Security, signed XML). */
|
|
63
|
+
export const xmldsigDetector = {
|
|
64
|
+
id: "xmldsig-crypto",
|
|
65
|
+
description: "Classical XML-DSig / XML-Enc algorithms (SAML, WS-Security, signed XML)",
|
|
66
|
+
scope: "config",
|
|
67
|
+
language: "any",
|
|
68
|
+
// Skip prose/docs: a page explaining the SAML algorithm URIs is not live config.
|
|
69
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
70
|
+
rules: [RULE_XMLDSIG_RSA, RULE_XMLDSIG_DSA, RULE_XMLDSIG_ECDSA, RULE_XMLENC_RSA],
|
|
71
|
+
detect({ file, content }) {
|
|
72
|
+
// Fast reject: only proceed if an XML-DSig / XML-Enc namespace fragment is present.
|
|
73
|
+
if (!content.includes("xmldsig") && !content.includes("xmlenc"))
|
|
74
|
+
return [];
|
|
75
|
+
const findings = [];
|
|
76
|
+
const add = (re, rule) => eachMatch(re, content, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
77
|
+
add(RE_XMLDSIG_RSA, RULE_XMLDSIG_RSA);
|
|
78
|
+
add(RE_XMLDSIG_DSA, RULE_XMLDSIG_DSA);
|
|
79
|
+
add(RE_XMLDSIG_ECDSA, RULE_XMLDSIG_ECDSA);
|
|
80
|
+
add(RE_XMLENC_RSA, RULE_XMLENC_RSA);
|
|
81
|
+
return findings;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=xmldsig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xmldsig.js","sourceRoot":"","sources":["../../src/detectors/xmldsig.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical crypto in XML Digital Signature / XML Encryption\n * (W3C XML-DSig / XML-Enc), the algorithm layer under SAML SSO, WS-Security /\n * SOAP, and signed XML documents. Enterprise identity is ubiquitous and its IdP\n * signing keys are long-lived, so a forged SAML assertion is a prime quantum\n * forgery surface; an encrypted SAML assertion (RSA key transport) is\n * harvest-now-decrypt-later exposed.\n *\n * Detection keys off the W3C algorithm URIs, which are globally-unique constant\n * strings (RFC-precise, so a lexical match is exact):\n * - `…xmldsig#rsa-sha1` / `…xmldsig-more#rsa-sha256` … → RSA signature (forgeable)\n * - `…xmldsig11#dsa-sha256` / `…xmldsig#dsa-sha1` → DSA signature\n * - `…xmldsig-more#ecdsa-sha256` … → ECDSA signature\n * - `…xmlenc#rsa-oaep(-mgf1p)` / `…xmlenc#rsa-1_5` → RSA key transport (HNDL)\n *\n * These URIs appear both in XML config (`.xml`) and as string constants in SAML\n * library code (python3-saml, xml-crypto, OpenSAML), so the detector runs on any\n * non-doc file.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// The `#fragment` of the W3C XML-DSig / XML-Enc algorithm URIs. Anchored on the\n// namespace fragment (`xmldsig…#` / `xmlenc#`) so a bare `rsa-sha256` elsewhere\n// doesn't misfire.\nconst RE_XMLDSIG_RSA = /\\bxmldsig(?:-more|11)?#rsa-sha(?:1|224|256|384|512)\\b/g;\nconst RE_XMLDSIG_DSA = /\\bxmldsig(?:11)?#dsa-sha(?:1|256)\\b/g;\nconst RE_XMLDSIG_ECDSA = /\\bxmldsig-more#ecdsa-sha(?:1|224|256|384|512)\\b/g;\nconst RE_XMLENC_RSA = /\\bxmlenc#rsa-(?:oaep(?:-mgf1p)?|1_5)\\b/g;\n\nconst RULE_XMLDSIG_RSA: RuleMeta = {\n id: \"xmldsig-rsa-sign\",\n title: \"XML-DSig RSA signature\",\n description: \"XML Digital Signature with an RSA-SHA* algorithm (SAML/WS-Security)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"XML signatures (SAML / WS-Security) use classical RSA (rsa-sha*); a quantum attacker could forge assertions signed with this key.\",\n remediation:\n \"Track PQC XML-DSig / SAML profiles (ML-DSA); rotate to a PQC signing key as tooling and IdPs add support.\",\n};\nconst RULE_XMLDSIG_DSA: RuleMeta = {\n id: \"xmldsig-dsa-sign\",\n title: \"XML-DSig DSA signature\",\n description: \"XML Digital Signature with a DSA-SHA* algorithm\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"XML signatures use classical DSA (dsa-sha*) — deprecated and quantum-forgeable.\",\n remediation: \"Migrate off DSA now; track PQC XML-DSig profiles (ML-DSA).\",\n};\nconst RULE_XMLDSIG_ECDSA: RuleMeta = {\n id: \"xmldsig-ecdsa-sign\",\n title: \"XML-DSig ECDSA signature\",\n description: \"XML Digital Signature with an ECDSA-SHA* algorithm\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"XML signatures use classical ECDSA (ecdsa-sha*); forgeable by a quantum attacker.\",\n remediation:\n \"Track PQC XML-DSig / SAML profiles (ML-DSA); rotate the signing key when supported.\",\n};\nconst RULE_XMLENC_RSA: RuleMeta = {\n id: \"xmlenc-rsa-keytransport\",\n title: \"XML-Enc RSA key transport\",\n description: \"XML Encryption with RSA-OAEP / RSA-1_5 key transport (encrypted SAML assertions)\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"XML Encryption wraps the content key with classical RSA (rsa-oaep / rsa-1_5); encrypted XML (e.g. SAML assertions) is harvest-now-decrypt-later exposed.\",\n remediation:\n \"Plan migration to a post-quantum KEM (ML-KEM-768) for key transport as XML-Enc / SAML PQC profiles mature.\",\n};\n\n/** Detects classical XML-DSig / XML-Enc algorithms (SAML, WS-Security, signed XML). */\nexport const xmldsigDetector: Detector = {\n id: \"xmldsig-crypto\",\n description: \"Classical XML-DSig / XML-Enc algorithms (SAML, WS-Security, signed XML)\",\n scope: \"config\",\n language: \"any\",\n // Skip prose/docs: a page explaining the SAML algorithm URIs is not live config.\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n rules: [RULE_XMLDSIG_RSA, RULE_XMLDSIG_DSA, RULE_XMLDSIG_ECDSA, RULE_XMLENC_RSA],\n detect({ file, content }): Finding[] {\n // Fast reject: only proceed if an XML-DSig / XML-Enc namespace fragment is present.\n if (!content.includes(\"xmldsig\") && !content.includes(\"xmlenc\")) return [];\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta): void =>\n eachMatch(re, content, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n add(RE_XMLDSIG_RSA, RULE_XMLDSIG_RSA);\n add(RE_XMLDSIG_DSA, RULE_XMLDSIG_DSA);\n add(RE_XMLDSIG_ECDSA, RULE_XMLDSIG_ECDSA);\n add(RE_XMLENC_RSA, RULE_XMLENC_RSA);\n return findings;\n },\n};\n"]}
|
package/dist/evidence.d.ts
CHANGED
|
@@ -30,10 +30,21 @@ export interface ReadinessReport {
|
|
|
30
30
|
attestation: {
|
|
31
31
|
/** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */
|
|
32
32
|
contentHash: string;
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
/**
|
|
34
|
+
* RFC-3161 / transparency-log token over `contentHash`, produced by an EXTERNAL
|
|
35
|
+
* timestamper (opaque string, e.g. base64). `null` until {@link signReadinessReport}
|
|
36
|
+
* runs one.
|
|
37
|
+
*/
|
|
38
|
+
timestamp: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Detached signature over `contentHash`, produced by an EXTERNAL signer (opaque
|
|
41
|
+
* string, e.g. base64/PEM). `null` until {@link signReadinessReport} runs one.
|
|
42
|
+
*/
|
|
43
|
+
signature: string | null;
|
|
44
|
+
/** Non-sensitive provenance label of the signer (e.g. "openssl", "cosign"). */
|
|
45
|
+
signedWith?: string;
|
|
46
|
+
/** Non-sensitive provenance label of the timestamper (e.g. "openssl-ts"). */
|
|
47
|
+
timestampedWith?: string;
|
|
37
48
|
};
|
|
38
49
|
}
|
|
39
50
|
export interface ReadinessReportOptions {
|
|
@@ -50,4 +61,63 @@ export interface ReadinessReportOptions {
|
|
|
50
61
|
* block itself, so re-running the same scan on the same commit is verifiable.
|
|
51
62
|
*/
|
|
52
63
|
export declare function buildReadinessReport(result: ScanResult, opts?: ReadinessReportOptions): ReadinessReport;
|
|
64
|
+
/** The result of {@link verifyReadinessReport}. */
|
|
65
|
+
export interface VerifyReadinessResult {
|
|
66
|
+
/** True iff the recomputed body hash equals the hash claimed in the attestation. */
|
|
67
|
+
valid: boolean;
|
|
68
|
+
/** The hash recomputed over the report's CURRENT body. */
|
|
69
|
+
computedHash: string;
|
|
70
|
+
/** The hash claimed in the report's attestation (`attestation.contentHash`). */
|
|
71
|
+
claimedHash: string;
|
|
72
|
+
/** A short human reason; present only when `valid` is false. */
|
|
73
|
+
reason?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Recompute the deterministic content hash over a readiness report's body and
|
|
77
|
+
* compare it to the hash the attestation claims. Detects tampering with ANY
|
|
78
|
+
* hashed field — a finding, the inventory, a policy verdict, or subject/tool
|
|
79
|
+
* metadata: editing it after the fact changes the recomputed hash, so `valid`
|
|
80
|
+
* becomes false.
|
|
81
|
+
*
|
|
82
|
+
* By construction the scan timestamp, the CBOM envelope, and the attestation
|
|
83
|
+
* block itself are EXCLUDED from the hash (see {@link buildReadinessReport}), so
|
|
84
|
+
* touching those does not fail verification — their integrity follows from their
|
|
85
|
+
* hashed inputs. The body is reconstructed from the report's OWN stored fields
|
|
86
|
+
* (including `tool.version`), so a report built by an older qScan still verifies.
|
|
87
|
+
*
|
|
88
|
+
* This checks the INTEGRITY hash only. It does NOT validate the detached
|
|
89
|
+
* signature or RFC-3161 timestamp: those are opaque tokens from an external
|
|
90
|
+
* signer (ADR-0004) and are verified with that signer's own tooling.
|
|
91
|
+
*/
|
|
92
|
+
export declare function verifyReadinessReport(report: ReadinessReport): VerifyReadinessResult;
|
|
93
|
+
/**
|
|
94
|
+
* An EXTERNAL signer/timestamper the tool orchestrates. Per ADR-0004 the tool
|
|
95
|
+
* implements no cryptography: it hands the payload to an operator-provided signer
|
|
96
|
+
* (an `openssl`/`cosign` invocation, an RFC-3161 TSA client, …) and records what
|
|
97
|
+
* comes back. `label` is a short, non-sensitive provenance string (e.g. the signer
|
|
98
|
+
* program name) — NOT the full command, which may contain a key path.
|
|
99
|
+
*/
|
|
100
|
+
export interface EvidenceSigner {
|
|
101
|
+
label: string;
|
|
102
|
+
/**
|
|
103
|
+
* Produce a detached signature / timestamp token (opaque string) over `payload`.
|
|
104
|
+
* May be async so a future signer can shell out OR call a KMS / RFC-3161 TSA over
|
|
105
|
+
* the network without foreclosing that once this contract freezes at 1.0.
|
|
106
|
+
*/
|
|
107
|
+
sign(payload: string): string | Promise<string>;
|
|
108
|
+
}
|
|
109
|
+
/** Options for {@link signReadinessReport}: a detached-signature and/or a timestamp signer. */
|
|
110
|
+
export interface SignEvidenceOptions {
|
|
111
|
+
signer?: EvidenceSigner;
|
|
112
|
+
timestamper?: EvidenceSigner;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fill a readiness report's attestation with a detached signature and/or RFC-3161
|
|
116
|
+
* timestamp, produced by EXTERNAL signers over the report's `contentHash`. Pure
|
|
117
|
+
* orchestration: it invokes the injected signers and records their opaque output
|
|
118
|
+
* plus a provenance label — it performs no cryptography itself (ADR-0004). Returns a
|
|
119
|
+
* NEW report; the hashed body is untouched (attestation is excluded from the hash),
|
|
120
|
+
* so signing never changes `contentHash`.
|
|
121
|
+
*/
|
|
122
|
+
export declare function signReadinessReport(report: ReadinessReport, opts: SignEvidenceOptions): Promise<ReadinessReport>;
|
|
53
123
|
//# sourceMappingURL=evidence.d.ts.map
|
package/dist/evidence.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,wBAAwB,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC;IACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACnC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE;QACX,+EAA+E;QAC/E,WAAW,EAAE,MAAM,CAAC;QACpB
|
|
1
|
+
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,wBAAwB,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC;IACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACnC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE;QACX,+EAA+E;QAC/E,WAAW,EAAE,MAAM,CAAC;QACpB;;;;WAIG;QACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB;;;WAGG;QACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,+EAA+E;QAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,6EAA6E;QAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAeD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE,sBAA2B,GAChC,eAAe,CA2CjB;AAED,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,qBAAqB,CA6BpF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,+FAA+F;AAC/F,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
|
package/dist/evidence.js
CHANGED
|
@@ -78,4 +78,75 @@ export function buildReadinessReport(result, opts = {}) {
|
|
|
78
78
|
attestation: { contentHash, timestamp: null, signature: null },
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Recompute the deterministic content hash over a readiness report's body and
|
|
83
|
+
* compare it to the hash the attestation claims. Detects tampering with ANY
|
|
84
|
+
* hashed field — a finding, the inventory, a policy verdict, or subject/tool
|
|
85
|
+
* metadata: editing it after the fact changes the recomputed hash, so `valid`
|
|
86
|
+
* becomes false.
|
|
87
|
+
*
|
|
88
|
+
* By construction the scan timestamp, the CBOM envelope, and the attestation
|
|
89
|
+
* block itself are EXCLUDED from the hash (see {@link buildReadinessReport}), so
|
|
90
|
+
* touching those does not fail verification — their integrity follows from their
|
|
91
|
+
* hashed inputs. The body is reconstructed from the report's OWN stored fields
|
|
92
|
+
* (including `tool.version`), so a report built by an older qScan still verifies.
|
|
93
|
+
*
|
|
94
|
+
* This checks the INTEGRITY hash only. It does NOT validate the detached
|
|
95
|
+
* signature or RFC-3161 timestamp: those are opaque tokens from an external
|
|
96
|
+
* signer (ADR-0004) and are verified with that signer's own tooling.
|
|
97
|
+
*/
|
|
98
|
+
export function verifyReadinessReport(report) {
|
|
99
|
+
const hashableBody = {
|
|
100
|
+
reportType: report.reportType,
|
|
101
|
+
specVersion: report.specVersion,
|
|
102
|
+
subject: {
|
|
103
|
+
repository: report.subject.repository,
|
|
104
|
+
commit: report.subject.commit,
|
|
105
|
+
scannedRoot: report.subject.scannedRoot,
|
|
106
|
+
},
|
|
107
|
+
tool: { name: report.tool.name, version: report.tool.version },
|
|
108
|
+
inventory: report.inventory,
|
|
109
|
+
findings: report.findings,
|
|
110
|
+
...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),
|
|
111
|
+
};
|
|
112
|
+
const computedHash = "sha256:" +
|
|
113
|
+
createHash("sha256")
|
|
114
|
+
.update(JSON.stringify(canonicalize(hashableBody)))
|
|
115
|
+
.digest("hex");
|
|
116
|
+
const claimedHash = report.attestation.contentHash;
|
|
117
|
+
if (computedHash === claimedHash) {
|
|
118
|
+
return { valid: true, computedHash, claimedHash };
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
valid: false,
|
|
122
|
+
computedHash,
|
|
123
|
+
claimedHash,
|
|
124
|
+
reason: "content-hash mismatch: the report body was modified after it was built",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Fill a readiness report's attestation with a detached signature and/or RFC-3161
|
|
129
|
+
* timestamp, produced by EXTERNAL signers over the report's `contentHash`. Pure
|
|
130
|
+
* orchestration: it invokes the injected signers and records their opaque output
|
|
131
|
+
* plus a provenance label — it performs no cryptography itself (ADR-0004). Returns a
|
|
132
|
+
* NEW report; the hashed body is untouched (attestation is excluded from the hash),
|
|
133
|
+
* so signing never changes `contentHash`.
|
|
134
|
+
*/
|
|
135
|
+
export async function signReadinessReport(report, opts) {
|
|
136
|
+
const payload = report.attestation.contentHash;
|
|
137
|
+
const signature = opts.signer ? await opts.signer.sign(payload) : report.attestation.signature;
|
|
138
|
+
const timestamp = opts.timestamper
|
|
139
|
+
? await opts.timestamper.sign(payload)
|
|
140
|
+
: report.attestation.timestamp;
|
|
141
|
+
return {
|
|
142
|
+
...report,
|
|
143
|
+
attestation: {
|
|
144
|
+
...report.attestation,
|
|
145
|
+
signature,
|
|
146
|
+
timestamp,
|
|
147
|
+
...(opts.signer ? { signedWith: opts.signer.label } : {}),
|
|
148
|
+
...(opts.timestamper ? { timestampedWith: opts.timestamper.label } : {}),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
81
152
|
//# sourceMappingURL=evidence.js.map
|
package/dist/evidence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\nimport { buildPolicyMapping } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyMapping } from \"./policy.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n /** §4 policy verdicts, present only when a crypto policy was supplied. */\n policyMapping?: PolicyMapping;\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /** RFC-3161 / transparency-log token — filled by an external signer. */\n timestamp: null;\n /** Detached signature over `contentHash` — filled by an external signer. */\n signature: null;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */\n policy?: CryptoPolicy;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.\n // Deterministic (same findings + policy → same mapping), so it is hashed.\n const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;\n\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n ...(policyMapping ? { policyMapping } : {}),\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\nimport { buildPolicyMapping } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyMapping } from \"./policy.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n /** §4 policy verdicts, present only when a crypto policy was supplied. */\n policyMapping?: PolicyMapping;\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /**\n * RFC-3161 / transparency-log token over `contentHash`, produced by an EXTERNAL\n * timestamper (opaque string, e.g. base64). `null` until {@link signReadinessReport}\n * runs one.\n */\n timestamp: string | null;\n /**\n * Detached signature over `contentHash`, produced by an EXTERNAL signer (opaque\n * string, e.g. base64/PEM). `null` until {@link signReadinessReport} runs one.\n */\n signature: string | null;\n /** Non-sensitive provenance label of the signer (e.g. \"openssl\", \"cosign\"). */\n signedWith?: string;\n /** Non-sensitive provenance label of the timestamper (e.g. \"openssl-ts\"). */\n timestampedWith?: string;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */\n policy?: CryptoPolicy;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.\n // Deterministic (same findings + policy → same mapping), so it is hashed.\n const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;\n\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n ...(policyMapping ? { policyMapping } : {}),\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n\n/** The result of {@link verifyReadinessReport}. */\nexport interface VerifyReadinessResult {\n /** True iff the recomputed body hash equals the hash claimed in the attestation. */\n valid: boolean;\n /** The hash recomputed over the report's CURRENT body. */\n computedHash: string;\n /** The hash claimed in the report's attestation (`attestation.contentHash`). */\n claimedHash: string;\n /** A short human reason; present only when `valid` is false. */\n reason?: string;\n}\n\n/**\n * Recompute the deterministic content hash over a readiness report's body and\n * compare it to the hash the attestation claims. Detects tampering with ANY\n * hashed field — a finding, the inventory, a policy verdict, or subject/tool\n * metadata: editing it after the fact changes the recomputed hash, so `valid`\n * becomes false.\n *\n * By construction the scan timestamp, the CBOM envelope, and the attestation\n * block itself are EXCLUDED from the hash (see {@link buildReadinessReport}), so\n * touching those does not fail verification — their integrity follows from their\n * hashed inputs. The body is reconstructed from the report's OWN stored fields\n * (including `tool.version`), so a report built by an older qScan still verifies.\n *\n * This checks the INTEGRITY hash only. It does NOT validate the detached\n * signature or RFC-3161 timestamp: those are opaque tokens from an external\n * signer (ADR-0004) and are verified with that signer's own tooling.\n */\nexport function verifyReadinessReport(report: ReadinessReport): VerifyReadinessResult {\n const hashableBody = {\n reportType: report.reportType,\n specVersion: report.specVersion,\n subject: {\n repository: report.subject.repository,\n commit: report.subject.commit,\n scannedRoot: report.subject.scannedRoot,\n },\n tool: { name: report.tool.name, version: report.tool.version },\n inventory: report.inventory,\n findings: report.findings,\n ...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),\n };\n const computedHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n const claimedHash = report.attestation.contentHash;\n if (computedHash === claimedHash) {\n return { valid: true, computedHash, claimedHash };\n }\n return {\n valid: false,\n computedHash,\n claimedHash,\n reason: \"content-hash mismatch: the report body was modified after it was built\",\n };\n}\n\n/**\n * An EXTERNAL signer/timestamper the tool orchestrates. Per ADR-0004 the tool\n * implements no cryptography: it hands the payload to an operator-provided signer\n * (an `openssl`/`cosign` invocation, an RFC-3161 TSA client, …) and records what\n * comes back. `label` is a short, non-sensitive provenance string (e.g. the signer\n * program name) — NOT the full command, which may contain a key path.\n */\nexport interface EvidenceSigner {\n label: string;\n /**\n * Produce a detached signature / timestamp token (opaque string) over `payload`.\n * May be async so a future signer can shell out OR call a KMS / RFC-3161 TSA over\n * the network without foreclosing that once this contract freezes at 1.0.\n */\n sign(payload: string): string | Promise<string>;\n}\n\n/** Options for {@link signReadinessReport}: a detached-signature and/or a timestamp signer. */\nexport interface SignEvidenceOptions {\n signer?: EvidenceSigner;\n timestamper?: EvidenceSigner;\n}\n\n/**\n * Fill a readiness report's attestation with a detached signature and/or RFC-3161\n * timestamp, produced by EXTERNAL signers over the report's `contentHash`. Pure\n * orchestration: it invokes the injected signers and records their opaque output\n * plus a provenance label — it performs no cryptography itself (ADR-0004). Returns a\n * NEW report; the hashed body is untouched (attestation is excluded from the hash),\n * so signing never changes `contentHash`.\n */\nexport async function signReadinessReport(\n report: ReadinessReport,\n opts: SignEvidenceOptions,\n): Promise<ReadinessReport> {\n const payload = report.attestation.contentHash;\n const signature = opts.signer ? await opts.signer.sign(payload) : report.attestation.signature;\n const timestamp = opts.timestamper\n ? await opts.timestamper.sign(payload)\n : report.attestation.timestamp;\n return {\n ...report,\n attestation: {\n ...report.attestation,\n signature,\n timestamp,\n ...(opts.signer ? { signedWith: opts.signer.label } : {}),\n ...(opts.timestamper ? { timestampedWith: opts.timestamper.label } : {}),\n },\n };\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,32 +28,35 @@ export { configToggleCodemod } from "./codemods/config-toggle.js";
|
|
|
28
28
|
export { remediateFindings } from "./remediate-pipeline.js";
|
|
29
29
|
export type { RemediateOptions, RemediationResult, VerifiedPatch, RejectedPatch, } from "./remediate-pipeline.js";
|
|
30
30
|
export { AbortError, BudgetExceededError } from "./errors.js";
|
|
31
|
-
export { scanParallel
|
|
32
|
-
export type { ScanChunk
|
|
33
|
-
export { DetectorRegistry, defaultRegistry
|
|
34
|
-
export type { RuleCatalogEntry } from "./registry.js";
|
|
31
|
+
export { scanParallel } from "./parallel.js";
|
|
32
|
+
export type { ScanChunk } from "./parallel.js";
|
|
33
|
+
export { DetectorRegistry, defaultRegistry } from "./registry.js";
|
|
35
34
|
export { fingerprintFinding, baselineFromFindings, applyBaseline, loadBaseline, saveBaseline, BASELINE_VERSION, } from "./baseline.js";
|
|
36
35
|
export type { Baseline } from "./baseline.js";
|
|
37
36
|
export { changedFiles } from "./changed.js";
|
|
38
37
|
export { loadConfig, ConfigError, CONFIG_FILENAME } from "./config.js";
|
|
39
38
|
export type { QuantakryptoFileConfig, LoadConfigResult } from "./config.js";
|
|
40
|
-
export { walkFiles, isBinaryPath,
|
|
39
|
+
export { walkFiles, isBinaryPath, looksMinified } from "./walk.js";
|
|
41
40
|
export { isAnalyzableSource, ANALYZABLE_SOURCE_EXTENSIONS, ANALYZABLE_LANGUAGES_LABEL, } from "./detect-utils.js";
|
|
42
41
|
export { buildInventory } from "./inventory.js";
|
|
43
42
|
export { vulnerableDependencies, DEP_VULNERABLE_RULE, isManifestFile } from "./dependencies.js";
|
|
44
43
|
export { SEVERITY_ORDER, severityRank, meetsThreshold, sarifLevel } from "./severity.js";
|
|
45
|
-
export { toSarif, toJson, formatSummary, formatTierGuidance } from "./report.js";
|
|
44
|
+
export { toSarif, toJson, formatSummary, formatTierGuidance, formatProfileGuidance, } from "./report.js";
|
|
46
45
|
export type { ReportOptions } from "./report.js";
|
|
47
46
|
export { toCbom } from "./cbom.js";
|
|
48
47
|
export type { CycloneDxBom, CbomComponent } from "./cbom.js";
|
|
49
48
|
export { mergeCboms } from "./cbom-merge.js";
|
|
50
|
-
export {
|
|
51
|
-
export type {
|
|
52
|
-
export {
|
|
49
|
+
export { toOpenVex } from "./vex.js";
|
|
50
|
+
export type { OpenVexDocument, OpenVexStatement, OpenVexOptions } from "./vex.js";
|
|
51
|
+
export { buildReadinessReport, signReadinessReport, verifyReadinessReport } from "./evidence.js";
|
|
52
|
+
export type { ReadinessReport, EvidenceFinding, ReadinessReportOptions, EvidenceSigner, SignEvidenceOptions, VerifyReadinessResult, } from "./evidence.js";
|
|
53
|
+
export { buildPolicyMapping, parseCryptoPolicy } from "./policy.js";
|
|
53
54
|
export type { CryptoPolicy, PolicyVerdict, PolicyMapping, PolicyFindingVerdict } from "./policy.js";
|
|
54
|
-
export { remediationFor, remediationForTier, TIER_PARAMS, STATEFUL_HBS_NOTE, PQC_TRANSITION_NOTE, statefulHbsApplies, } from "./remediation.js";
|
|
55
|
+
export { remediationFor, remediationForTier, remediationForProfile, TIER_PARAMS, STATEFUL_HBS_NOTE, PQC_TRANSITION_NOTE, statefulHbsApplies, } from "./remediation.js";
|
|
55
56
|
export type { SecurityTier } from "./remediation.js";
|
|
56
|
-
export { PQC_STANDARDS
|
|
57
|
+
export { PQC_STANDARDS } from "./standards.js";
|
|
57
58
|
export type { PqcStandards, StandardsCitation, StandardsReviewStatus } from "./standards.js";
|
|
59
|
+
export { STANDARDS_PROFILES, DEFAULT_PROFILE_ID, standardsProfileIds, getStandardsProfile, defaultStandardsProfile, } from "./standards-profiles.js";
|
|
60
|
+
export type { StandardsProfile, HybridStance } from "./standards-profiles.js";
|
|
58
61
|
export { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH, CWE_CERT_VALIDATION, CWE_HARDCODED_KEY, CWE_RISKY_PRIMITIVE, } from "./cwe.js";
|
|
59
62
|
//# sourceMappingURL=index.d.ts.map
|