@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.
- package/README.md +8 -6
- package/dist/commands/agent/helpers.js +2 -2
- package/dist/commands/agent/helpers.js.map +1 -1
- package/dist/commands/kmip/index.js +1 -1
- package/dist/commands/kmip/index.js.map +1 -1
- package/dist/commands/lmk-escrow.d.ts.map +1 -1
- package/dist/commands/lmk-escrow.js +36 -0
- package/dist/commands/lmk-escrow.js.map +1 -1
- package/dist/commands/mysql/index.js +2 -2
- package/dist/commands/mysql/index.js.map +1 -1
- package/dist/commands/secret/create.d.ts.map +1 -1
- package/dist/commands/secret/create.js +48 -4
- package/dist/commands/secret/create.js.map +1 -1
- package/dist/commands/secret/decrypt.js +1 -1
- package/dist/commands/secret/index.js +2 -2
- package/dist/commands/secret/types.d.ts +1 -0
- package/dist/commands/secret/types.d.ts.map +1 -1
- package/dist/commands/superadmin/index.d.ts.map +1 -1
- package/dist/commands/superadmin/index.js +2 -0
- package/dist/commands/superadmin/index.js.map +1 -1
- package/dist/commands/superadmin/rootkey.d.ts +13 -0
- package/dist/commands/superadmin/rootkey.d.ts.map +1 -0
- package/dist/commands/superadmin/rootkey.js +223 -0
- package/dist/commands/superadmin/rootkey.js.map +1 -0
- package/dist/lib/config/store.d.ts.map +1 -1
- package/dist/lib/config/store.js +3 -0
- package/dist/lib/config/store.js.map +1 -1
- package/dist/lib/lmk-escrow-restore.d.ts +33 -0
- package/dist/lib/lmk-escrow-restore.d.ts.map +1 -0
- package/dist/lib/lmk-escrow-restore.js +124 -0
- package/dist/lib/lmk-escrow-restore.js.map +1 -0
- package/dist/lib/lmk-escrow.d.ts +12 -0
- package/dist/lib/lmk-escrow.d.ts.map +1 -1
- package/dist/lib/lmk-escrow.js +91 -64
- package/dist/lib/lmk-escrow.js.map +1 -1
- package/dist/lib/stdin.d.ts +11 -0
- package/dist/lib/stdin.d.ts.map +1 -0
- package/dist/lib/stdin.js +27 -0
- package/dist/lib/stdin.js.map +1 -0
- package/dist/services/signature-verifier.js +1 -1
- package/dist/types/update.js +1 -1
- package/dist/types/update.js.map +1 -1
- 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;
|
|
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"}
|
package/dist/lib/config/store.js
CHANGED
|
@@ -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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"}
|
package/dist/lib/lmk-escrow.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/lib/lmk-escrow.js
CHANGED
|
@@ -371,77 +371,104 @@ function parseBundle(bundle) {
|
|
|
371
371
|
throw error;
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
381
|
-
|
|
398
|
+
seen.add(row.version);
|
|
399
|
+
if (metadata.status === 'ACTIVE') {
|
|
400
|
+
activeCount += 1;
|
|
401
|
+
activeVersion = row.version;
|
|
382
402
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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) {
|