@nexart/cli 0.16.0 → 1.0.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/CHANGELOG.md +70 -0
- package/README.md +105 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +111 -18
- package/dist/index.js.map +1 -1
- package/dist/standaloneVerify.d.ts +18 -2
- package/dist/standaloneVerify.d.ts.map +1 -1
- package/dist/standaloneVerify.js +40 -1
- package/dist/standaloneVerify.js.map +1 -1
- package/dist/tsaVerify.d.ts.map +1 -1
- package/dist/tsaVerify.js +58 -4
- package/dist/tsaVerify.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @nexart/cli — Changelog
|
|
2
2
|
|
|
3
|
+
## v1.0.0 (2026-06-18) — First stable release (includes `verify-offline`)
|
|
4
|
+
|
|
5
|
+
> **Stabilisation release.** Marks the `@nexart/cli` command surface stable under
|
|
6
|
+
> semantic versioning. No cryptographic, hashing, canonicalisation, or exit-code
|
|
7
|
+
> change. Builds on `@nexart/ai-execution@^1.0.0` and `@nexart/codemode-sdk@^1.12.0`,
|
|
8
|
+
> both stable. The CLI continues to implement **zero** independent verification logic —
|
|
9
|
+
> it delegates entirely to the SDK verification core. The `verify-offline` command
|
|
10
|
+
> introduced for this release (below) is the only functional addition.
|
|
11
|
+
|
|
12
|
+
### Included in this release
|
|
13
|
+
The new `verify-offline` command — a UX-only entry point over the existing offline
|
|
14
|
+
verification path:
|
|
15
|
+
|
|
16
|
+
- **New `verify-offline <record.json>` command.** A convenience alias for
|
|
17
|
+
`verify-record --offline`: it runs the **same** verification core (integrity via
|
|
18
|
+
certificate-hash recompute, Ed25519 receipt signature, timestamp reporting) and
|
|
19
|
+
**never contacts a NexArt node**. The signing key comes from `--keys` (an alias for
|
|
20
|
+
`--public-key`) or a key embedded in the record. Reads from a file path or `-` (stdin).
|
|
21
|
+
- **Offline report labelling.** A passing record prints
|
|
22
|
+
**`FINAL STATUS: AUTHENTIC (OFFLINE)`** with an explicit **`Mode: Offline Verification`**
|
|
23
|
+
line (the redaction note is still appended for privacy-redacted records). A real
|
|
24
|
+
integrity mismatch or invalid signature stays **`FINAL STATUS: FAILED`** with exit `1`.
|
|
25
|
+
`--json` gains an additive `"mode": "offline"` field; online output is unchanged.
|
|
26
|
+
- **No new verification logic.** Hashing, the version-dispatched `canonicalForProtocol()`
|
|
27
|
+
canonicalisation, the 3-state core, and exit codes are byte-for-byte identical to
|
|
28
|
+
`verify-record`. As before, a missing or unverifiable timestamp is *reported* but never
|
|
29
|
+
gates the verdict or exit code. `verify-bundle` is untouched.
|
|
30
|
+
|
|
31
|
+
## v0.16.1 (2026-06-17) — `verify-record`: 3-state verification (VERIFIED / AUTHENTIC (REDACTED) / FAILED)
|
|
32
|
+
|
|
33
|
+
UX release. `verify-bundle` is **byte-for-byte unchanged**; the new third state applies
|
|
34
|
+
to `verify-record` (and the `verify-url` / `verify-hash` commands that render through it).
|
|
35
|
+
|
|
36
|
+
- **Privacy-redacted records are no longer reported as FAILED.** A public record served
|
|
37
|
+
from a node may have the snapshot's `input` / `output` *content* stripped while the
|
|
38
|
+
`inputHash` / `outputHash` anchors are kept. Because `certificateHash` is sealed over the
|
|
39
|
+
full snapshot, recompute is impossible on a redacted snapshot — but that is **not
|
|
40
|
+
tampering**. Such records now report **`FINAL STATUS: AUTHENTIC (REDACTED)`** with
|
|
41
|
+
**`⚠ Integrity: NOT RECOMPUTABLE (redacted)`** and **exit 0**, provided the receipt
|
|
42
|
+
signature still verifies.
|
|
43
|
+
- **Three exit-stable states.** `VERIFIED` (exit 0) → content present, hash recomputes;
|
|
44
|
+
`AUTHENTIC (REDACTED)` (exit 0) → content stripped, hashes kept, signature passes;
|
|
45
|
+
`FAILED` (exit 1) → a real content mismatch or an invalid signature. A genuine mismatch
|
|
46
|
+
is **never** softened to redacted, and a redacted record whose hash still recomputes
|
|
47
|
+
stays **VERIFIED**.
|
|
48
|
+
- **Report.** For a redacted record the Signature line is shown before the Integrity line,
|
|
49
|
+
and a short **Public Verification (Redacted)** note explains that full integrity
|
|
50
|
+
verification requires the original (un-redacted) execution data. `--short` now emits
|
|
51
|
+
`✔ VERIFIED` / `⚠ AUTHENTIC (REDACTED)` / `✖ FAILED`. `--json` gains a `redacted`
|
|
52
|
+
boolean, an `authentic-redacted` `status`, and a `redacted` `integrity` value.
|
|
53
|
+
- **No cryptographic change.** `certificateHash` computation, canonicalisation, and the
|
|
54
|
+
signature / timestamp logic are untouched — this is purely a classification and
|
|
55
|
+
presentation layer. `verify-bundle` does not opt into redaction classification and is
|
|
56
|
+
byte-for-byte unchanged.
|
|
57
|
+
- **UX hardening.** The redacted context block now names the redaction source
|
|
58
|
+
(`Redaction: Applied by certification node (privacy mode)`) and sits tight under the
|
|
59
|
+
headline. A present-but-unverifiable timestamp (parse failure, or verification not
|
|
60
|
+
attempted) reports `⚠ Timestamp: PRESENT BUT NOT VERIFIED` instead of
|
|
61
|
+
`INVALID (malformed token)`, and a missing one reports `Timestamp: NOT INCLUDED` — the
|
|
62
|
+
timestamp still never affects `FINAL STATUS`. Section order is fixed: Signature →
|
|
63
|
+
Integrity → Timestamp → Node → Identity → Execution.
|
|
64
|
+
- **Timestamp compatibility (no longer "malformed token").** RFC 3161 timestamp
|
|
65
|
+
verification now accepts the full `TimeStampResp` returned by the certification node
|
|
66
|
+
(PKIStatusInfo + `timeStampToken`) in addition to a bare `TimeStampToken` (CMS
|
|
67
|
+
`ContentInfo`). The wrapper is detected structurally and unwrapped to the inner
|
|
68
|
+
`ContentInfo` before the unchanged signature / imprint / chain checks run. This is a
|
|
69
|
+
parsing-layer fix only — `certificateHash`, the imprint binding, the signature logic,
|
|
70
|
+
and the "timestamp never affects `FINAL STATUS`/exit code" invariant are all
|
|
71
|
+
unchanged; an unparseable token still degrades to `PRESENT BUT NOT VERIFIED`.
|
|
72
|
+
|
|
3
73
|
## v0.16.0 (2026-06-17) — `verify-record`: fully automatic, audit-grade, self-contained
|
|
4
74
|
|
|
5
75
|
Feature release. `verify-bundle` is **byte-for-byte unchanged** — the redesigned
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @nexart/cli
|
|
1
|
+
# @nexart/cli v1.0.0
|
|
2
2
|
|
|
3
3
|
Command-line interface for NexArt — run, replay, and verify deterministic generative art, plus AI execution certification commands.
|
|
4
4
|
|
|
@@ -34,7 +34,9 @@ authenticity without relying on NexArt systems or source code.
|
|
|
34
34
|
### What this verifies
|
|
35
35
|
|
|
36
36
|
- **Integrity** — the record's `certificateHash` is recomputed from the record's
|
|
37
|
-
contents and compared to the stored value.
|
|
37
|
+
contents and compared to the stored value. A privacy-redacted public record
|
|
38
|
+
(input/output content stripped, hashes kept) is reported as **AUTHENTIC
|
|
39
|
+
(REDACTED)** rather than failed — see the [3-state model](#verifying-a-full-cer-record-nexart-verify-record).
|
|
38
40
|
- **Authenticity** — the Ed25519 signature over the node's receipt is verified,
|
|
39
41
|
when a signature is present.
|
|
40
42
|
|
|
@@ -691,13 +693,16 @@ nexart verify-bundle cer.json --verify-timestamp --tsa-roots tsa-roots.pem
|
|
|
691
693
|
not established.
|
|
692
694
|
|
|
693
695
|
3. **Timestamp (RFC 3161)** *(optional, additive — `--verify-timestamp`)* — when
|
|
694
|
-
the bundle carries an RFC 3161
|
|
696
|
+
the bundle carries an RFC 3161 timestamp (in `trustedTimestamps[]` or a
|
|
695
697
|
singular `timestamp`), this **offline** check confirms the token anchors the
|
|
696
|
-
bundle to a point in time.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
698
|
+
bundle to a point in time. Both wire shapes are accepted: a full
|
|
699
|
+
`TimeStampResp` (the certification node's output) and a bare `TimeStampToken`
|
|
700
|
+
(e.g. OpenSSL's `-token_out`) — the response wrapper is detected and unwrapped
|
|
701
|
+
automatically. It verifies, in order: (1) the TSA's CMS signature over the
|
|
702
|
+
embedded `TSTInfo`; (2) that the token's message imprint equals the bundle's
|
|
703
|
+
`certificateHash` digest; (3) that the TSA signing certificate chains to a root
|
|
704
|
+
supplied via `--tsa-roots`; (4) that the certificate is valid at the timestamp;
|
|
705
|
+
and (5) that the timestamp is at or after the bundle's `createdAt`.
|
|
701
706
|
|
|
702
707
|
This layer is **strictly additive**: it never modifies `certificateHash`, is
|
|
703
708
|
never included in canonicalisation, and **never affects the Integrity or
|
|
@@ -813,7 +818,7 @@ nexart verify-record record.json --json
|
|
|
813
818
|
# Read the record from stdin (use "-" as the path)
|
|
814
819
|
curl -s https://node.example/records/abc.json | nexart verify-record -
|
|
815
820
|
|
|
816
|
-
# One-line verdict only (✔ VERIFIED / ✖ FAILED)
|
|
821
|
+
# One-line verdict only (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
|
|
817
822
|
nexart verify-record record.json --short
|
|
818
823
|
|
|
819
824
|
# Fully offline, with an explicit signing key
|
|
@@ -825,6 +830,35 @@ record root is treated as the bundle itself (a certified bundle with its receipt
|
|
|
825
830
|
signature embedded works directly). There is no "missing bundle" error — a
|
|
826
831
|
structurally invalid record simply reports a clean **Integrity: FAIL**.
|
|
827
832
|
|
|
833
|
+
**Three-state model (since v0.16.1).** A public record served from a node may be
|
|
834
|
+
**privacy-redacted**: the snapshot's `input` / `output` *content* is stripped while the
|
|
835
|
+
`inputHash` / `outputHash` anchors are kept. Because `certificateHash` is sealed over the
|
|
836
|
+
full snapshot, it can no longer be recomputed from a redacted snapshot — but that is
|
|
837
|
+
**not tampering**. `verify-record` (and `verify-url` / `verify-hash`) detect this case and
|
|
838
|
+
report a third outcome:
|
|
839
|
+
|
|
840
|
+
| Outcome | When | Integrity line | Exit |
|
|
841
|
+
| --- | --- | --- | --- |
|
|
842
|
+
| **VERIFIED** | content present, `certificateHash` recomputes, signature not invalid | `✔ Integrity: PASS` | `0` |
|
|
843
|
+
| **AUTHENTIC (REDACTED)** | content stripped (hashes kept), recompute not possible, signature **passes** | `⚠ Integrity: NOT RECOMPUTABLE (redacted)` | `0` |
|
|
844
|
+
| **FAILED** | a real integrity mismatch (content present but hash differs) or an invalid signature | `✖ Integrity: FAIL` | `1` |
|
|
845
|
+
|
|
846
|
+
A redacted record is reclassified **only** when the snapshot is genuinely content-stripped
|
|
847
|
+
*and* the signature still verifies — a true content mismatch is **never** softened to
|
|
848
|
+
redacted, an invalid signature is always **FAILED**, and a redacted record whose hash still
|
|
849
|
+
recomputes simply stays **VERIFIED**. The `certificateHash` computation, canonicalisation,
|
|
850
|
+
and signature/timestamp logic are **unchanged**; this is purely a classification and
|
|
851
|
+
presentation layer.
|
|
852
|
+
|
|
853
|
+
> **What AUTHENTIC (REDACTED) does and does not prove.** It proves the record carries a
|
|
854
|
+
> valid node signature over the sealed `certificateHash` — i.e. it is authentic. It does
|
|
855
|
+
> **not** recompute the full snapshot, so it cannot prove the *content* behind the kept
|
|
856
|
+
> `inputHash` / `outputHash` was not altered before redaction. Redaction detection is
|
|
857
|
+
> structural (content absent, hashes present), so treat **AUTHENTIC (REDACTED)** as
|
|
858
|
+
> trustworthy only when the record comes from an authenticated node source (e.g. the
|
|
859
|
+
> node's public endpoint over TLS). For the strongest guarantee, obtain the full,
|
|
860
|
+
> un-redacted record and re-run `verify-record` to get **VERIFIED**.
|
|
861
|
+
|
|
828
862
|
**Signing key resolution (by priority).**
|
|
829
863
|
|
|
830
864
|
1. **`--public-key <file>`** — an explicit override (PEM, JWK, NodeKeysDocument, or
|
|
@@ -853,14 +887,28 @@ a timestamp *status*, never a crash.
|
|
|
853
887
|
**Input from a file or stdin.** Pass a file path, or **`-`** to read the record from
|
|
854
888
|
standard input — handy for pipelines such as `curl -s … | nexart verify-record -`.
|
|
855
889
|
|
|
856
|
-
**Report.** The default output is an audit-grade summary
|
|
857
|
-
**Signature**, **Timestamp** (`VERIFIED` / `INVALID (<reason>)` /
|
|
858
|
-
VERIFIED` / `NOT INCLUDED
|
|
859
|
-
(node id, key id, key source, operator), **Identity** (asserted identity, or
|
|
860
|
-
and **Execution** (protocol, provider, model, input/output hashes, parameters,
|
|
861
|
-
created-at).
|
|
862
|
-
|
|
863
|
-
|
|
890
|
+
**Report.** The default output is an audit-grade summary rendered in a fixed section
|
|
891
|
+
order — **Signature**, **Integrity**, **Timestamp** (`VERIFIED` / `INVALID (<reason>)` /
|
|
892
|
+
`PRESENT BUT NOT VERIFIED` / `NOT INCLUDED`, plus generation time and authority),
|
|
893
|
+
**Node** (node id, key id, key source, operator), **Identity** (asserted identity, or
|
|
894
|
+
none), and **Execution** (protocol, provider, model, input/output hashes, parameters,
|
|
895
|
+
created-at). A present-but-unverifiable token (a parse failure, or verification not
|
|
896
|
+
attempted) is shown as a caution — `⚠ Timestamp: PRESENT BUT NOT VERIFIED` — never as an
|
|
897
|
+
error, and the timestamp **never** affects `FINAL STATUS`. The report ends with a single
|
|
898
|
+
headline line — **`FINAL STATUS: VERIFIED EVIDENCE RECORD`**, **`FINAL STATUS: AUTHENTIC
|
|
899
|
+
(REDACTED)`**, or **`FINAL STATUS: FAILED`**. For a redacted record the Signature line is
|
|
900
|
+
shown before the Integrity line and a tight context block follows the headline:
|
|
901
|
+
|
|
902
|
+
```
|
|
903
|
+
FINAL STATUS: AUTHENTIC (REDACTED)
|
|
904
|
+
Mode: Public Verification (Redacted)
|
|
905
|
+
Redaction: Applied by certification node (privacy mode)
|
|
906
|
+
! Full integrity verification requires access to original execution data
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
`--json` emits the same information as a structured object (with `status` of `verified` /
|
|
910
|
+
`authentic-redacted` / `failed`, `integrity` including `redacted`, and a `redacted`
|
|
911
|
+
boolean); `--short` prints only `✔ VERIFIED`, `⚠ AUTHENTIC (REDACTED)`, or `✖ FAILED`.
|
|
864
912
|
|
|
865
913
|
| Option | Description |
|
|
866
914
|
| --- | --- |
|
|
@@ -869,7 +917,7 @@ structured object; `--short` prints only `✔ VERIFIED` or `✖ FAILED`.
|
|
|
869
917
|
| `--kid <id>` | Key id to select from the key set (default: the receipt's `attestorKeyId`). |
|
|
870
918
|
| `--tsa-roots <file>` | Override the default DigiCert TSA trust roots with custom root certificate(s) (PEM). |
|
|
871
919
|
| `--json` | Emit a machine-readable report. |
|
|
872
|
-
| `--short` | Print only a single verdict line (`✔ VERIFIED` / `✖ FAILED`). |
|
|
920
|
+
| `--short` | Print only a single verdict line (`✔ VERIFIED` / `⚠ AUTHENTIC (REDACTED)` / `✖ FAILED`). |
|
|
873
921
|
| `--verbose` | Show intermediate verification steps. |
|
|
874
922
|
|
|
875
923
|
> **`verify-bundle` is unchanged.** The redesigned output and automatic key/timestamp
|
|
@@ -879,6 +927,43 @@ structured object; `--short` prints only `✔ VERIFIED` or `✖ FAILED`.
|
|
|
879
927
|
|
|
880
928
|
---
|
|
881
929
|
|
|
930
|
+
## Offline verification (`nexart verify-offline`)
|
|
931
|
+
|
|
932
|
+
`nexart verify-offline <record.json>` is a convenience alias for
|
|
933
|
+
`verify-record --offline` for relying parties who want a single, unambiguous
|
|
934
|
+
"verify this locally, never touch the network" command. It runs the **exact same**
|
|
935
|
+
verification core — integrity (certificate-hash recompute), Ed25519 receipt
|
|
936
|
+
signature, and timestamp reporting — and **never contacts a NexArt node**. The
|
|
937
|
+
signing key must come from `--keys` (an alias for `--public-key`) or be embedded in
|
|
938
|
+
the record.
|
|
939
|
+
|
|
940
|
+
```bash
|
|
941
|
+
# Verify a record locally with an explicit node key file
|
|
942
|
+
nexart verify-offline record.json --keys node-keys.json
|
|
943
|
+
|
|
944
|
+
# Verify a record that embeds its own signing key
|
|
945
|
+
nexart verify-offline record.json
|
|
946
|
+
|
|
947
|
+
# Read the record from stdin
|
|
948
|
+
curl -s https://node.example/records/abc.json | nexart verify-offline - --keys node-keys.json
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
It differs from `verify-record` only in presentation: a passing record reports
|
|
952
|
+
**`FINAL STATUS: AUTHENTIC (OFFLINE)`** with an explicit **`Mode: Offline
|
|
953
|
+
Verification`** line (the redaction note is still appended for a privacy-redacted
|
|
954
|
+
record), while a real mismatch or invalid signature stays **`FINAL STATUS:
|
|
955
|
+
FAILED`** with exit `1`. `--json` adds `"mode": "offline"`. **Hashing,
|
|
956
|
+
canonicalisation, the 3-state core, and exit codes are identical to
|
|
957
|
+
`verify-record`** — this is a UX wrapper, not new verification logic. As with
|
|
958
|
+
`verify-record`, a missing or unverifiable timestamp is *reported* but never
|
|
959
|
+
changes the verdict or exit code.
|
|
960
|
+
|
|
961
|
+
> **This verifies authenticity and integrity, not re-execution.** A pass confirms
|
|
962
|
+
> the record was signed by a NexArt node and has not been tampered with; it does
|
|
963
|
+
> **not** re-run the workload.
|
|
964
|
+
|
|
965
|
+
---
|
|
966
|
+
|
|
882
967
|
## Verifying from a URL (`nexart verify-url`)
|
|
883
968
|
|
|
884
969
|
`nexart verify-url <url>` is the one-command verification experience: it **fetches** a
|
|
@@ -889,7 +974,7 @@ automatic key/timestamp resolution and audit-grade renderer as `verify-record`.
|
|
|
889
974
|
# Fetch and fully verify a published record
|
|
890
975
|
nexart verify-url https://node.example/records/abc.json
|
|
891
976
|
|
|
892
|
-
# One-line
|
|
977
|
+
# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
|
|
893
978
|
nexart verify-url https://node.example/records/abc.json --short
|
|
894
979
|
|
|
895
980
|
# Machine-readable report
|
|
@@ -917,7 +1002,7 @@ through `verify-url`, so the **output is identical** to `verify-url`.
|
|
|
917
1002
|
# Resolve and fully verify a record by certificate hash
|
|
918
1003
|
nexart verify-hash sha256:abc123…
|
|
919
1004
|
|
|
920
|
-
# One-line
|
|
1005
|
+
# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
|
|
921
1006
|
nexart verify-hash sha256:abc123… --short
|
|
922
1007
|
|
|
923
1008
|
# Machine-readable report
|
package/dist/index.d.ts
CHANGED
|
@@ -253,6 +253,7 @@ export interface RecordVerifyOpts {
|
|
|
253
253
|
offline?: boolean;
|
|
254
254
|
wellKnownUrl?: string;
|
|
255
255
|
short?: boolean;
|
|
256
|
+
offlineMode?: boolean;
|
|
256
257
|
}
|
|
257
258
|
export declare function verifyRecordStandaloneCommand(file: string, opts: RecordVerifyOpts): Promise<StandaloneVerifyResult>;
|
|
258
259
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1559,6 +1559,9 @@ export async function verifyRecordFromObject(record, opts) {
|
|
|
1559
1559
|
publicKeyMaterial,
|
|
1560
1560
|
kid: attestorKeyId,
|
|
1561
1561
|
verbose: opts.verbose,
|
|
1562
|
+
// Public records may be privacy-redacted (input/output content stripped). Classify
|
|
1563
|
+
// those as `integrity: 'redacted'` rather than a tamper FAIL.
|
|
1564
|
+
classifyRedacted: true,
|
|
1562
1565
|
});
|
|
1563
1566
|
// If no override / well-known key was used but the signature still verified, the
|
|
1564
1567
|
// verifier must have used key material embedded in the bundle.
|
|
@@ -1594,7 +1597,8 @@ export async function verifyRecordFromObject(record, opts) {
|
|
|
1594
1597
|
opts,
|
|
1595
1598
|
});
|
|
1596
1599
|
// Exit code is determined SOLELY by integrity + signature — never by the timestamp.
|
|
1597
|
-
|
|
1600
|
+
// VERIFIED and AUTHENTIC (REDACTED) both exit 0; only FAILED exits 1.
|
|
1601
|
+
process.exitCode = result.status === 'failed' ? 1 : 0;
|
|
1598
1602
|
return result;
|
|
1599
1603
|
}
|
|
1600
1604
|
/**
|
|
@@ -1750,7 +1754,7 @@ function keySourceLabel(source) {
|
|
|
1750
1754
|
function formatRecordTimestampLine(status) {
|
|
1751
1755
|
const CHECK = '\u2714';
|
|
1752
1756
|
const CROSS = '\u2716';
|
|
1753
|
-
const
|
|
1757
|
+
const WARN = '\u26A0';
|
|
1754
1758
|
switch (status) {
|
|
1755
1759
|
case 'valid':
|
|
1756
1760
|
return `${CHECK} Timestamp: VERIFIED`;
|
|
@@ -1764,13 +1768,15 @@ function formatRecordTimestampLine(status) {
|
|
|
1764
1768
|
return `${CROSS} Timestamp: INVALID (expired)`;
|
|
1765
1769
|
case 'invalid-time':
|
|
1766
1770
|
return `${CROSS} Timestamp: INVALID (time)`;
|
|
1771
|
+
// A present-but-unverifiable token (parse failure, or verification not attempted) is
|
|
1772
|
+
// NOT a broken record — it is advisory only and never affects FINAL STATUS. Report it
|
|
1773
|
+
// as a caution, not an error, so users don't read it as tampering.
|
|
1767
1774
|
case 'malformed':
|
|
1768
|
-
return `${CROSS} Timestamp: INVALID (malformed token)`;
|
|
1769
1775
|
case 'present-unverified':
|
|
1770
|
-
return `${
|
|
1776
|
+
return `${WARN} Timestamp: PRESENT BUT NOT VERIFIED`;
|
|
1771
1777
|
case 'not-present':
|
|
1772
1778
|
default:
|
|
1773
|
-
return `${
|
|
1779
|
+
return `${CROSS} Timestamp: NOT INCLUDED`;
|
|
1774
1780
|
}
|
|
1775
1781
|
}
|
|
1776
1782
|
/**
|
|
@@ -1795,7 +1801,9 @@ function renderRecordVerification(result, tsaResult, ctx) {
|
|
|
1795
1801
|
if (ctx.opts.json) {
|
|
1796
1802
|
process.stdout.write(JSON.stringify({
|
|
1797
1803
|
status: result.status,
|
|
1804
|
+
...(ctx.opts.offlineMode ? { mode: 'offline' } : {}),
|
|
1798
1805
|
integrity: result.integrity,
|
|
1806
|
+
redacted: result.integrity === 'redacted',
|
|
1799
1807
|
signature: result.signature,
|
|
1800
1808
|
protocolVersion: result.protocolVersion,
|
|
1801
1809
|
certificateHash: result.expectedCertificateHash,
|
|
@@ -1824,11 +1832,16 @@ function renderRecordVerification(result, tsaResult, ctx) {
|
|
|
1824
1832
|
}, null, 2) + '\n');
|
|
1825
1833
|
return;
|
|
1826
1834
|
}
|
|
1827
|
-
const
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1835
|
+
const WARN = '\u26A0';
|
|
1836
|
+
const mark = (p) => p === 'pass' ? '\u2714' : p === 'redacted' ? WARN : p === 'fail' ? '\u2716' : '\u25CB';
|
|
1837
|
+
const label = (p) => p === 'redacted' ? 'NOT RECOMPUTABLE (redacted)' : p.toUpperCase();
|
|
1838
|
+
// --short: a single machine-glanceable verdict line, nothing else. 3-state.
|
|
1830
1839
|
if (ctx.opts.short) {
|
|
1831
|
-
console.log(result.status === 'verified'
|
|
1840
|
+
console.log(result.status === 'verified'
|
|
1841
|
+
? '\u2714 VERIFIED'
|
|
1842
|
+
: result.status === 'authentic-redacted'
|
|
1843
|
+
? `${WARN} AUTHENTIC (REDACTED)`
|
|
1844
|
+
: '\u2716 FAILED');
|
|
1832
1845
|
return;
|
|
1833
1846
|
}
|
|
1834
1847
|
if (ctx.opts.verbose && result.steps.length > 0) {
|
|
@@ -1837,9 +1850,10 @@ function renderRecordVerification(result, tsaResult, ctx) {
|
|
|
1837
1850
|
console.log(` - ${s}`);
|
|
1838
1851
|
console.log('');
|
|
1839
1852
|
}
|
|
1840
|
-
// ── Verdict block ──
|
|
1841
|
-
|
|
1853
|
+
// ── Verdict block ── Signature FIRST: authenticity establishes trust up front,
|
|
1854
|
+
// then integrity (recomputable vs redacted), then the (advisory) timestamp.
|
|
1842
1855
|
console.log(`${mark(result.signature)} Signature: ${label(result.signature)}`);
|
|
1856
|
+
console.log(`${mark(result.integrity)} Integrity: ${label(result.integrity)}`);
|
|
1843
1857
|
console.log(formatRecordTimestampLine(tsaResult.status));
|
|
1844
1858
|
if (tsaResult.status !== 'not-present' && genTimeIso) {
|
|
1845
1859
|
console.log(` Time: ${genTimeIso}`);
|
|
@@ -1885,11 +1899,34 @@ function renderRecordVerification(result, tsaResult, ctx) {
|
|
|
1885
1899
|
}
|
|
1886
1900
|
if (str(ctx.bundle.createdAt))
|
|
1887
1901
|
console.log(` Created: ${str(ctx.bundle.createdAt)}`);
|
|
1888
|
-
// ── Final verdict line ──
|
|
1902
|
+
// ── Final verdict line (3-state) ──
|
|
1903
|
+
// `verify-offline` (offlineMode) is a DISPLAY wrapper: a non-failed record is labelled
|
|
1904
|
+
// "AUTHENTIC (OFFLINE)" regardless of the redacted/verified split, while a failure stays
|
|
1905
|
+
// "FAILED". The 3-state core, exit code, and crypto are untouched.
|
|
1906
|
+
const offline = ctx.opts.offlineMode === true;
|
|
1889
1907
|
console.log('');
|
|
1890
|
-
console.log(result.status === '
|
|
1891
|
-
? 'FINAL STATUS:
|
|
1892
|
-
:
|
|
1908
|
+
console.log(result.status === 'failed'
|
|
1909
|
+
? 'FINAL STATUS: FAILED'
|
|
1910
|
+
: offline
|
|
1911
|
+
? 'FINAL STATUS: AUTHENTIC (OFFLINE)'
|
|
1912
|
+
: result.status === 'verified'
|
|
1913
|
+
? 'FINAL STATUS: VERIFIED EVIDENCE RECORD'
|
|
1914
|
+
: 'FINAL STATUS: AUTHENTIC (REDACTED)');
|
|
1915
|
+
// Mode / context block. In offline mode an explicit "Mode: Offline Verification" line
|
|
1916
|
+
// is always shown for a passing record; the redaction note is appended when the public
|
|
1917
|
+
// record was privacy-redacted. Online keeps the original redacted-only context block.
|
|
1918
|
+
if (offline && result.status !== 'failed') {
|
|
1919
|
+
console.log('Mode: Offline Verification');
|
|
1920
|
+
if (result.status === 'authentic-redacted') {
|
|
1921
|
+
console.log('Redaction: Applied by certification node (privacy mode)');
|
|
1922
|
+
console.log('! Full integrity verification requires access to original execution data');
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
else if (result.status === 'authentic-redacted') {
|
|
1926
|
+
console.log('Mode: Public Verification (Redacted)');
|
|
1927
|
+
console.log('Redaction: Applied by certification node (privacy mode)');
|
|
1928
|
+
console.log('! Full integrity verification requires access to original execution data');
|
|
1929
|
+
}
|
|
1893
1930
|
// ── Failure reasons (stderr) ──
|
|
1894
1931
|
if (result.status === 'failed' || result.reasons.length > 0) {
|
|
1895
1932
|
for (const r of result.reasons)
|
|
@@ -2544,7 +2581,7 @@ if (_isMainModule) {
|
|
|
2544
2581
|
default: false,
|
|
2545
2582
|
})
|
|
2546
2583
|
.option('short', {
|
|
2547
|
-
describe: 'Print only a single verdict line (✔ VERIFIED / ✖ FAILED)',
|
|
2584
|
+
describe: 'Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)',
|
|
2548
2585
|
type: 'boolean',
|
|
2549
2586
|
default: false,
|
|
2550
2587
|
})
|
|
@@ -2574,6 +2611,62 @@ if (_isMainModule) {
|
|
|
2574
2611
|
offline: argv.offline,
|
|
2575
2612
|
tsaRoots: argv['tsa-roots'],
|
|
2576
2613
|
});
|
|
2614
|
+
})
|
|
2615
|
+
.command('verify-offline <file>', 'Verify a CER record locally without contacting a NexArt node (offline-only alias of verify-record --offline)', (yargs) => {
|
|
2616
|
+
return yargs
|
|
2617
|
+
.positional('file', {
|
|
2618
|
+
describe: 'Path to the CER record JSON, or "-" to read it from stdin ({ bundle, verification?, timestamp? } or a bare bundle)',
|
|
2619
|
+
type: 'string',
|
|
2620
|
+
demandOption: true,
|
|
2621
|
+
})
|
|
2622
|
+
.option('keys', {
|
|
2623
|
+
alias: 'public-key',
|
|
2624
|
+
describe: 'Node public key(s) for signature verification (PEM, JWK, NodeKeysDocument, or raw base64url). Falls back to a key embedded in the record',
|
|
2625
|
+
type: 'string',
|
|
2626
|
+
})
|
|
2627
|
+
.option('kid', {
|
|
2628
|
+
describe: 'Key id to select from the key set (default: the receipt attestorKeyId)',
|
|
2629
|
+
type: 'string',
|
|
2630
|
+
})
|
|
2631
|
+
.option('json', {
|
|
2632
|
+
describe: 'Output machine-readable JSON',
|
|
2633
|
+
type: 'boolean',
|
|
2634
|
+
default: false,
|
|
2635
|
+
})
|
|
2636
|
+
.option('short', {
|
|
2637
|
+
describe: 'Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)',
|
|
2638
|
+
type: 'boolean',
|
|
2639
|
+
default: false,
|
|
2640
|
+
})
|
|
2641
|
+
.option('verbose', {
|
|
2642
|
+
describe: 'Show intermediate verification steps',
|
|
2643
|
+
type: 'boolean',
|
|
2644
|
+
default: false,
|
|
2645
|
+
})
|
|
2646
|
+
.option('tsa-roots', {
|
|
2647
|
+
describe: 'Override the default DigiCert TSA trust roots with custom root certificate(s) (PEM)',
|
|
2648
|
+
type: 'string',
|
|
2649
|
+
})
|
|
2650
|
+
.example('$0 verify-offline record.json --keys node-keys.json', 'Fully offline verification with an explicit key file')
|
|
2651
|
+
.example('$0 verify-offline record.json', 'Offline verification using a key embedded in the record')
|
|
2652
|
+
.epilogue('This verifies that the record was signed by a NexArt node and has not been tampered with. ' +
|
|
2653
|
+
'It does not re-execute the workload, and never contacts a node.');
|
|
2654
|
+
}, async (argv) => {
|
|
2655
|
+
// yargs collapses a bare "-" positional to an empty string; restore the stdin sentinel.
|
|
2656
|
+
const file = argv.file === '' ? '-' : argv.file;
|
|
2657
|
+
await verifyRecordStandaloneCommand(file, {
|
|
2658
|
+
// `keys` is the alias for `public-key`; yargs populates both, prefer either.
|
|
2659
|
+
publicKey: argv.keys ?? argv['public-key'],
|
|
2660
|
+
kid: argv.kid,
|
|
2661
|
+
json: argv.json,
|
|
2662
|
+
short: argv.short,
|
|
2663
|
+
verbose: argv.verbose,
|
|
2664
|
+
tsaRoots: argv['tsa-roots'],
|
|
2665
|
+
// verify-offline is offline by definition: no network for keys or anything else.
|
|
2666
|
+
offline: true,
|
|
2667
|
+
// Display-only: label the report as an offline verification.
|
|
2668
|
+
offlineMode: true,
|
|
2669
|
+
});
|
|
2577
2670
|
})
|
|
2578
2671
|
.command('verify-hash <certificate_hash>', 'Resolve a certificate hash to its public CER record and verify it — one-command verification', (yargs) => {
|
|
2579
2672
|
return yargs
|
|
@@ -2601,7 +2694,7 @@ if (_isMainModule) {
|
|
|
2601
2694
|
default: false,
|
|
2602
2695
|
})
|
|
2603
2696
|
.option('short', {
|
|
2604
|
-
describe: 'Print only a single verdict line (✔ VERIFIED / ✖ FAILED)',
|
|
2697
|
+
describe: 'Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)',
|
|
2605
2698
|
type: 'boolean',
|
|
2606
2699
|
default: false,
|
|
2607
2700
|
})
|
|
@@ -2654,7 +2747,7 @@ if (_isMainModule) {
|
|
|
2654
2747
|
default: false,
|
|
2655
2748
|
})
|
|
2656
2749
|
.option('short', {
|
|
2657
|
-
describe: 'Print only a single verdict line (✔ VERIFIED / ✖ FAILED)',
|
|
2750
|
+
describe: 'Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)',
|
|
2658
2751
|
type: 'boolean',
|
|
2659
2752
|
default: false,
|
|
2660
2753
|
})
|