keel-verifier 1.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keel API, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: keel-verifier
3
+ Version: 1.0.0
4
+ Summary: Independent verifier for Keel governance evidence
5
+ Author: Keel API, Inc.
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Keel API, Inc.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://keelapi.com/verify
29
+ Project-URL: Documentation, https://docs.keelapi.com/11-running-keel-verify
30
+ Project-URL: Repository, https://github.com/keelapi/keel-verifier
31
+ Project-URL: Changelog, https://github.com/keelapi/keel-verifier/releases
32
+ Requires-Python: >=3.10
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: cryptography>=42
36
+ Dynamic: license-file
37
+
38
+ # keel-verifier
39
+
40
+ Independent verifier for Keel governance evidence.
41
+
42
+ It runs locally, requires no access to Keel, and makes no outbound network calls unless you explicitly ask it to fetch a public key or key manifest URL.
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ python -m pip install keel-verifier
48
+ keel-verify export --help
49
+ ```
50
+
51
+ From a checkout:
52
+
53
+ ```bash
54
+ python -m pip install -e .
55
+ python -m keel_verifier --help
56
+ ```
57
+
58
+ The v0.2.0 invocation pattern still works:
59
+
60
+ ```bash
61
+ python -m keel_verifier sample/export.json --self-attested
62
+ ```
63
+
64
+ ## What It Verifies
65
+
66
+ `keel-verify export` verifies a signed compliance export in three layers:
67
+
68
+ 1. The export bytes match the signed manifest `content_hash`.
69
+ 2. The manifest Ed25519 signature verifies against a trusted key.
70
+ 3. Optional Phase C/D checks walk bundled chain entries and verify closure records.
71
+
72
+ `keel-verify checkpoint` verifies integrity checkpoint JSON artifacts: the `chain_heads` composite hash, the Ed25519 checkpoint signature, and an embedded RFC 3161 timestamp MessageImprint when present.
73
+
74
+ ## Obtaining a Signed Export
75
+
76
+ Request an audit export from Keel's compliance export API and include chain entries when you want full lifecycle walking:
77
+
78
+ ```bash
79
+ curl -sS -X POST "https://api.keelapi.com/v1/compliance/exports?include_chain_entries=true" -H "Authorization: Bearer $KEEL_API_KEY" -H "Content-Type: application/json" -d '{"project_id":"<project_uuid>","format":"json"}'
80
+ ```
81
+
82
+ Download both artifacts returned by the export workflow:
83
+
84
+ - the export payload, for example `export.json`
85
+ - the signed manifest, for example `manifest.json`
86
+
87
+ Then run:
88
+
89
+ ```bash
90
+ keel-verify export export.json manifest.json --walk-events --verify-closure
91
+ ```
92
+
93
+ The flag form used by the internal verifier is also supported:
94
+
95
+ ```bash
96
+ keel-verify export --export-file export.json --manifest manifest.json --walk-events --verify-closure
97
+ ```
98
+
99
+ ## Chain Walking
100
+
101
+ `--walk-events` parses `audit_export_bundle` files with `schema_version=2` and `include_chain_entries=true`.
102
+
103
+ It groups entries by `chain_scope`, sorts by `sequence_number`, recomputes every `record_hash`, verifies `prev_hash` continuity inside the export window, and fails closed on unknown `chain_format_version` values.
104
+
105
+ Schema version 1 exports remain backward compatible. They can still be verified at the export-signature layer, but they do not contain chain entries to walk.
106
+
107
+ ## Closure Verification
108
+
109
+ `--verify-closure` verifies `permit.closed` entries.
110
+
111
+ For `closure_v1`, it verifies the closure Ed25519 signature and cross-references provider/client response digests against the bundled lifecycle events.
112
+
113
+ For `closure_v2`, it also verifies `dispatch_request_digest_v1` against the permit's `binding_request_hash`, proving that the dispatch-time request body is the one covered by the closure record.
114
+
115
+ Closure verification uses public keys with purpose `permit_binding_signing`. Pass a manifest explicitly when needed:
116
+
117
+ ```bash
118
+ keel-verify export export.json manifest.json --key-manifest permit-binding-keys.json --walk-events --verify-closure
119
+ ```
120
+
121
+ The bundled trust root lives at `keel_verifier/data/trust_root.json`. It includes the production export and checkpoint signing keys currently served by `https://api.keelapi.com/v1/compliance/keys`. The production permit-binding endpoint returned 404 on 2026-05-07, so maintainers should refresh the bundled manifest when `https://api.keelapi.com/v1/integrity/permit-binding-public-keys` is live.
122
+
123
+ ## Tampering Matrix
124
+
125
+ The verifier emits stable `WALK_*` failure codes, including:
126
+
127
+ - `WALK_RECORD_HASH_MISMATCH`
128
+ - `WALK_PREV_HASH_DISCONTINUITY`
129
+ - `WALK_SEQUENCE_INVERSION`
130
+ - `WALK_UNKNOWN_CHAIN_FORMAT`
131
+ - `WALK_CLOSURE_SIGNATURE_INVALID`
132
+ - `WALK_CLOSURE_DIGEST_MISMATCH`
133
+ - `WALK_CLOSURE_DIGEST_MISSING`
134
+ - `WALK_CLOSURE_DISPATCH_DIGEST_MISMATCH`
135
+ - `WALK_UNKNOWN_CLOSURE_FORMAT`
136
+
137
+ The authoritative matrix is maintained in the Keel docs: https://docs.keelapi.com/12-tampering-detection-matrix
138
+
139
+ ## Trust Model
140
+
141
+ There are two useful kinds of verification:
142
+
143
+ - Self-attested: the file agrees with itself. This proves internal consistency only.
144
+ - Trust-root verified: the artifact verifies against a key you trust, such as the bundled production trust root, a pinned public key, or a manifest fetched and saved out-of-band.
145
+
146
+ Trust sources, strongest first:
147
+
148
+ | Mode | Flags | Notes |
149
+ | --- | --- | --- |
150
+ | Pinned key | `--expected-public-key ed25519:...` or `--public-key ed25519:...` | Strongest when obtained out-of-band. |
151
+ | Key manifest | `--key-manifest keys.json` | Supports key rotation and active windows. |
152
+ | Key manifest URL | `--key-manifest-url URL` | Explicit network fetch. |
153
+ | Bundled trust root | none | Default. No phone-home. |
154
+ | Self-attested | `--self-attested` | Development/sample mode only. |
155
+
156
+ `--public-key-url` is also supported for checkpoint verification against the single live checkpoint public-key endpoint.
157
+
158
+ ## CLI Examples
159
+
160
+ ```bash
161
+ keel-verify export export.json manifest.json
162
+ keel-verify export export.json manifest.json --walk-events
163
+ keel-verify export export.json manifest.json --walk-events --verify-closure
164
+ keel-verify checkpoint checkpoint.json
165
+ python -m keel_verifier sample/export.json --self-attested
166
+ python -m keel_verifier sample/export.json --json --self-attested
167
+ ```
168
+
169
+ Exit code `0` means verified. Exit code `1` means verification failed. Exit code `2` means bad usage.
170
+
171
+ ## Network Behavior
172
+
173
+ The verifier does not phone home. It reaches the network only when you pass `--public-key-url` or `--key-manifest-url`.
174
+
175
+ There is no telemetry.
176
+
177
+ ## Library Use
178
+
179
+ ```python
180
+ from keel_verifier import verify, verify_export_walk_events, verify_closure_record
181
+
182
+ result = verify("sample/export.json", self_attested=True)
183
+ if not result.ok:
184
+ raise SystemExit(result.error)
185
+ ```
186
+
187
+ ## Versioning
188
+
189
+ v1.0.0 syncs the public verifier with the Phase A/B/C/D internal verifier. v0.2.0 users can keep using `python -m keel_verifier <artifact>`.
190
+
191
+ ## License
192
+
193
+ MIT. See `LICENSE`.
@@ -0,0 +1,156 @@
1
+ # keel-verifier
2
+
3
+ Independent verifier for Keel governance evidence.
4
+
5
+ It runs locally, requires no access to Keel, and makes no outbound network calls unless you explicitly ask it to fetch a public key or key manifest URL.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ python -m pip install keel-verifier
11
+ keel-verify export --help
12
+ ```
13
+
14
+ From a checkout:
15
+
16
+ ```bash
17
+ python -m pip install -e .
18
+ python -m keel_verifier --help
19
+ ```
20
+
21
+ The v0.2.0 invocation pattern still works:
22
+
23
+ ```bash
24
+ python -m keel_verifier sample/export.json --self-attested
25
+ ```
26
+
27
+ ## What It Verifies
28
+
29
+ `keel-verify export` verifies a signed compliance export in three layers:
30
+
31
+ 1. The export bytes match the signed manifest `content_hash`.
32
+ 2. The manifest Ed25519 signature verifies against a trusted key.
33
+ 3. Optional Phase C/D checks walk bundled chain entries and verify closure records.
34
+
35
+ `keel-verify checkpoint` verifies integrity checkpoint JSON artifacts: the `chain_heads` composite hash, the Ed25519 checkpoint signature, and an embedded RFC 3161 timestamp MessageImprint when present.
36
+
37
+ ## Obtaining a Signed Export
38
+
39
+ Request an audit export from Keel's compliance export API and include chain entries when you want full lifecycle walking:
40
+
41
+ ```bash
42
+ curl -sS -X POST "https://api.keelapi.com/v1/compliance/exports?include_chain_entries=true" -H "Authorization: Bearer $KEEL_API_KEY" -H "Content-Type: application/json" -d '{"project_id":"<project_uuid>","format":"json"}'
43
+ ```
44
+
45
+ Download both artifacts returned by the export workflow:
46
+
47
+ - the export payload, for example `export.json`
48
+ - the signed manifest, for example `manifest.json`
49
+
50
+ Then run:
51
+
52
+ ```bash
53
+ keel-verify export export.json manifest.json --walk-events --verify-closure
54
+ ```
55
+
56
+ The flag form used by the internal verifier is also supported:
57
+
58
+ ```bash
59
+ keel-verify export --export-file export.json --manifest manifest.json --walk-events --verify-closure
60
+ ```
61
+
62
+ ## Chain Walking
63
+
64
+ `--walk-events` parses `audit_export_bundle` files with `schema_version=2` and `include_chain_entries=true`.
65
+
66
+ It groups entries by `chain_scope`, sorts by `sequence_number`, recomputes every `record_hash`, verifies `prev_hash` continuity inside the export window, and fails closed on unknown `chain_format_version` values.
67
+
68
+ Schema version 1 exports remain backward compatible. They can still be verified at the export-signature layer, but they do not contain chain entries to walk.
69
+
70
+ ## Closure Verification
71
+
72
+ `--verify-closure` verifies `permit.closed` entries.
73
+
74
+ For `closure_v1`, it verifies the closure Ed25519 signature and cross-references provider/client response digests against the bundled lifecycle events.
75
+
76
+ For `closure_v2`, it also verifies `dispatch_request_digest_v1` against the permit's `binding_request_hash`, proving that the dispatch-time request body is the one covered by the closure record.
77
+
78
+ Closure verification uses public keys with purpose `permit_binding_signing`. Pass a manifest explicitly when needed:
79
+
80
+ ```bash
81
+ keel-verify export export.json manifest.json --key-manifest permit-binding-keys.json --walk-events --verify-closure
82
+ ```
83
+
84
+ The bundled trust root lives at `keel_verifier/data/trust_root.json`. It includes the production export and checkpoint signing keys currently served by `https://api.keelapi.com/v1/compliance/keys`. The production permit-binding endpoint returned 404 on 2026-05-07, so maintainers should refresh the bundled manifest when `https://api.keelapi.com/v1/integrity/permit-binding-public-keys` is live.
85
+
86
+ ## Tampering Matrix
87
+
88
+ The verifier emits stable `WALK_*` failure codes, including:
89
+
90
+ - `WALK_RECORD_HASH_MISMATCH`
91
+ - `WALK_PREV_HASH_DISCONTINUITY`
92
+ - `WALK_SEQUENCE_INVERSION`
93
+ - `WALK_UNKNOWN_CHAIN_FORMAT`
94
+ - `WALK_CLOSURE_SIGNATURE_INVALID`
95
+ - `WALK_CLOSURE_DIGEST_MISMATCH`
96
+ - `WALK_CLOSURE_DIGEST_MISSING`
97
+ - `WALK_CLOSURE_DISPATCH_DIGEST_MISMATCH`
98
+ - `WALK_UNKNOWN_CLOSURE_FORMAT`
99
+
100
+ The authoritative matrix is maintained in the Keel docs: https://docs.keelapi.com/12-tampering-detection-matrix
101
+
102
+ ## Trust Model
103
+
104
+ There are two useful kinds of verification:
105
+
106
+ - Self-attested: the file agrees with itself. This proves internal consistency only.
107
+ - Trust-root verified: the artifact verifies against a key you trust, such as the bundled production trust root, a pinned public key, or a manifest fetched and saved out-of-band.
108
+
109
+ Trust sources, strongest first:
110
+
111
+ | Mode | Flags | Notes |
112
+ | --- | --- | --- |
113
+ | Pinned key | `--expected-public-key ed25519:...` or `--public-key ed25519:...` | Strongest when obtained out-of-band. |
114
+ | Key manifest | `--key-manifest keys.json` | Supports key rotation and active windows. |
115
+ | Key manifest URL | `--key-manifest-url URL` | Explicit network fetch. |
116
+ | Bundled trust root | none | Default. No phone-home. |
117
+ | Self-attested | `--self-attested` | Development/sample mode only. |
118
+
119
+ `--public-key-url` is also supported for checkpoint verification against the single live checkpoint public-key endpoint.
120
+
121
+ ## CLI Examples
122
+
123
+ ```bash
124
+ keel-verify export export.json manifest.json
125
+ keel-verify export export.json manifest.json --walk-events
126
+ keel-verify export export.json manifest.json --walk-events --verify-closure
127
+ keel-verify checkpoint checkpoint.json
128
+ python -m keel_verifier sample/export.json --self-attested
129
+ python -m keel_verifier sample/export.json --json --self-attested
130
+ ```
131
+
132
+ Exit code `0` means verified. Exit code `1` means verification failed. Exit code `2` means bad usage.
133
+
134
+ ## Network Behavior
135
+
136
+ The verifier does not phone home. It reaches the network only when you pass `--public-key-url` or `--key-manifest-url`.
137
+
138
+ There is no telemetry.
139
+
140
+ ## Library Use
141
+
142
+ ```python
143
+ from keel_verifier import verify, verify_export_walk_events, verify_closure_record
144
+
145
+ result = verify("sample/export.json", self_attested=True)
146
+ if not result.ok:
147
+ raise SystemExit(result.error)
148
+ ```
149
+
150
+ ## Versioning
151
+
152
+ v1.0.0 syncs the public verifier with the Phase A/B/C/D internal verifier. v0.2.0 users can keep using `python -m keel_verifier <artifact>`.
153
+
154
+ ## License
155
+
156
+ MIT. See `LICENSE`.
@@ -0,0 +1,21 @@
1
+ """Standalone verifier for Keel governance evidence."""
2
+
3
+ from keel_verifier.verifier import (
4
+ CHAIN_FORMAT_HASHERS,
5
+ CLOSURE_FORMAT_VERIFIERS,
6
+ VerifyResult,
7
+ verify,
8
+ verify_closure_record,
9
+ verify_export_walk_events,
10
+ )
11
+
12
+ __all__ = [
13
+ "CHAIN_FORMAT_HASHERS",
14
+ "CLOSURE_FORMAT_VERIFIERS",
15
+ "VerifyResult",
16
+ "verify",
17
+ "verify_closure_record",
18
+ "verify_export_walk_events",
19
+ "__version__",
20
+ ]
21
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from keel_verifier.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,296 @@
1
+ """Command-line interface for keel_verifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from keel_verifier import __version__
10
+ from keel_verifier.verifier import (
11
+ KEELAPI_CHECKPOINT_PUBLIC_KEY_URL,
12
+ KEELAPI_COMPLIANCE_KEYS_URL,
13
+ VerifyResult,
14
+ cmd_checkpoint,
15
+ cmd_export,
16
+ verify,
17
+ )
18
+
19
+ LEGACY_COMMANDS = {"export", "checkpoint"}
20
+
21
+
22
+ def _public_key_alias(args: argparse.Namespace) -> None:
23
+ if getattr(args, "public_key", None) and getattr(args, "expected_public_key", None):
24
+ raise argparse.ArgumentTypeError(
25
+ "--public-key and --expected-public-key are aliases; pass only one"
26
+ )
27
+ if getattr(args, "expected_public_key", None) is None:
28
+ args.expected_public_key = getattr(args, "public_key", None)
29
+
30
+
31
+ def _trust_flag_count(args: argparse.Namespace, *, include_public_key_url: bool) -> int:
32
+ values = [
33
+ getattr(args, "expected_public_key", None),
34
+ getattr(args, "key_manifest", None),
35
+ getattr(args, "key_manifest_url", None),
36
+ getattr(args, "self_attested", False),
37
+ ]
38
+ if include_public_key_url:
39
+ values.append(getattr(args, "public_key_url", None))
40
+ return sum(bool(value) for value in values)
41
+
42
+
43
+ def _add_key_manifest_args(p: argparse.ArgumentParser) -> None:
44
+ p.add_argument(
45
+ "--key-manifest",
46
+ help=(
47
+ "Local path to a Keel public key manifest JSON file. Defaults to "
48
+ "the bundled production trust root when no trust override is passed."
49
+ ),
50
+ )
51
+ p.add_argument(
52
+ "--key-manifest-url",
53
+ help=f"URL to fetch the key manifest from (canonical: {KEELAPI_COMPLIANCE_KEYS_URL}).",
54
+ )
55
+
56
+
57
+ def _add_common_trust_args(p: argparse.ArgumentParser) -> None:
58
+ p.add_argument(
59
+ "--expected-public-key",
60
+ help="ed25519:<base64> public key the artifact must be signed with.",
61
+ )
62
+ p.add_argument(
63
+ "--public-key",
64
+ help="Alias for --expected-public-key, preserved for v0.2.0 users.",
65
+ )
66
+ p.add_argument(
67
+ "--self-attested",
68
+ action="store_true",
69
+ help=(
70
+ "Verify against the artifact's embedded public_key. This only proves "
71
+ "internal consistency; it does not prove Keel signed the artifact."
72
+ ),
73
+ )
74
+ p.add_argument(
75
+ "--offline",
76
+ action="store_true",
77
+ help=argparse.SUPPRESS,
78
+ )
79
+
80
+
81
+ def _cmd_export_cli(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
82
+ try:
83
+ _public_key_alias(args)
84
+ except argparse.ArgumentTypeError as exc:
85
+ parser.error(str(exc))
86
+ args.export_file = args.export_file_flag or args.export_file_pos
87
+ args.manifest = args.manifest_flag or args.manifest_pos
88
+ if not args.export_file:
89
+ parser.error("export requires EXPORT_FILE or --export-file")
90
+ if not args.manifest:
91
+ parser.error("export requires MANIFEST or --manifest")
92
+ if _trust_flag_count(args, include_public_key_url=False) > 1:
93
+ parser.error(
94
+ "--expected-public-key/--public-key, --key-manifest, "
95
+ "--key-manifest-url, and --self-attested are mutually exclusive"
96
+ )
97
+ return cmd_export(args)
98
+
99
+
100
+ def _cmd_checkpoint_cli(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
101
+ try:
102
+ _public_key_alias(args)
103
+ except argparse.ArgumentTypeError as exc:
104
+ parser.error(str(exc))
105
+ args.checkpoint_file = args.checkpoint_file_flag or args.checkpoint_file_pos
106
+ if not args.checkpoint_file:
107
+ parser.error("checkpoint requires CHECKPOINT_FILE or --checkpoint-file")
108
+ if _trust_flag_count(args, include_public_key_url=True) > 1:
109
+ parser.error(
110
+ "--expected-public-key/--public-key, --public-key-url, --key-manifest, "
111
+ "--key-manifest-url, and --self-attested are mutually exclusive"
112
+ )
113
+ return cmd_checkpoint(args)
114
+
115
+
116
+ def _build_parser() -> argparse.ArgumentParser:
117
+ parser = argparse.ArgumentParser(
118
+ prog="keel-verify",
119
+ description="Standalone verifier for Keel trust artifacts.",
120
+ epilog=(
121
+ "New export verification supports --walk-events and --verify-closure. "
122
+ "Backward compatible usage remains: python -m keel_verifier <checkpoint.json>."
123
+ ),
124
+ )
125
+ parser.add_argument("--version", action="version", version=f"keel_verifier {__version__}")
126
+ sub = parser.add_subparsers(dest="cmd")
127
+
128
+ p_export = sub.add_parser("export", help="Verify a signed compliance export.")
129
+ p_export.add_argument("export_file_pos", nargs="?", metavar="EXPORT_FILE")
130
+ p_export.add_argument("manifest_pos", nargs="?", metavar="MANIFEST")
131
+ p_export.add_argument("--export-file", dest="export_file_flag")
132
+ p_export.add_argument("--manifest", dest="manifest_flag")
133
+ p_export.add_argument(
134
+ "--walk-events",
135
+ action="store_true",
136
+ help=(
137
+ "After export content hash and signature verification, parse an "
138
+ "audit export bundle and walk bundled chain_entries."
139
+ ),
140
+ )
141
+ p_export.add_argument(
142
+ "--verify-closure",
143
+ action="store_true",
144
+ help=(
145
+ "After export content hash and signature verification, verify "
146
+ "permit.closed closure signatures and dispatch/provider/client digest "
147
+ "consistency from bundled chain_entries."
148
+ ),
149
+ )
150
+ _add_common_trust_args(p_export)
151
+ _add_key_manifest_args(p_export)
152
+ p_export.set_defaults(func=lambda args: _cmd_export_cli(p_export, args))
153
+
154
+ p_cp = sub.add_parser("checkpoint", help="Verify an integrity checkpoint JSON file.")
155
+ p_cp.add_argument("checkpoint_file_pos", nargs="?", metavar="CHECKPOINT_FILE")
156
+ p_cp.add_argument("--checkpoint-file", dest="checkpoint_file_flag")
157
+ _add_common_trust_args(p_cp)
158
+ p_cp.add_argument(
159
+ "--public-key-url",
160
+ help=(
161
+ "URL to fetch the single checkpoint public key "
162
+ f"(canonical: {KEELAPI_CHECKPOINT_PUBLIC_KEY_URL})."
163
+ ),
164
+ )
165
+ _add_key_manifest_args(p_cp)
166
+ p_cp.add_argument(
167
+ "--tsa-ca-bundle",
168
+ help="Optional CA bundle for TSA trust-chain validation (note only).",
169
+ )
170
+ p_cp.set_defaults(func=lambda args: _cmd_checkpoint_cli(p_cp, args))
171
+ return parser
172
+
173
+
174
+ def _build_legacy_parser() -> argparse.ArgumentParser:
175
+ parser = argparse.ArgumentParser(
176
+ prog="python -m keel_verifier",
177
+ description=(
178
+ "Backward-compatible v0.2.0 checkpoint verifier. For signed "
179
+ "compliance exports, use: keel-verify export --help."
180
+ ),
181
+ )
182
+ parser.add_argument("export_file", help="Path to a sealed Keel checkpoint/export JSON file.")
183
+ parser.add_argument("--json", action="store_true", dest="as_json")
184
+ parser.add_argument("--no-tsa", action="store_true", help="Skip RFC 3161 TSA receipt verification.")
185
+ parser.add_argument("--public-key", metavar="ed25519:BASE64")
186
+ parser.add_argument(
187
+ "--public-key-url",
188
+ metavar="URL",
189
+ help=f"Fetch the trust-root public key from this URL (canonical: {KEELAPI_CHECKPOINT_PUBLIC_KEY_URL}).",
190
+ )
191
+ parser.add_argument(
192
+ "--self-attested",
193
+ action="store_true",
194
+ dest="self_attested",
195
+ help=(
196
+ "Verify against the artifact's own embedded public_key. This only "
197
+ "proves internal consistency; it does not prove Keel signed it."
198
+ ),
199
+ )
200
+ parser.add_argument("--offline", action="store_true", help=argparse.SUPPRESS)
201
+ parser.add_argument("--version", action="version", version=f"keel_verifier {__version__}")
202
+ return parser
203
+
204
+
205
+ def _print_human(result: VerifyResult, export_path: str, stream) -> None:
206
+ p = lambda s="": print(s, file=stream)
207
+
208
+ if result.ok:
209
+ p(f"VERIFIED: {export_path}")
210
+ else:
211
+ p(f"FAILED: {export_path}")
212
+ if result.error:
213
+ for line in result.error.splitlines():
214
+ p(f" {line}")
215
+
216
+ if result.checkpoint_id:
217
+ p(f" Checkpoint: {result.checkpoint_id}")
218
+ if result.computed_at:
219
+ p(f" Computed at: {result.computed_at}")
220
+ if result.composite_hash:
221
+ p(f" Composite: {result.composite_hash}")
222
+ if result.chain_heads_count:
223
+ p(f" Chain heads: {result.chain_heads_count} scope(s)")
224
+ if result.public_key:
225
+ p(f" Public key: {result.public_key}")
226
+ if result.key_id:
227
+ p(f" Key id: {result.key_id}")
228
+ if result.trust_source:
229
+ p(f" Trust source: {result.trust_source}")
230
+
231
+ if result.tsa_present:
232
+ if not result.tsa_checked:
233
+ p(" TSA: present (skipped — --no-tsa)")
234
+ elif result.tsa_verified:
235
+ p(f" TSA: verified ({result.tsa_reason})")
236
+ if result.tsa_url:
237
+ p(f" url: {result.tsa_url}")
238
+ if result.tsa_requested_at:
239
+ p(f" stamped at: {result.tsa_requested_at}")
240
+ else:
241
+ p(f" TSA: FAILED ({result.tsa_reason})")
242
+ else:
243
+ p(" TSA: not present")
244
+
245
+ if result.ok and result.self_attested:
246
+ p()
247
+ p("WARNING: --self-attested verification only proves internal consistency.")
248
+ p("It does not prove that Keel signed this artifact. Drop --self-attested to")
249
+ p("verify against the bundled trust root, or pin explicitly with:")
250
+ p(f" --public-key-url {KEELAPI_CHECKPOINT_PUBLIC_KEY_URL}")
251
+
252
+
253
+ def _main_legacy(argv: list[str]) -> int:
254
+ parser = _build_legacy_parser()
255
+ args = parser.parse_args(argv)
256
+ flags = (args.public_key, args.public_key_url, args.self_attested)
257
+ if sum(bool(x) for x in flags) > 1:
258
+ print(
259
+ "ERROR: --public-key, --public-key-url, and --self-attested are mutually exclusive.",
260
+ file=sys.stderr,
261
+ )
262
+ return 2
263
+
264
+ result = verify(
265
+ args.export_file,
266
+ public_key=args.public_key,
267
+ public_key_url=args.public_key_url,
268
+ self_attested=args.self_attested,
269
+ check_tsa=not args.no_tsa,
270
+ )
271
+
272
+ if args.as_json:
273
+ print(json.dumps(result.to_dict(), indent=2, sort_keys=True))
274
+ if not result.ok and result.error:
275
+ print(result.error, file=sys.stderr)
276
+ else:
277
+ stream = sys.stdout if result.ok else sys.stderr
278
+ _print_human(result, args.export_file, stream)
279
+ return 0 if result.ok else 1
280
+
281
+
282
+ def main(argv: list[str] | None = None) -> int:
283
+ raw = list(sys.argv[1:] if argv is None else argv)
284
+ if raw and raw[0] not in LEGACY_COMMANDS and raw[0] not in {"-h", "--help", "--version"}:
285
+ return _main_legacy(raw)
286
+
287
+ parser = _build_parser()
288
+ args = parser.parse_args(raw)
289
+ if not hasattr(args, "func"):
290
+ parser.print_help()
291
+ return 0
292
+ return args.func(args)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ raise SystemExit(main())