@parmana/verifier 1.6.2 → 1.6.3
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 +44 -10
- package/bin/parmana-verify.mjs +1 -1
- package/package.json +1 -1
- package/src/trust.mjs +9 -1
- package/src/verify.mjs +18 -4
package/README.md
CHANGED
|
@@ -25,7 +25,16 @@ node bin/parmana-verify.mjs test/vectors/record-whole-record-v3.json test/vector
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
|
-
{
|
|
28
|
+
{
|
|
29
|
+
"valid": true,
|
|
30
|
+
"coverage": "full",
|
|
31
|
+
"errors": [],
|
|
32
|
+
"checks": {
|
|
33
|
+
"trustRecordHash": true,
|
|
34
|
+
"signature": true,
|
|
35
|
+
"hybridSignatures": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
29
38
|
```
|
|
30
39
|
|
|
31
40
|
The exit code is `0`. A record that has been changed is rejected:
|
|
@@ -36,6 +45,15 @@ node bin/parmana-verify.mjs test/vectors/record-tampered-amount.json test/vector
|
|
|
36
45
|
|
|
37
46
|
The exit code is `1`, and the output lists the failed checks. The test vectors are labelled test material; see [test/vectors/README.md](test/vectors/README.md). For real records, pin the keys you trust (see [Pinning a key](#pinning-a-key)).
|
|
38
47
|
|
|
48
|
+
On Windows PowerShell, run the same two commands with Windows paths. Use Node 24 or later:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
node bin\parmana-verify.mjs test\vectors\record-whole-record-v3.json test\vectors\trust-whole-record.json
|
|
52
|
+
node bin\parmana-verify.mjs test\vectors\record-tampered-amount.json test\vectors\trust.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The first prints the valid result above and exits with `0`. The second prints `"valid":false` and exits with `1`.
|
|
56
|
+
|
|
39
57
|
## Installation and usage
|
|
40
58
|
|
|
41
59
|
The package is published on npm as [`@parmana/verifier`](https://www.npmjs.com/package/@parmana/verifier). Install it, or run it without installing:
|
|
@@ -104,6 +122,15 @@ A key is used only if all of these hold:
|
|
|
104
122
|
|
|
105
123
|
A compromised key is rejected whatever the timestamp, so signatures it made stay void.
|
|
106
124
|
|
|
125
|
+
### Records with no hybrid envelope
|
|
126
|
+
|
|
127
|
+
A record with no `signatures` array is a legacy-only record. The verifier refuses it by default. It is accepted only when:
|
|
128
|
+
|
|
129
|
+
- the trust file sets `hybridRequiredFrom` and the record was created before that instant; or
|
|
130
|
+
- the trust file sets `"allowLegacyOnly": true` and does not set `hybridRequiredFrom`.
|
|
131
|
+
|
|
132
|
+
Otherwise the result is invalid, and the error says which setting would accept it. A record created at or after `hybridRequiredFrom` always needs a valid hybrid envelope, whatever `allowLegacyOnly` says. See [docs/FORMAT.md](docs/FORMAT.md#trust-set-trustjson).
|
|
133
|
+
|
|
107
134
|
## Examples
|
|
108
135
|
|
|
109
136
|
Run every example with:
|
|
@@ -141,7 +168,7 @@ The suite covers the legacy signature and hash, hybrid signatures, schema versio
|
|
|
141
168
|
- The verifier can only check that a record was signed by the key you pinned. It cannot prove that the key belongs to Parmana. That depends on how you obtained and compared the fingerprint.
|
|
142
169
|
- For records with `schemaVersion` 2, or with no `schemaVersion`, only the fields listed in [docs/FORMAT.md](docs/FORMAT.md) are protected. Any other field can be changed without the verifier noticing. Read decisions only from covered fields. See "Fields not covered by any signature" in that document.
|
|
143
170
|
- For records with `schemaVersion` 3, every field except the signature envelope, `verifications` and `receipts` is protected. Check that `coverage` is `full` when that matters.
|
|
144
|
-
- A record with `schemaVersion` 2 can be downgraded by someone who can write to storage, since `schemaVersion` is unsigned. Set `hybridRequiredFrom` in the trust file to reject downgraded records created after a cutover. Records before the cutover stay open to this.
|
|
171
|
+
- A record with `schemaVersion` 2 can be downgraded by someone who can write to storage, since `schemaVersion` is unsigned. Set `hybridRequiredFrom` in the trust file to reject downgraded records created after a cutover. Records before the cutover stay open to this. With no cutover set, a record with no hybrid envelope is refused unless the trust file sets `allowLegacyOnly` to `true` (see "Records with no hybrid envelope" above).
|
|
145
172
|
- The validity window is checked against the record's `createdAt`, not against the signing time. A record that carries a false `createdAt` is checked against the window for that false time.
|
|
146
173
|
- Keys and fingerprints must be kept current. The verifier does not know about rotations or revocations that you have not written into the trust file.
|
|
147
174
|
- It does not verify the policy the record cites, the authorization decision logic, or the state of any connector or external system.
|
|
@@ -166,9 +193,12 @@ Every report is listed once, here. Start with the question you have.
|
|
|
166
193
|
|
|
167
194
|
**Releases**
|
|
168
195
|
|
|
196
|
+
- **Published versions:** `@parmana/verifier` 1.6.2 on npm and `parmana-verifier` 1.6.2 on PyPI, from release `verifier-v1.6.2`.
|
|
169
197
|
- **Changelog:** [CHANGELOG.md](CHANGELOG.md) lists each change to the record formats and the command-line interface.
|
|
198
|
+
- **Release process:** [RELEASING.md](RELEASING.md) describes how a release is built, checked and published.
|
|
170
199
|
|
|
171
200
|
---
|
|
201
|
+
|
|
172
202
|
## Read the Parmana blog
|
|
173
203
|
|
|
174
204
|
The [Parmana blog](https://blog.parmanasystems.com) explains how the verifier fits into the wider product, in plain terms. Read the post for your role:
|
|
@@ -183,16 +213,20 @@ New posts are published regularly, so check the blog for updates.
|
|
|
183
213
|
|
|
184
214
|
This is version 1.6.2, released with the SDK packages. Each change to the record formats or the command-line interface is listed in [CHANGELOG.md](CHANGELOG.md).
|
|
185
215
|
|
|
216
|
+
On 2026-10-29, on Node 24.22.1 and Windows PowerShell, the two quick-start commands above returned the output shown, with exit codes `0` and `1`. The release process builds the packages, records their hashes, and attaches build provenance. See [RELEASING.md](RELEASING.md) for the steps.
|
|
217
|
+
|
|
218
|
+
What this version does not do is listed under [Limitations](#limitations).
|
|
219
|
+
|
|
186
220
|
## Repository layout
|
|
187
221
|
|
|
188
|
-
| Path
|
|
189
|
-
|
|
|
190
|
-
| `bin/`
|
|
191
|
-
| `src/`
|
|
192
|
-
| `test/`
|
|
193
|
-
| `examples/`
|
|
194
|
-
| `docs/`
|
|
195
|
-
| `reports/`
|
|
222
|
+
| Path | Contents |
|
|
223
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
224
|
+
| `bin/` | The `parmana-verify` command-line entry point |
|
|
225
|
+
| `src/` | The checks: canonical JSON, hashing, signatures, trust rules |
|
|
226
|
+
| `test/` | The test suite, and `test/vectors/`, the labelled test records |
|
|
227
|
+
| `examples/` | Nine runnable examples, run by `npm run examples` |
|
|
228
|
+
| `docs/` | `FORMAT.md` (the input and output rules) and `SECURITY-REVIEW.md` |
|
|
229
|
+
| `reports/` | Pointer to the reports index in this README (see Reports and evidence) |
|
|
196
230
|
|
|
197
231
|
## Security
|
|
198
232
|
|
package/bin/parmana-verify.mjs
CHANGED
|
@@ -46,7 +46,7 @@ try {
|
|
|
46
46
|
process.exit(2);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const result = verifyExecutionTrustRecord(proof, policy.keys, { hybridRequiredFrom: policy.hybridRequiredFrom ?? undefined });
|
|
49
|
+
const result = verifyExecutionTrustRecord(proof, policy.keys, { hybridRequiredFrom: policy.hybridRequiredFrom ?? undefined, allowLegacyOnly: policy.allowLegacyOnly });
|
|
50
50
|
|
|
51
51
|
console.log(JSON.stringify(result));
|
|
52
52
|
|
package/package.json
CHANGED
package/src/trust.mjs
CHANGED
|
@@ -47,6 +47,14 @@ export function parseTrustPolicy(document) {
|
|
|
47
47
|
hybridRequiredFrom = document.hybridRequiredFrom;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Off by default: a record with no hybrid envelope is refused unless the
|
|
51
|
+
// trust file explicitly allows legacy-only records.
|
|
52
|
+
if (document.allowLegacyOnly !== undefined && typeof document.allowLegacyOnly !== "boolean") {
|
|
53
|
+
throw new Error('allowLegacyOnly must be true or false');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const allowLegacyOnly = document.allowLegacyOnly === true;
|
|
57
|
+
|
|
50
58
|
const trustKeys = new Map();
|
|
51
59
|
|
|
52
60
|
for (const [index, entry] of document.keys.entries()) {
|
|
@@ -100,5 +108,5 @@ export function parseTrustPolicy(document) {
|
|
|
100
108
|
});
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
return { keys: trustKeys, hybridRequiredFrom };
|
|
111
|
+
return { keys: trustKeys, hybridRequiredFrom, allowLegacyOnly };
|
|
104
112
|
}
|
package/src/verify.mjs
CHANGED
|
@@ -116,16 +116,30 @@ export function verifyExecutionTrustRecord(record, trustKeys, options = {}) {
|
|
|
116
116
|
checks.hybridSignatures = false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
// A record with no hybrid envelope is accepted only when the caller allows
|
|
120
|
+
// it: explicitly (allowLegacyOnly), or because it predates a cutover the
|
|
121
|
+
// caller supplied. Otherwise it is refused, so a stripped envelope never
|
|
122
|
+
// passes by default.
|
|
123
|
+
const cutoverGiven = options.hybridRequiredFrom !== undefined && options.hybridRequiredFrom !== null;
|
|
124
|
+
const predatesCutover = cutoverGiven && !isAtOrAfterCutover(record.createdAt, options.hybridRequiredFrom);
|
|
125
|
+
// With a cutover, the cutover alone decides; allowLegacyOnly applies only when none is given.
|
|
126
|
+
const legacyOnlyAllowed = cutoverGiven ? predatesCutover : options.allowLegacyOnly === true;
|
|
127
|
+
|
|
128
|
+
// A record created at or after the cutover needs a valid envelope, whether it
|
|
129
|
+
// is missing or present but invalid.
|
|
130
|
+
const atOrAfterCutover = cutoverGiven && !predatesCutover;
|
|
131
|
+
if (atOrAfterCutover && checks.hybridSignatures !== true) {
|
|
122
132
|
errors.push(`record created at or after hybridRequiredFrom (${options.hybridRequiredFrom}) has no valid hybrid envelope.`);
|
|
133
|
+
} else if (checks.hybridSignatures === undefined && !legacyOnlyAllowed) {
|
|
134
|
+
errors.push("record has no hybrid envelope: set hybridRequiredFrom or allowLegacyOnly to accept a legacy-only record.");
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
const envelopeOk = checks.hybridSignatures === undefined ? legacyOnlyAllowed : checks.hybridSignatures;
|
|
138
|
+
|
|
125
139
|
const coverage = schemaVersion === WHOLE_RECORD_SCHEMA_VERSION && checks.hybridSignatures === true ? "full" : "legacy";
|
|
126
140
|
|
|
127
141
|
return {
|
|
128
|
-
valid: hashValid && signatureValid &&
|
|
142
|
+
valid: hashValid && signatureValid && envelopeOk,
|
|
129
143
|
coverage,
|
|
130
144
|
errors,
|
|
131
145
|
checks,
|