@zincapp/znvault-cli 4.19.0 → 4.21.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.
Files changed (43) hide show
  1. package/README.md +8 -6
  2. package/dist/commands/agent/helpers.js +2 -2
  3. package/dist/commands/agent/helpers.js.map +1 -1
  4. package/dist/commands/kmip/index.js +1 -1
  5. package/dist/commands/kmip/index.js.map +1 -1
  6. package/dist/commands/lmk-escrow.d.ts.map +1 -1
  7. package/dist/commands/lmk-escrow.js +36 -0
  8. package/dist/commands/lmk-escrow.js.map +1 -1
  9. package/dist/commands/mysql/index.js +2 -2
  10. package/dist/commands/mysql/index.js.map +1 -1
  11. package/dist/commands/secret/create.d.ts.map +1 -1
  12. package/dist/commands/secret/create.js +48 -4
  13. package/dist/commands/secret/create.js.map +1 -1
  14. package/dist/commands/secret/decrypt.js +1 -1
  15. package/dist/commands/secret/index.js +2 -2
  16. package/dist/commands/secret/types.d.ts +1 -0
  17. package/dist/commands/secret/types.d.ts.map +1 -1
  18. package/dist/commands/superadmin/index.d.ts.map +1 -1
  19. package/dist/commands/superadmin/index.js +2 -0
  20. package/dist/commands/superadmin/index.js.map +1 -1
  21. package/dist/commands/superadmin/rootkey.d.ts +13 -0
  22. package/dist/commands/superadmin/rootkey.d.ts.map +1 -0
  23. package/dist/commands/superadmin/rootkey.js +223 -0
  24. package/dist/commands/superadmin/rootkey.js.map +1 -0
  25. package/dist/lib/config/store.d.ts.map +1 -1
  26. package/dist/lib/config/store.js +3 -0
  27. package/dist/lib/config/store.js.map +1 -1
  28. package/dist/lib/lmk-escrow-restore.d.ts +33 -0
  29. package/dist/lib/lmk-escrow-restore.d.ts.map +1 -0
  30. package/dist/lib/lmk-escrow-restore.js +124 -0
  31. package/dist/lib/lmk-escrow-restore.js.map +1 -0
  32. package/dist/lib/lmk-escrow.d.ts +12 -0
  33. package/dist/lib/lmk-escrow.d.ts.map +1 -1
  34. package/dist/lib/lmk-escrow.js +91 -64
  35. package/dist/lib/lmk-escrow.js.map +1 -1
  36. package/dist/lib/stdin.d.ts +11 -0
  37. package/dist/lib/stdin.d.ts.map +1 -0
  38. package/dist/lib/stdin.js +27 -0
  39. package/dist/lib/stdin.js.map +1 -0
  40. package/dist/services/signature-verifier.js +1 -1
  41. package/dist/types/update.js +1 -1
  42. package/dist/types/update.js.map +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,223 @@
