provared 0.2.0 → 0.4.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 +324 -0
- package/README.md +134 -653
- package/bin/provared-check.mjs +39 -2
- package/docs/guide.md +745 -0
- package/docs/threat-model.md +16 -7
- package/package.json +25 -2
- package/page/index.html +1 -1
- package/page/style.css +3 -0
- package/spec/provared-format.md +30 -7
- package/src/dev.d.ts +54 -0
- package/src/dev.js +123 -0
- package/src/guard.js +73 -5
- package/src/recorder.js +12 -2
- package/src/signatures.js +7 -0
- package/src/types.d.ts +2 -0
package/bin/provared-check.mjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// It reads one file and writes to the terminal. It makes no network request.
|
|
20
20
|
|
|
21
21
|
import { readFileSync } from 'node:fs';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
22
23
|
import { MIN_BLOCKS_AFTER, REFUSAL_REASONS, blocksInChain, checkBook, checkHeaderChain, checkShow, conditionWords, limitWords, neverWords } from '../src/check.js';
|
|
23
24
|
|
|
24
25
|
// Node.js marks its ML-DSA and SLH-DSA as experimental and says so on every
|
|
@@ -31,8 +32,11 @@ process.on('warning', (w) => {
|
|
|
31
32
|
const USAGE = `Usage: provared-check <file> [--issuer <thumbprint>]... [--sealer <fingerprint>]... [--stamp-service <fingerprint>]...
|
|
32
33
|
[--block <fingerprint>]... [--voucher <fingerprint>]... [--disclosures <file>] [--cancellation <file>]...
|
|
33
34
|
[--root <top fingerprint>] [--size <entries>] [--headers <file>] [--json]
|
|
35
|
+
provared-check --sample [--json]
|
|
34
36
|
|
|
35
37
|
<file> a book (one entry to a line) or a Show (one JSON object)
|
|
38
|
+
--sample check the sample record that comes with the library, naming the passkey, the recorder and the
|
|
39
|
+
time-stamp service it was made with as trusted (they are stand-ins, and the people are invented)
|
|
36
40
|
--issuer the thumbprint of an issuer key you already trust; may be repeated
|
|
37
41
|
--sealer the fingerprint of the key set of a recorder you expect to have sealed the book; may be repeated
|
|
38
42
|
--stamp-service the fingerprint of the certificate of a time-stamp service you trust; may be repeated
|
|
@@ -64,13 +68,16 @@ const ONCE = { '--disclosures': 'disclosures', '--root': 'expectedRoot', '--size
|
|
|
64
68
|
// fingerprint is refused, never set aside: a typing error must not quietly
|
|
65
69
|
// leave a time-stamp or a name uncounted.
|
|
66
70
|
function parseArguments(argv) {
|
|
67
|
-
const out = { file: null, issuerKeys: [], sealKeys: [], stampServices: [], blocks: [], vouchers: [], cancellations: [], disclosures: undefined, expectedRoot: undefined, expectedSize: undefined, json: false };
|
|
71
|
+
const out = { file: null, sample: false, issuerKeys: [], sealKeys: [], stampServices: [], blocks: [], vouchers: [], cancellations: [], disclosures: undefined, expectedRoot: undefined, expectedSize: undefined, json: false };
|
|
68
72
|
for (let i = 0; i < argv.length; i++) {
|
|
69
73
|
const a = argv[i];
|
|
70
74
|
if (a === '--help' || a === '-h') return { help: true };
|
|
71
75
|
if (a === '--json') {
|
|
72
76
|
if (out.json) return { error: '--json may be given once.' };
|
|
73
77
|
out.json = true;
|
|
78
|
+
} else if (a === '--sample') {
|
|
79
|
+
if (out.sample) return { error: '--sample may be given once.' };
|
|
80
|
+
out.sample = true;
|
|
74
81
|
} else if (Object.hasOwn(LISTS, a) || Object.hasOwn(ONCE, a)) {
|
|
75
82
|
if (i + 1 >= argv.length) return { error: `${a} needs a value.` };
|
|
76
83
|
const value = argv[++i];
|
|
@@ -85,6 +92,28 @@ function parseArguments(argv) {
|
|
|
85
92
|
} else if (!a.startsWith('-') && out.file === null) out.file = a;
|
|
86
93
|
else return { error: `${a.startsWith('-') ? 'Unknown option' : 'More than one file'}: ${safe(a)}` };
|
|
87
94
|
}
|
|
95
|
+
if (out.sample) {
|
|
96
|
+
if (out.file !== null) return { error: '--sample names the sample record itself: give no file with it.' };
|
|
97
|
+
for (const name of ['issuerKeys', 'sealKeys', 'stampServices', 'blocks', 'vouchers', 'cancellations']) {
|
|
98
|
+
if (out[name].length) return { error: '--sample names what to trust itself: give no other option with it but --json.' };
|
|
99
|
+
}
|
|
100
|
+
if (out.disclosures !== undefined || out.expectedRoot !== undefined || out.expectedSize !== undefined || out.headers !== undefined) {
|
|
101
|
+
return { error: '--sample names what to trust itself: give no other option with it but --json.' };
|
|
102
|
+
}
|
|
103
|
+
// The sample, and the fingerprints of the stand-ins it was made with,
|
|
104
|
+
// come with the library (samples/office-supplies.expected.json).
|
|
105
|
+
let expected;
|
|
106
|
+
try {
|
|
107
|
+
expected = JSON.parse(readFileSync(new URL('../samples/office-supplies.expected.json', import.meta.url), 'utf8'));
|
|
108
|
+
} catch {
|
|
109
|
+
return { error: 'The sample record that comes with the library could not be read.' };
|
|
110
|
+
}
|
|
111
|
+
out.file = fileURLToPath(new URL('../samples/office-supplies.jsonl', import.meta.url));
|
|
112
|
+
out.issuerKeys = [expected.issuerKey];
|
|
113
|
+
out.sealKeys = [...expected.sealKeys];
|
|
114
|
+
out.stampServices = [...expected.stampServices];
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
88
117
|
return out.file === null ? { error: 'No file was named.' } : out;
|
|
89
118
|
}
|
|
90
119
|
|
|
@@ -428,7 +457,9 @@ function named(s) {
|
|
|
428
457
|
}
|
|
429
458
|
|
|
430
459
|
function trustWords(s) {
|
|
431
|
-
|
|
460
|
+
const passkeys = new Set(args.issuerKeys).size === 1 ? 'the passkey you trust' : 'the passkeys you trust';
|
|
461
|
+
const recorders = new Set(args.sealKeys).size === 1 ? 'the recorder you expect' : 'the recorders you expect';
|
|
462
|
+
if (named(s)) return s.counts.seals ? `yes: ${passkeys}, and ${recorders}` : `yes: ${passkeys}`;
|
|
432
463
|
if (args.issuerKeys.length === 0) return 'NO: you named no passkey you trust (--issuer), so anyone could have made this record. This is not a pass.';
|
|
433
464
|
return 'NO: the record has a seal, and you named no recorder you expect (--sealer), so anyone could have sealed it. This is not a pass.';
|
|
434
465
|
}
|
|
@@ -469,6 +500,12 @@ if (args.error) {
|
|
|
469
500
|
process.exit(2);
|
|
470
501
|
}
|
|
471
502
|
|
|
503
|
+
if (args.sample && !args.json) {
|
|
504
|
+
console.log('The sample record that comes with the library: an invented office agent, under a permission signed with a stand-in for a passkey.');
|
|
505
|
+
console.log('Trusted for this check, as the sample was made with them: the stand-in passkey, the recorder and the time-stamp service.');
|
|
506
|
+
console.log(` --issuer ${args.issuerKeys[0]} --sealer ${args.sealKeys[0]} --stamp-service ${args.stampServices[0]}\n`);
|
|
507
|
+
}
|
|
508
|
+
|
|
472
509
|
let text;
|
|
473
510
|
try {
|
|
474
511
|
text = readFileSync(args.file, 'utf8');
|