1
+ // Path: src/commands/superadmin/rootkey.ts
2
+ /**
3
+ * `znvault superadmin rootkey` — root-of-trust provider chain for the
4
+ * vault's bootstrap key (BSK).
5
+ *
6
+ * Routes (superadmin only, deployment-wide, no tenant scoping):
7
+ * GET /v1/superadmin/rootkey/status
8
+ * POST /v1/superadmin/rootkey/verify
9
+ * POST /v1/superadmin/rootkey/wrap { provider }
10
+ *
11
+ * Contract: no command prints key material — the server never returns
12
+ * any; the publishable KCV fingerprint is the only key identity shown.
13
+ * No command can remove an envelope or the cleartext key file
14
+ * (retirement is a separate, deliberate server-side procedure that has
15
+ * no API in this release). `verify` exits 1 when any configured provider
16
+ * fails or mismatches, so it can gate migrations and drive monitoring.
17
+ */
18
+ import Table from 'cli-table3';
19
+ import { client } from '../../lib/client.js';
20
+ import * as output from '../../lib/output.js';
21
+ // ─── Helpers ───────────────────────────────────────────────────────────────
22
+ function formatDate(iso) {
23
+ if (!iso)
24
+ return '-';
25
+ try {
26
+ return new Date(iso).toLocaleString();
27
+ }
28
+ catch {
29
+ return iso;
30
+ }
31
+ }
32
+ // ─── Handlers ──────────────────────────────────────────────────────────────
33
+ export async function rootkeyStatus(options) {
34
+ const spinner = output.spinner('Fetching root key provider status...').start();
35
+ try {
36
+ const response = await client.get('/v1/superadmin/rootkey/status');
37
+ spinner.stop();
38
+ if (options.json) {
39
+ output.json(response);
40
+ return;
41
+ }
42
+ const resolution = response.resolution;
43
+ // Effective degraded = boot resolution OR the latest periodic probe;
44
+ // status must never report last-boot nostalgia as current health.
45
+ const effectiveDegraded = resolution !== null &&
46
+ (resolution.degraded || resolution.lastProbe?.degraded === true);
47
+ output.section('Root Key Provider Status');
48
+ output.keyValue({
49
+ 'Degraded': resolution ? (effectiveDegraded ? 'YES — investigate' : 'no') : 'unknown (no resolution)',
50
+ 'Served by (last boot)': resolution?.servedBy ?? '-',
51
+ 'Active KCV': resolution?.kcv ?? '-',
52
+ 'Resolved at': formatDate(resolution?.resolvedAt),
53
+ 'Total latency': resolution ? `${String(resolution.totalLatencyMs)}ms` : '-',
54
+ 'Last probe': resolution?.lastProbe
55
+ ? `${formatDate(resolution.lastProbe.at)} — ${resolution.lastProbe.degraded ? 'DEGRADED' : 'healthy'}`
56
+ : 'never (probe has not run yet)',
57
+ 'Cleartext file on this node': response.localFile.present ? 'present' : 'absent',
58
+ });
59
+ if (effectiveDegraded) {
60
+ output.warn('The chain is DEGRADED: a configured provider failed or had no material ' +
61
+ 'at the last resolution or probe. The node is up, but redundancy is reduced.');
62
+ }
63
+ if (resolution) {
64
+ const table = new Table({
65
+ head: ['Priority', 'Provider', 'Outcome', 'Latency'],
66
+ style: { head: ['cyan'] },
67
+ });
68
+ for (const attempt of resolution.attempts) {
69
+ const priority = resolution.configured.find((c) => c.id === attempt.providerId)?.priority ?? '-';
70
+ table.push([
71
+ String(priority),
72
+ attempt.providerId,
73
+ attempt.outcome + (attempt.error ? ` (${attempt.error})` : ''),
74
+ attempt.latencyMs === undefined ? '-' : `${String(attempt.latencyMs)}ms`,
75
+ ]);
76
+ }
77
+ console.log(table.toString());
78
+ }
79
+ if (resolution?.lastProbe) {
80
+ const probeTable = new Table({
81
+ head: ['Probe: Provider', 'Outcome', 'KCV', 'Latency'],
82
+ style: { head: ['cyan'] },
83
+ });
84
+ for (const result of resolution.lastProbe.results) {
85
+ probeTable.push([
86
+ result.providerId,
87
+ result.outcome + (result.error ? ` (${result.error})` : ''),
88
+ result.kcv ?? '-',
89
+ `${String(result.latencyMs)}ms`,
90
+ ]);
91
+ }
92
+ console.log(probeTable.toString());
93
+ }
94
+ if (response.envelopes.length > 0) {
95
+ const table = new Table({
96
+ head: ['Envelope', 'Key ID', 'KCV', 'Updated'],
97
+ style: { head: ['cyan'] },
98
+ });
99
+ for (const envelope of response.envelopes) {
100
+ table.push([
101
+ envelope.provider_id,
102
+ envelope.key_id ?? '-',
103
+ envelope.kcv,
104
+ formatDate(envelope.updated_at),
105
+ ]);
106
+ }
107
+ console.log(table.toString());
108
+ }
109
+ else {
110
+ output.info('No provider envelopes exist yet (create one with: rootkey wrap --provider <id>).');
111
+ }
112
+ }
113
+ catch (err) {
114
+ spinner.fail('Failed to fetch root key status');
115
+ output.error(err instanceof Error ? err.message : String(err));
116
+ process.exit(1);
117
+ }
118
+ }
119
+ export async function rootkeyVerify(options) {
120
+ const spinner = output.spinner('Verifying every configured root key provider...').start();
121
+ try {
122
+ const response = await client.post('/v1/superadmin/rootkey/verify', {});
123
+ spinner.stop();
124
+ if (options.json) {
125
+ output.json(response);
126
+ }
127
+ else {
128
+ const table = new Table({
129
+ head: ['Provider', 'Outcome', 'KCV', 'Latency'],
130
+ style: { head: ['cyan'] },
131
+ });
132
+ for (const result of response.results) {
133
+ table.push([
134
+ result.providerId,
135
+ result.outcome + (result.error ? ` (${result.error})` : ''),
136
+ result.kcv ?? '-',
137
+ `${String(result.latencyMs)}ms`,
138
+ ]);
139
+ }
140
+ console.log(table.toString());
141
+ }
142
+ if (response.allMatch) {
143
+ if (!options.json) {
144
+ output.success(`All configured providers agree with the active key (KCV ${response.activeKcv}).`);
145
+ }
146
+ return;
147
+ }
148
+ // STRICT gate: anything other than 'match' fails — including a
149
+ // provider with no material. A green result on an unprovisioned
150
+ // provider would let an operator reorder priorities onto nothing.
151
+ const failing = response.results
152
+ .filter((r) => r.outcome !== 'match')
153
+ .map((r) => r.providerId);
154
+ output.error(`Root key verification FAILED for: ${failing.join(', ')}. ` +
155
+ 'Do not change provider priorities or retire anything until every ' +
156
+ 'configured provider verifies.');
157
+ process.exit(1);
158
+ }
159
+ catch (err) {
160
+ spinner.fail('Root key verification failed');
161
+ output.error(err instanceof Error ? err.message : String(err));
162
+ process.exit(1);
163
+ }
164
+ }
165
+ /**
166
+ * Mirror of the server's schema constraint. Enforced BEFORE any output or
167
+ * request: an operator who accidentally pastes a secret as --provider must
168
+ * see nothing echo it — not the spinner, not an error, not CI logs.
169
+ */
170
+ const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
171
+ export async function rootkeyWrap(options) {
172
+ if (!PROVIDER_ID_PATTERN.test(options.provider)) {
173
+ output.error('Invalid provider id: expected 1-32 lowercase letters, digits or hyphens ' +
174
+ "(e.g. 'aws-kms'). The value you passed is not echoed on purpose.");
175
+ process.exit(1);
176
+ }
177
+ const spinner = output.spinner(`Wrapping the bootstrap key into '${options.provider}'...`).start();
178
+ try {
179
+ const receipt = await client.post('/v1/superadmin/rootkey/wrap', { provider: options.provider });
180
+ spinner.stop();
181
+ if (options.json) {
182
+ output.json(receipt);
183
+ return;
184
+ }
185
+ output.section('Root Key Envelope Written');
186
+ output.keyValue({
187
+ 'Provider': receipt.providerId,
188
+ 'Key ID': receipt.keyId ?? '-',
189
+ 'KCV': receipt.kcv,
190
+ 'Created': formatDate(receipt.createdAt),
191
+ });
192
+ output.success(`Envelope for '${receipt.providerId}' persisted (replicates to every node via the database).`);
193
+ output.info("Next: run 'znvault superadmin rootkey verify' to prove every provider opens the same key.");
194
+ }
195
+ catch (err) {
196
+ spinner.fail('Failed to wrap the bootstrap key');
197
+ output.error(err instanceof Error ? err.message : String(err));
198
+ process.exit(1);
199
+ }
200
+ }
201
+ // ─── Registration ──────────────────────────────────────────────────────────
202
+ export function registerRootkeyCommands(parent) {
203
+ const rootkey = parent
204
+ .command('rootkey')
205
+ .description('Root-of-trust provider chain for the bootstrap key (superadmin only)');
206
+ rootkey
207
+ .command('status')
208
+ .description('Show configured providers, last-boot resolution, degraded flag and KCV')
209
+ .option('--json', 'Output as JSON')
210
+ .action(rootkeyStatus);
211
+ rootkey
212
+ .command('verify')
213
+ .description('Probe every configured provider and compare KCVs; exits 1 on any failure')
214
+ .option('--json', 'Output as JSON')
215
+ .action(rootkeyVerify);
216
+ rootkey
217
+ .command('wrap')
218
+ .description("Wrap the current bootstrap key into a provider's envelope (never retires anything)")
219
+ .requiredOption('--provider <id>', "Configured envelope provider id (e.g. 'aws-kms')")
220
+ .option('--json', 'Output the receipt as JSON')
221
+ .action(rootkeyWrap);
222
+ }
223
+ //# sourceMappingURL=rootkey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rootkey.js","sourceRoot":"","sources":["../../../src/commands/superadmin/rootkey.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/config/store.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6B9C;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,CAAC,WAAW,CAclC,CAAC;AAKH;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/config/store.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgC9C;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,CAAC,WAAW,CAclC,CAAC;AAKH;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
@@ -22,6 +22,9 @@ function getStoreInstance() {
22
22
  const configDir = process.env.ZNVAULT_CONFIG_DIR;
23
23
  _store = new Conf({
24
24
  projectName: 'znvault',
25
+ // Profiles contain rotating access/refresh tokens. Preserve 0600 on
26
+ // every atomic rewrite instead of inheriting a permissive process umask.
27
+ configFileMode: 0o600,
25
28
  // Use custom config directory if specified (for test isolation)
26
29
  ...(configDir ? { cwd: configDir } : {}),
27
30
  defaults: {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/lib/config/store.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAEhC;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,kCAAkC;AAClC,IAAI,MAAM,GAA6B,IAAI,CAAC;AAE5C;;;GAGG;AACH,SAAS,gBAAgB;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,8DAA8D;QAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAEjD,MAAM,GAAG,IAAI,IAAI,CAAc;YAC7B,WAAW,EAAE,SAAS;YACtB,gEAAgE;YAChE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,QAAQ,EAAE;gBACR,aAAa,EAAE,eAAe;gBAC9B,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;aACZ;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAsB,IAAI,KAAK,CAAC,EAAuB,EAAE;IACzE,GAAG,CAAC,OAAO,EAAE,IAA6B;QACxC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,OAAO,EAAE,IAA6B,EAAE,KAAc;QACxD,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACnC,QAA+C,CAAC,IAAc,CAAC,GAAG,KAAK,CAAC;QACzE,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC,CAAC;AAEH,oDAAoD;AACpD,IAAI,sBAAsB,GAAkB,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,sBAAsB,GAAG,OAAO,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,IAAI,CAAC;IACd,sBAAsB,GAAG,IAAI,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/lib/config/store.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAEhC;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,kCAAkC;AAClC,IAAI,MAAM,GAA6B,IAAI,CAAC;AAE5C;;;GAGG;AACH,SAAS,gBAAgB;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,8DAA8D;QAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAEjD,MAAM,GAAG,IAAI,IAAI,CAAc;YAC7B,WAAW,EAAE,SAAS;YACtB,oEAAoE;YACpE,yEAAyE;YACzE,cAAc,EAAE,KAAK;YACrB,gEAAgE;YAChE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,QAAQ,EAAE;gBACR,aAAa,EAAE,eAAe;gBAC9B,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;aACZ;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAsB,IAAI,KAAK,CAAC,EAAuB,EAAE;IACzE,GAAG,CAAC,OAAO,EAAE,IAA6B;QACxC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,OAAO,EAAE,IAA6B,EAAE,KAAc;QACxD,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACnC,QAA+C,CAAC,IAAc,CAAC,GAAG,KAAK,CAAC;QACzE,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC,CAAC;AAEH,oDAAoD;AACpD,IAAI,sBAAsB,GAAkB,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,sBAAsB,GAAG,OAAO,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,IAAI,CAAC;IACd,sBAAsB,GAAG,IAAI,CAAC;AAChC,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface RestoreBootstrapKeyOptions {
2
+ /** A complete escrow bundle. It is fully verified before anything is written. */
3
+ bundle: Buffer;
4
+ /** Where the bootstrap key file should end up, e.g. `${DATA_DIR}/lmk.bin`. */
5
+ targetPath: string;
6
+ }
7
+ export interface RestoreBootstrapKeyReport {
8
+ /**
9
+ * `RESTORED` — the file did not exist and was written.
10
+ * `ALREADY_PRESENT` — the file already held this exact key; nothing changed.
11
+ */
12
+ outcome: 'RESTORED' | 'ALREADY_PRESENT';
13
+ targetPath: string;
14
+ bundleId: string;
15
+ copyLabel: string;
16
+ activeLmkVersion: number;
17
+ /** Publishable fingerprint. Safe for logs, receipts and drill records. */
18
+ bskSha256: string;
19
+ }
20
+ /**
21
+ * Restore the bootstrap key carried by `bundle` to `targetPath`.
22
+ *
23
+ * Refuses, without touching anything, when the target already holds a
24
+ * *different* key. Replacing a live bootstrap key silently destroys every
25
+ * secret it protects, and the loss stays invisible until the next unwrap —
26
+ * so the only safe response is to stop and let a human look. Restoring a key
27
+ * that is already in place is a no-op, so the drill is repeatable.
28
+ *
29
+ * The write is atomic and read back before success is reported. A write that
30
+ * was not read back is not a restore.
31
+ */
32
+ export declare function restoreBootstrapKeyFromBundle(options: RestoreBootstrapKeyOptions): RestoreBootstrapKeyReport;
33
+ //# sourceMappingURL=lmk-escrow-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lmk-escrow-restore.d.ts","sourceRoot":"","sources":["../../src/lib/lmk-escrow-restore.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,0BAA0B;IACzC,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,OAAO,EAAE,UAAU,GAAG,iBAAiB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CAsD3B"}
@@ -0,0 +1,124 @@
1
+ // Path: src/lib/lmk-escrow-restore.ts
2
+ //
3
+ // Restore a bootstrap key from a verified escrow bundle.
4
+ //
5
+ // This is the other half of `lmk escrow snapshot`. Without it the escrow copy
6
+ // could be written and checked but never used, and zn-vault's startup guard —
7
+ // which now refuses to boot when DATA_DIR/lmk.bin is missing and lmk_versions
8
+ // holds real versions — pointed at a recovery path that had no implementation.
9
+ //
10
+ // SCOPE: this writes the plaintext bootstrap key file, which is the correct
11
+ // target while the deployment stores the BSK that way. If a root-key provider
12
+ // is later introduced and the plaintext file is retired, this module needs a
13
+ // matching mode that re-wraps instead. Until then the plaintext file is the
14
+ // supported recovery target and this is the supported way to produce it.
15
+ import { closeSync, existsSync, fsyncSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from 'node:fs';
16
+ import { timingSafeEqual } from 'node:crypto';
17
+ import { basename, dirname, isAbsolute, join } from 'node:path';
18
+ import { withVerifiedBootstrapKey, } from './lmk-escrow.js';
19
+ const BSK_BYTES = 32;
20
+ const FILE_MODE = 0o600;
21
+ /**
22
+ * Restore the bootstrap key carried by `bundle` to `targetPath`.
23
+ *
24
+ * Refuses, without touching anything, when the target already holds a
25
+ * *different* key. Replacing a live bootstrap key silently destroys every
26
+ * secret it protects, and the loss stays invisible until the next unwrap —
27
+ * so the only safe response is to stop and let a human look. Restoring a key
28
+ * that is already in place is a no-op, so the drill is repeatable.
29
+ *
30
+ * The write is atomic and read back before success is reported. A write that
31
+ * was not read back is not a restore.
32
+ */
33
+ export function restoreBootstrapKeyFromBundle(options) {
34
+ const { bundle, targetPath } = options;
35
+ if (!isAbsolute(targetPath) && !targetPath.startsWith('.')) {
36
+ // Relative paths are accepted (tests and DATA_DIR defaults use them) but a
37
+ // bare filename with no directory part has no safe temp-file location.
38
+ if (dirname(targetPath) === '.' && basename(targetPath) === targetPath) {
39
+ throw new Error('Bootstrap key target must include a directory');
40
+ }
41
+ }
42
+ const directory = dirname(targetPath);
43
+ if (!existsSync(directory)) {
44
+ throw new Error(`Bootstrap key target directory does not exist: ${directory}`);
45
+ }
46
+ // Verification first, always: a corrupted bundle must not create a file, not
47
+ // even an empty one.
48
+ return withVerifiedBootstrapKey(bundle, (bsk, report) => {
49
+ if (bsk.length !== BSK_BYTES) {
50
+ throw new Error('Verified bundle carries a bootstrap key of unexpected length');
51
+ }
52
+ if (existsSync(targetPath)) {
53
+ const current = readFileSync(targetPath);
54
+ const identical = current.length === bsk.length && timingSafeEqual(current, bsk);
55
+ current.fill(0);
56
+ if (identical) {
57
+ return describe('ALREADY_PRESENT', targetPath, report);
58
+ }
59
+ throw new Error(`Refusing to restore: ${targetPath} already holds a different bootstrap key. ` +
60
+ `Overwriting it would destroy every secret wrapped under the current key, ` +
61
+ `and the loss would not surface until the next unwrap. Move the existing ` +
62
+ `file aside deliberately, after establishing which key is correct, then ` +
63
+ `run this again.`);
64
+ }
65
+ writeAtomically(targetPath, bsk);
66
+ const readBack = readFileSync(targetPath);
67
+ const matches = readBack.length === bsk.length && timingSafeEqual(readBack, bsk);
68
+ readBack.fill(0);
69
+ if (!matches) {
70
+ unlinkSync(targetPath);
71
+ throw new Error('Bootstrap key read-back did not match the bundle; the partial file was removed');
72
+ }
73
+ return describe('RESTORED', targetPath, report);
74
+ });
75
+ }
76
+ function describe(outcome, targetPath, report) {
77
+ return {
78
+ outcome,
79
+ targetPath,
80
+ bundleId: report.bundleId,
81
+ copyLabel: report.copyLabel,
82
+ activeLmkVersion: report.activeLmkVersion,
83
+ bskSha256: report.bskSha256,
84
+ };
85
+ }
86
+ /**
87
+ * Write through a sibling temp file and rename, so an interrupted write can
88
+ * never leave a truncated bootstrap key where a whole one is expected. A
89
+ * truncated key is indistinguishable from a wrong one at rest and fails only
90
+ * later, which is the worst way for this to fail.
91
+ */
92
+ function writeAtomically(targetPath, contents) {
93
+ const directory = dirname(targetPath);
94
+ const temporary = join(directory, `.${basename(targetPath)}.restore-tmp`);
95
+ if (existsSync(temporary))
96
+ unlinkSync(temporary);
97
+ let handle = null;
98
+ try {
99
+ // wx: fail if it exists. Mode is set at creation, never widened later.
100
+ handle = openSync(temporary, 'wx', FILE_MODE);
101
+ writeSync(handle, contents, 0, contents.length, 0);
102
+ fsyncSync(handle);
103
+ closeSync(handle);
104
+ handle = null;
105
+ if (statSync(temporary).size !== contents.length) {
106
+ throw new Error('Short write while restoring the bootstrap key');
107
+ }
108
+ renameSync(temporary, targetPath);
109
+ }
110
+ catch (error) {
111
+ if (handle !== null) {
112
+ try {
113
+ closeSync(handle);
114
+ }
115
+ catch {
116
+ // The original error is the one worth reporting.
117
+ }
118
+ }
119
+ if (existsSync(temporary))
120
+ unlinkSync(temporary);
121
+ throw error;
122
+ }
123
+ }
124
+ //# sourceMappingURL=lmk-escrow-restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lmk-escrow-restore.js","sourceRoot":"","sources":["../../src/lib/lmk-escrow-restore.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,18 @@ export interface LmkEscrowWriteReceipt extends LmkEscrowVerificationReport {
40
40
  */
41
41
  export declare function buildLmkEscrowBundle(options: BuildLmkEscrowBundleOptions): Buffer;
42
42
  export declare function verifyLmkEscrowBundleBuffer(bundle: Buffer): LmkEscrowVerificationReport;
43
+ /**
44
+ * Run the full bundle verification and hand the live bootstrap key to `fn`.
45
+ *
46
+ * The key is never returned: it is valid only for the duration of the
47
+ * callback and is zeroised afterwards, on success and on failure alike.
48
+ * Callers must not copy it, log it, or let it escape the callback.
49
+ *
50
+ * Verification is not a formality here. It proves cryptographically that this
51
+ * BSK unwraps the LMK versions the bundle carries, so a bundle that passes has
52
+ * the right key, not merely a well-formed one.
53
+ */
54
+ export declare function withVerifiedBootstrapKey<T>(bundle: Buffer, fn: (bsk: Buffer, report: LmkEscrowVerificationReport) => T): T;
43
55
  export declare function makeLmkEscrowFilename(report: LmkEscrowVerificationReport): string;
44
56
  /**
45
57
  * Write once to the final filename, flush, reopen from the destination and
@@ -1 +1 @@
1
- {"version":3,"file":"lmk-escrow.d.ts","sourceRoot":"","sources":["../../src/lib/lmk-escrow.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAoF/D,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,yBAAyB,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,IAAI,CAAC;IACZ,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,UAAU,GAAG,uBAAuB,CAAC;IACrD,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,qBAAsB,SAAQ,2BAA2B;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AA8KD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,CA2CjF;AAgLD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,2BAA2B,CAyEvF;AAgCD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,MAAM,CAKjF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,2BAA2B,GACnC,qBAAqB,CA+CvB;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,CAa1F"}
1
+ {"version":3,"file":"lmk-escrow.d.ts","sourceRoot":"","sources":["../../src/lib/lmk-escrow.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAoF/D,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,yBAAyB,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,IAAI,CAAC;IACZ,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,UAAU,GAAG,uBAAuB,CAAC;IACrD,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,qBAAsB,SAAQ,2BAA2B;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AA8KD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,CA2CjF;AA0PD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,2BAA2B,CAOvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,KAAK,CAAC,GAC1D,CAAC,CAQH;AAgCD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,MAAM,CAKjF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,2BAA2B,GACnC,qBAAqB,CA+CvB;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,CAa1F"}
@@ -371,77 +371,104 @@ function parseBundle(bundle) {
371
371
  throw error;
372
372
  }
373
373
  }
374
- export function verifyLmkEscrowBundleBuffer(bundle) {
375
- const parsed = parseBundle(bundle);
376
- try {
377
- if (parsed.metadata.bskSha256 !== sha256Hex(parsed.bsk)) {
378
- throw new Error('BSK fingerprint does not match the escrow metadata');
374
+ function zeroiseParsedBundle(parsed) {
375
+ parsed.bsk.fill(0);
376
+ parsed.bundleDigest.fill(0);
377
+ for (const row of parsed.wrappedLmks)
378
+ row.wrapped?.fill(0);
379
+ }
380
+ function validateParsedBundle(parsed) {
381
+ if (parsed.metadata.bskSha256 !== sha256Hex(parsed.bsk)) {
382
+ throw new Error('BSK fingerprint does not match the escrow metadata');
383
+ }
384
+ if (parsed.metadata.lmkVersions.length !== parsed.wrappedLmks.length) {
385
+ throw new Error('LMK inventory count does not match binary records');
386
+ }
387
+ const recoverableVersions = [];
388
+ const unrecoverableVersions = [];
389
+ const seen = new Set();
390
+ let activeCount = 0;
391
+ let activeVersion = null;
392
+ for (let index = 0; index < parsed.wrappedLmks.length; index += 1) {
393
+ const row = parsed.wrappedLmks[index];
394
+ const metadata = parsed.metadata.lmkVersions[index];
395
+ if (row.version !== metadata.version || seen.has(row.version)) {
396
+ throw new Error('LMK inventory order/version mismatch');
379
397
  }
380
- if (parsed.metadata.lmkVersions.length !== parsed.wrappedLmks.length) {
381
- throw new Error('LMK inventory count does not match binary records');
398
+ seen.add(row.version);
399
+ if (metadata.status === 'ACTIVE') {
400
+ activeCount += 1;
401
+ activeVersion = row.version;
382
402
  }
383
- const recoverableVersions = [];
384
- const unrecoverableVersions = [];
385
- const seen = new Set();
386
- let activeCount = 0;
387
- let activeVersion = null;
388
- for (let index = 0; index < parsed.wrappedLmks.length; index += 1) {
389
- const row = parsed.wrappedLmks[index];
390
- const metadata = parsed.metadata.lmkVersions[index];
391
- if (row.version !== metadata.version || seen.has(row.version)) {
392
- throw new Error('LMK inventory order/version mismatch');
393
- }
394
- seen.add(row.version);
395
- if (metadata.status === 'ACTIVE') {
396
- activeCount += 1;
397
- activeVersion = row.version;
398
- }
399
- if (row.wrapped === null) {
400
- if (metadata.materialState !== 'UNRECOVERABLE' || metadata.wrappedBytes !== 0) {
401
- throw new Error(`LMK version ${String(row.version)} material-state mismatch`);
402
- }
403
- unrecoverableVersions.push(row.version);
403
+ if (row.wrapped === null) {
404
+ if (metadata.materialState !== 'UNRECOVERABLE' || metadata.wrappedBytes !== 0) {
405
+ throw new Error(`LMK version ${String(row.version)} material-state mismatch`);
404
406
  }
405
- else {
406
- if (metadata.materialState !== 'WRAPPED' || metadata.wrappedBytes !== row.wrapped.length) {
407
- throw new Error(`LMK version ${String(row.version)} wrapped-material mismatch`);
408
- }
409
- validateWrappedLmk(parsed.bsk, row.wrapped, row.version);
410
- recoverableVersions.push(row.version);
411
- }
412
- }
413
- if (activeCount !== 1)
414
- throw new Error(`Expected one ACTIVE LMK, found ${String(activeCount)}`);
415
- if (activeVersion !== parsed.metadata.activeLmkVersion) {
416
- throw new Error('ACTIVE LMK version does not match the escrow metadata summary');
417
- }
418
- if (!recoverableVersions.includes(parsed.metadata.activeLmkVersion)) {
419
- throw new Error('The ACTIVE LMK is not recoverable from this bundle');
407
+ unrecoverableVersions.push(row.version);
420
408
  }
421
- const complete = unrecoverableVersions.length === 0;
422
- if (parsed.metadata.allAvailableVersionsRecoverable !== complete) {
423
- throw new Error('Recoverability summary does not match LMK inventory');
409
+ else {
410
+ if (metadata.materialState !== 'WRAPPED' || metadata.wrappedBytes !== row.wrapped.length) {
411
+ throw new Error(`LMK version ${String(row.version)} wrapped-material mismatch`);
412
+ }
413
+ validateWrappedLmk(parsed.bsk, row.wrapped, row.version);
414
+ recoverableVersions.push(row.version);
424
415
  }
425
- return {
426
- valid: true,
427
- formatVersion: 1,
428
- bundleId: `sha256:${parsed.bundleDigest.toString('hex')}`,
429
- createdAt: parsed.metadata.createdAt,
430
- copyLabel: parsed.metadata.copyLabel,
431
- activeLmkVersion: parsed.metadata.activeLmkVersion,
432
- recoverability: complete ? 'COMPLETE' : 'KNOWN_HISTORICAL_GAPS',
433
- recoverableVersions,
434
- unrecoverableVersions,
435
- bskSha256: parsed.metadata.bskSha256,
436
- backupId: parsed.metadata.backup.state === 'VERIFIED' ? parsed.metadata.backup.id : null,
437
- activeRotationId: parsed.metadata.activeRotation?.rotationId ?? null,
438
- };
416
+ }
417
+ if (activeCount !== 1)
418
+ throw new Error(`Expected one ACTIVE LMK, found ${String(activeCount)}`);
419
+ if (activeVersion !== parsed.metadata.activeLmkVersion) {
420
+ throw new Error('ACTIVE LMK version does not match the escrow metadata summary');
421
+ }
422
+ if (!recoverableVersions.includes(parsed.metadata.activeLmkVersion)) {
423
+ throw new Error('The ACTIVE LMK is not recoverable from this bundle');
424
+ }
425
+ const complete = unrecoverableVersions.length === 0;
426
+ if (parsed.metadata.allAvailableVersionsRecoverable !== complete) {
427
+ throw new Error('Recoverability summary does not match LMK inventory');
428
+ }
429
+ return {
430
+ valid: true,
431
+ formatVersion: 1,
432
+ bundleId: `sha256:${parsed.bundleDigest.toString('hex')}`,
433
+ createdAt: parsed.metadata.createdAt,
434
+ copyLabel: parsed.metadata.copyLabel,
435
+ activeLmkVersion: parsed.metadata.activeLmkVersion,
436
+ recoverability: complete ? 'COMPLETE' : 'KNOWN_HISTORICAL_GAPS',
437
+ recoverableVersions,
438
+ unrecoverableVersions,
439
+ bskSha256: parsed.metadata.bskSha256,
440
+ backupId: parsed.metadata.backup.state === 'VERIFIED' ? parsed.metadata.backup.id : null,
441
+ activeRotationId: parsed.metadata.activeRotation?.rotationId ?? null,
442
+ };
443
+ }
444
+ export function verifyLmkEscrowBundleBuffer(bundle) {
445
+ const parsed = parseBundle(bundle);
446
+ try {
447
+ return validateParsedBundle(parsed);
439
448
  }
440
449
  finally {
441
- parsed.bsk.fill(0);
442
- parsed.bundleDigest.fill(0);
443
- for (const row of parsed.wrappedLmks)
444
- row.wrapped?.fill(0);
450
+ zeroiseParsedBundle(parsed);
451
+ }
452
+ }
453
+ /**
454
+ * Run the full bundle verification and hand the live bootstrap key to `fn`.
455
+ *
456
+ * The key is never returned: it is valid only for the duration of the
457
+ * callback and is zeroised afterwards, on success and on failure alike.
458
+ * Callers must not copy it, log it, or let it escape the callback.
459
+ *
460
+ * Verification is not a formality here. It proves cryptographically that this
461
+ * BSK unwraps the LMK versions the bundle carries, so a bundle that passes has
462
+ * the right key, not merely a well-formed one.
463
+ */
464
+ export function withVerifiedBootstrapKey(bundle, fn) {
465
+ const parsed = parseBundle(bundle);
466
+ try {
467
+ const report = validateParsedBundle(parsed);
468
+ return fn(parsed.bsk, report);
469
+ }
470
+ finally {
471
+ zeroiseParsedBundle(parsed);
445
472
  }
446
473
  }
447
474
  function assertSafeDestination(mountPath, requireDedicatedMount) {