@skrr-ai/cli 0.1.123 → 0.1.124
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/dist/commands/agents/delete.d.ts +8 -0
- package/dist/commands/agents/delete.js +16 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/search/workspace.js +1 -1
- package/dist/commands/store/gallery-admissions/approve.js +1 -1
- package/dist/commands/store/gallery-admissions/preview.js +1 -1
- package/dist/commands/store/gallery-admissions/refuse.js +1 -1
- package/dist/commands/store/gallery-admissions/show.js +1 -1
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/keychain.js +41 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +13 -28
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +387 -90
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +11 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +33 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +12 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +15 -37
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +133 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +536 -101
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +13 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +43 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +104 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +14 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +25 -7
- package/oclif.manifest.json +1 -1
- package/package.json +4 -1
|
@@ -28,6 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var deviceIdentityBridge_exports = {};
|
|
30
30
|
__export(deviceIdentityBridge_exports, {
|
|
31
|
+
DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT: () => DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT,
|
|
32
|
+
PENDING_HARDWARE_KEY_MAX_AGE_MS: () => PENDING_HARDWARE_KEY_MAX_AGE_MS,
|
|
31
33
|
__enrollmentBackoffPathForTest: () => __enrollmentBackoffPathForTest,
|
|
32
34
|
__enrollmentMarkerPathForTest: () => __enrollmentMarkerPathForTest,
|
|
33
35
|
__getStateForTest: () => __getStateForTest,
|
|
@@ -36,23 +38,35 @@ __export(deviceIdentityBridge_exports, {
|
|
|
36
38
|
__setStateForTest: () => __setStateForTest,
|
|
37
39
|
__setUnreadableReloadIntervalForTest: () => __setUnreadableReloadIntervalForTest,
|
|
38
40
|
clearEnrollmentMarker: () => clearEnrollmentMarker,
|
|
41
|
+
commitDeviceKeyCandidate: () => commitDeviceKeyCandidate,
|
|
42
|
+
commitPendingHardwareKey: () => commitPendingHardwareKey,
|
|
39
43
|
describeDeviceIdentityState: () => describeDeviceIdentityState,
|
|
40
44
|
describeDeviceKeySplit: () => describeDeviceKeySplit,
|
|
45
|
+
describeStoredDeviceKey: () => describeStoredDeviceKey,
|
|
46
|
+
discardPendingHardwareKey: () => discardPendingHardwareKey,
|
|
41
47
|
enrollWithServer: () => enrollWithServer,
|
|
42
48
|
getDevicePublicKey: () => getDevicePublicKey,
|
|
43
49
|
initDeviceIdentity: () => initDeviceIdentity,
|
|
44
50
|
isDeviceIdentityActive: () => isDeviceIdentityActive,
|
|
51
|
+
persistPendingHardwareKey: () => persistPendingHardwareKey,
|
|
52
|
+
prepareHardwareKeyCandidate: () => prepareHardwareKeyCandidate,
|
|
45
53
|
resetDeviceIdentity: () => resetDeviceIdentity,
|
|
46
|
-
signServerRequest: () => signServerRequest
|
|
54
|
+
signServerRequest: () => signServerRequest,
|
|
55
|
+
signWithCandidate: () => signWithCandidate
|
|
47
56
|
});
|
|
48
57
|
module.exports = __toCommonJS(deviceIdentityBridge_exports);
|
|
49
58
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
50
59
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
51
60
|
var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
52
61
|
var import_deviceKey = require("./deviceKey.js");
|
|
62
|
+
var import_deviceKeyHandle = require("./deviceKeyHandle.js");
|
|
63
|
+
var import_deviceKeyHardware = require("./deviceKeyHardware.js");
|
|
53
64
|
var import_credentialEnvelopeBridge = require("./credentialEnvelopeBridge.js");
|
|
54
65
|
var import_profileStateDir = require("./profileStateDir.js");
|
|
66
|
+
var import_liveState = require("./liveState.js");
|
|
55
67
|
var import_runtime = require("./runtime.js");
|
|
68
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
69
|
+
const DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT = "stored device key is unreadable on this machine \u2014 only an interactive daemon sign-in replaces it";
|
|
56
70
|
const PUBLIC_KEY_FILENAME = "device-public-key.jwk";
|
|
57
71
|
const PRIVATE_KEY_FILENAME = "device-private-key";
|
|
58
72
|
const ENROLLMENT_MARKER_FILENAME = "device-enrollment.json";
|
|
@@ -82,7 +96,16 @@ function keyFilesStamp(profile) {
|
|
|
82
96
|
};
|
|
83
97
|
return `${part(publicKeyPath(profile))}|${part(privateKeyPath(profile))}`;
|
|
84
98
|
}
|
|
99
|
+
let _activeSeen = null;
|
|
100
|
+
function trackActiveKeyFiles(profile) {
|
|
101
|
+
try {
|
|
102
|
+
_activeSeen = { profile, stamp: keyFilesStamp(profile), checkedAt: Date.now() };
|
|
103
|
+
} catch {
|
|
104
|
+
_activeSeen = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
85
107
|
function reloadIfRecovered() {
|
|
108
|
+
reloadIfActiveKeyChanged();
|
|
86
109
|
if (_state.kind !== "disabled" || _state.reason !== "unreadable_key")
|
|
87
110
|
return;
|
|
88
111
|
const seen = _unreadableSeen;
|
|
@@ -101,26 +124,70 @@ function reloadIfRecovered() {
|
|
|
101
124
|
if (stamp === seen.stamp)
|
|
102
125
|
return;
|
|
103
126
|
seen.stamp = stamp;
|
|
104
|
-
let
|
|
127
|
+
let key = null;
|
|
105
128
|
try {
|
|
106
|
-
|
|
129
|
+
key = loadStoredKey(seen.profile);
|
|
107
130
|
} catch {
|
|
108
|
-
|
|
131
|
+
key = null;
|
|
109
132
|
}
|
|
110
|
-
if (!
|
|
133
|
+
if (!key)
|
|
111
134
|
return;
|
|
112
135
|
_state = {
|
|
113
136
|
kind: "active",
|
|
114
|
-
|
|
137
|
+
key,
|
|
115
138
|
profile: seen.profile,
|
|
116
|
-
envelopeProtected: (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
|
|
139
|
+
envelopeProtected: key.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
|
|
117
140
|
};
|
|
118
141
|
_unreadableSeen = null;
|
|
119
|
-
|
|
142
|
+
trackActiveKeyFiles(seen.profile);
|
|
143
|
+
emit("reloaded_after_recovery", true, {
|
|
144
|
+
profile: seen.profile,
|
|
145
|
+
thumbprint: key.thumbprint,
|
|
146
|
+
custody: key.custody
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function reloadIfActiveKeyChanged() {
|
|
150
|
+
const state = _state;
|
|
151
|
+
const seen = _activeSeen;
|
|
152
|
+
if (state.kind !== "active" || !seen || seen.profile !== state.profile || _initializing)
|
|
153
|
+
return;
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
if (now - seen.checkedAt < _unreadableReloadIntervalMs)
|
|
156
|
+
return;
|
|
157
|
+
seen.checkedAt = now;
|
|
158
|
+
let stamp;
|
|
159
|
+
try {
|
|
160
|
+
stamp = keyFilesStamp(seen.profile);
|
|
161
|
+
} catch {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (stamp === seen.stamp)
|
|
165
|
+
return;
|
|
166
|
+
seen.stamp = stamp;
|
|
167
|
+
let key = null;
|
|
168
|
+
try {
|
|
169
|
+
key = loadStoredKey(seen.profile);
|
|
170
|
+
} catch {
|
|
171
|
+
key = null;
|
|
172
|
+
}
|
|
173
|
+
if (!key || key.thumbprint === state.key.thumbprint)
|
|
174
|
+
return;
|
|
175
|
+
_state = {
|
|
176
|
+
kind: "active",
|
|
177
|
+
key,
|
|
178
|
+
profile: seen.profile,
|
|
179
|
+
envelopeProtected: key.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
|
|
180
|
+
};
|
|
181
|
+
emit("reloaded_after_key_change", true, {
|
|
182
|
+
profile: seen.profile,
|
|
183
|
+
from: state.key.thumbprint,
|
|
184
|
+
thumbprint: key.thumbprint,
|
|
185
|
+
custody: key.custody
|
|
186
|
+
});
|
|
120
187
|
}
|
|
121
188
|
async function withDeviceIdentityLock(profile, body) {
|
|
122
189
|
const dir = (0, import_profileStateDir.profileStateDir)(profile);
|
|
123
|
-
|
|
190
|
+
(0, import_stateFiles.ensureStateDir)(dir, `take the device identity lock for profile ${profile}`);
|
|
124
191
|
const release = await import_proper_lockfile.default.lock(dir, {
|
|
125
192
|
realpath: false,
|
|
126
193
|
lockfilePath: import_node_path.default.join(dir, ".device-identity.lock"),
|
|
@@ -176,29 +243,6 @@ function enrollmentMarkerPath(profile) {
|
|
|
176
243
|
function enrollmentBackoffPath(profile) {
|
|
177
244
|
return import_node_path.default.join(profileConfigDir(profile), ENROLLMENT_BACKOFF_FILENAME);
|
|
178
245
|
}
|
|
179
|
-
function atomicWrite0600(target, value) {
|
|
180
|
-
const dir = import_node_path.default.dirname(target);
|
|
181
|
-
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
182
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
183
|
-
const buf = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
|
|
184
|
-
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
185
|
-
try {
|
|
186
|
-
import_node_fs.default.writeSync(fd, buf);
|
|
187
|
-
import_node_fs.default.fsyncSync(fd);
|
|
188
|
-
} finally {
|
|
189
|
-
import_node_fs.default.closeSync(fd);
|
|
190
|
-
}
|
|
191
|
-
import_node_fs.default.renameSync(tmp, target);
|
|
192
|
-
try {
|
|
193
|
-
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
194
|
-
try {
|
|
195
|
-
import_node_fs.default.fsyncSync(dirFd);
|
|
196
|
-
} finally {
|
|
197
|
-
import_node_fs.default.closeSync(dirFd);
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
246
|
function emit(eventName, success, metadata, opts = {}) {
|
|
203
247
|
(0, import_runtime.emitAuthTelemetry)({
|
|
204
248
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -216,17 +260,31 @@ function emit(eventName, success, metadata, opts = {}) {
|
|
|
216
260
|
}
|
|
217
261
|
}
|
|
218
262
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
publicJwk
|
|
222
|
-
thumbprint:
|
|
263
|
+
function writePublicKeyRecord(profile, publicJwk) {
|
|
264
|
+
(0, import_stateFiles.atomicWrite0600)(publicKeyPath(profile), JSON.stringify({
|
|
265
|
+
publicJwk,
|
|
266
|
+
thumbprint: (0, import_deviceKey.jwkThumbprint)(publicJwk),
|
|
267
|
+
alg: publicJwk.kty === "EC" ? "ES256" : "EdDSA",
|
|
268
|
+
custody: publicJwk.kty === "EC" ? "hardware" : "software",
|
|
223
269
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
|
+
}), `write the device public key for profile ${profile}`);
|
|
271
|
+
}
|
|
272
|
+
function persistHardwareKey(profile, record) {
|
|
273
|
+
(0, import_stateFiles.atomicWrite0600)(privateKeyPath(profile), (0, import_deviceKeyHardware.serializeHardwareKeyRecord)(record), `write the hardware device key record for profile ${profile}`);
|
|
274
|
+
writePublicKeyRecord(profile, record.publicJwk);
|
|
275
|
+
emit("keypair_persisted", true, {
|
|
276
|
+
profile,
|
|
277
|
+
thumbprint: (0, import_deviceKey.jwkThumbprint)(record.publicJwk),
|
|
278
|
+
custody: "hardware",
|
|
279
|
+
envelopeProtected: false
|
|
224
280
|
});
|
|
225
|
-
|
|
281
|
+
}
|
|
282
|
+
function persistKeypair(profile, kp) {
|
|
283
|
+
writePublicKeyRecord(profile, kp.publicJwk);
|
|
226
284
|
const privatePlaintext = JSON.stringify(kp.privateJwk);
|
|
227
285
|
const wireForm = (0, import_credentialEnvelopeBridge.wrapIfActiveOrPassthrough)(privatePlaintext);
|
|
228
286
|
const envelopeProtected = wireForm !== privatePlaintext;
|
|
229
|
-
atomicWrite0600(privateKeyPath(profile), wireForm);
|
|
287
|
+
(0, import_stateFiles.atomicWrite0600)(privateKeyPath(profile), wireForm, `write the device private key for profile ${profile}`);
|
|
230
288
|
emit("keypair_persisted", true, {
|
|
231
289
|
profile,
|
|
232
290
|
thumbprint: kp.thumbprint,
|
|
@@ -267,7 +325,7 @@ function readIndependentIdentityFile(file, sealed) {
|
|
|
267
325
|
import_node_fs.default.closeSync(fd);
|
|
268
326
|
}
|
|
269
327
|
}
|
|
270
|
-
function
|
|
328
|
+
function loadStoredKey(profile) {
|
|
271
329
|
const pubPath = publicKeyPath(profile);
|
|
272
330
|
const privPath = privateKeyPath(profile);
|
|
273
331
|
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
@@ -281,6 +339,8 @@ function loadKeypair(profile) {
|
|
|
281
339
|
let storedThumbprint;
|
|
282
340
|
try {
|
|
283
341
|
const parsed = JSON.parse(pubRaw);
|
|
342
|
+
if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
|
|
343
|
+
throw new Error("public key is not a device key");
|
|
284
344
|
publicJwk = parsed.publicJwk;
|
|
285
345
|
storedThumbprint = parsed.thumbprint;
|
|
286
346
|
} catch (err) {
|
|
@@ -290,7 +350,22 @@ function loadKeypair(profile) {
|
|
|
290
350
|
});
|
|
291
351
|
return null;
|
|
292
352
|
}
|
|
293
|
-
const
|
|
353
|
+
const hardware = independent ? null : (0, import_deviceKeyHardware.parseHardwareKeyRecord)(privRaw);
|
|
354
|
+
if (hardware) {
|
|
355
|
+
if ((0, import_deviceKey.jwkThumbprint)(hardware.publicJwk) !== (0, import_deviceKey.jwkThumbprint)(publicJwk)) {
|
|
356
|
+
try {
|
|
357
|
+
writePublicKeyRecord(profile, hardware.publicJwk);
|
|
358
|
+
emit("public_key_repaired_from_hardware_record", true, { profile }, { logLevel: "info" });
|
|
359
|
+
} catch (err) {
|
|
360
|
+
emit("public_key_unreadable", false, { profile, message: err?.message ?? "unknown" });
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return (0, import_deviceKeyHardware.hardwareDeviceKeyHandle)(hardware);
|
|
365
|
+
}
|
|
366
|
+
const decryptResult = (0, import_credentialEnvelopeBridge.maybeDecryptOnRead)(privRaw, {
|
|
367
|
+
unreadableHint: DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT
|
|
368
|
+
});
|
|
294
369
|
if (decryptResult.plaintext === null) {
|
|
295
370
|
emit("private_key_decrypt_failed", false, { profile });
|
|
296
371
|
return null;
|
|
@@ -306,7 +381,7 @@ function loadKeypair(profile) {
|
|
|
306
381
|
});
|
|
307
382
|
return null;
|
|
308
383
|
}
|
|
309
|
-
if (publicJwk.x !== privateJwk.x) {
|
|
384
|
+
if (publicJwk.kty !== "OKP" || publicJwk.x !== privateJwk.x) {
|
|
310
385
|
emit("keypair_inconsistent", false, { profile });
|
|
311
386
|
return null;
|
|
312
387
|
}
|
|
@@ -318,16 +393,20 @@ function loadKeypair(profile) {
|
|
|
318
393
|
recomputed
|
|
319
394
|
});
|
|
320
395
|
}
|
|
321
|
-
|
|
322
|
-
publicJwk,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
396
|
+
try {
|
|
397
|
+
return (0, import_deviceKeyHandle.softwareEd25519Handle)({ publicJwk, privateJwk, thumbprint: recomputed });
|
|
398
|
+
} catch (err) {
|
|
399
|
+
emit("private_key_malformed", false, {
|
|
400
|
+
profile,
|
|
401
|
+
message: err?.message ?? "unknown"
|
|
402
|
+
});
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
326
405
|
}
|
|
327
406
|
function deviceIdentityOf(dir) {
|
|
328
407
|
try {
|
|
329
408
|
const parsed = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(dir, PUBLIC_KEY_FILENAME), "utf-8"));
|
|
330
|
-
if (!
|
|
409
|
+
if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
|
|
331
410
|
return null;
|
|
332
411
|
return (0, import_deviceKey.jwkThumbprint)(parsed.publicJwk);
|
|
333
412
|
} catch {
|
|
@@ -370,6 +449,7 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
370
449
|
onEvent: (name, success, meta) => emit(name, success, meta)
|
|
371
450
|
});
|
|
372
451
|
} catch (err) {
|
|
452
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
373
453
|
emit("path_consolidation.failed", false, {
|
|
374
454
|
profile,
|
|
375
455
|
message: err?.message ?? "unknown"
|
|
@@ -390,6 +470,7 @@ async function initDeviceIdentity(profile, options = {}) {
|
|
|
390
470
|
if (_state.kind === "uninit")
|
|
391
471
|
await initDeviceIdentityLocked(profile, options);
|
|
392
472
|
}).catch((err) => {
|
|
473
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
393
474
|
_state = { kind: "disabled", reason: "init_failed" };
|
|
394
475
|
emit("init_failed", false, {
|
|
395
476
|
profile,
|
|
@@ -409,22 +490,24 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
409
490
|
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
410
491
|
if (!independent)
|
|
411
492
|
await consolidateDeviceIdentity(profile);
|
|
412
|
-
let
|
|
493
|
+
let stored = null;
|
|
413
494
|
try {
|
|
414
|
-
|
|
495
|
+
stored = loadStoredKey(profile);
|
|
415
496
|
} catch (err) {
|
|
416
497
|
emit("load_failed", false, {
|
|
417
498
|
profile,
|
|
418
499
|
message: err?.message ?? "unknown"
|
|
419
500
|
});
|
|
420
|
-
|
|
501
|
+
stored = null;
|
|
421
502
|
}
|
|
422
|
-
if (
|
|
423
|
-
const envelopeProtected = (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)();
|
|
424
|
-
_state = { kind: "active",
|
|
503
|
+
if (stored) {
|
|
504
|
+
const envelopeProtected = stored.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)();
|
|
505
|
+
_state = { kind: "active", key: stored, profile, envelopeProtected };
|
|
506
|
+
trackActiveKeyFiles(profile);
|
|
425
507
|
emit("active", true, {
|
|
426
508
|
profile,
|
|
427
|
-
thumbprint:
|
|
509
|
+
thumbprint: stored.thumbprint,
|
|
510
|
+
custody: stored.custody,
|
|
428
511
|
envelopeProtected,
|
|
429
512
|
regenerated: false
|
|
430
513
|
});
|
|
@@ -458,6 +541,7 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
458
541
|
try {
|
|
459
542
|
persistResult = persistKeypair(profile, fresh);
|
|
460
543
|
} catch (err) {
|
|
544
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
461
545
|
_state = { kind: "disabled", reason: "persist_failed" };
|
|
462
546
|
emit("persist_failed", false, {
|
|
463
547
|
profile,
|
|
@@ -465,15 +549,29 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
465
549
|
});
|
|
466
550
|
return;
|
|
467
551
|
}
|
|
552
|
+
let freshKey;
|
|
553
|
+
try {
|
|
554
|
+
freshKey = (0, import_deviceKeyHandle.softwareEd25519Handle)(fresh);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
_state = { kind: "disabled", reason: "init_failed" };
|
|
557
|
+
emit("init_failed", false, {
|
|
558
|
+
profile,
|
|
559
|
+
reason: "keygen_threw",
|
|
560
|
+
message: err?.message ?? "unknown"
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
468
564
|
_state = {
|
|
469
565
|
kind: "active",
|
|
470
|
-
|
|
566
|
+
key: freshKey,
|
|
471
567
|
profile,
|
|
472
568
|
envelopeProtected: persistResult.envelopeProtected
|
|
473
569
|
};
|
|
570
|
+
trackActiveKeyFiles(profile);
|
|
474
571
|
emit("active", true, {
|
|
475
572
|
profile,
|
|
476
573
|
thumbprint: fresh.thumbprint,
|
|
574
|
+
custody: "software",
|
|
477
575
|
envelopeProtected: persistResult.envelopeProtected,
|
|
478
576
|
regenerated: true
|
|
479
577
|
});
|
|
@@ -482,12 +580,18 @@ async function resetDeviceIdentity(opts) {
|
|
|
482
580
|
if (_initializing)
|
|
483
581
|
await _initializing;
|
|
484
582
|
if (opts?.clearOnDisk && opts.profile) {
|
|
485
|
-
const targets = [
|
|
583
|
+
const targets = [
|
|
584
|
+
...[PUBLIC_KEY_FILENAME, PRIVATE_KEY_FILENAME].flatMap((name) => (0, import_profileStateDir.profileStateFilePathsToClear)(opts.profile, name)),
|
|
585
|
+
...retiredKeyCopies(opts.profile, PUBLIC_KEY_FILENAME),
|
|
586
|
+
...retiredKeyCopies(opts.profile, PRIVATE_KEY_FILENAME),
|
|
587
|
+
pendingHardwareKeyPath(opts.profile)
|
|
588
|
+
];
|
|
486
589
|
await withDeviceIdentityLock(opts.profile, async () => {
|
|
487
590
|
for (const p of targets) {
|
|
488
591
|
try {
|
|
489
|
-
|
|
592
|
+
(0, import_stateFiles.removeStateFile)(p, `delete the device key file ${import_node_path.default.basename(p)}`);
|
|
490
593
|
} catch (err) {
|
|
594
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
491
595
|
if (err.code !== "ENOENT") {
|
|
492
596
|
emit("clear_failed", false, {
|
|
493
597
|
profile: opts.profile,
|
|
@@ -501,6 +605,150 @@ async function resetDeviceIdentity(opts) {
|
|
|
501
605
|
}
|
|
502
606
|
_state = { kind: "uninit" };
|
|
503
607
|
_unreadableSeen = null;
|
|
608
|
+
_activeSeen = null;
|
|
609
|
+
}
|
|
610
|
+
async function prepareHardwareKeyCandidate(profile) {
|
|
611
|
+
if (/^desktop-bg-[a-f0-9]{32}$/i.test(profile))
|
|
612
|
+
return { ok: false, reason: "independent_profile" };
|
|
613
|
+
if (_state.kind === "active" && _state.key.custody === "hardware") {
|
|
614
|
+
return { ok: false, reason: "already_hardware" };
|
|
615
|
+
}
|
|
616
|
+
const capability = await (0, import_deviceKeyHardware.probeHardwareDeviceKey)();
|
|
617
|
+
if (!capability.available)
|
|
618
|
+
return { ok: false, reason: "unavailable", capability };
|
|
619
|
+
try {
|
|
620
|
+
const { record, handle } = await (0, import_deviceKeyHardware.createHardwareDeviceKey)();
|
|
621
|
+
return { ok: true, candidate: { key: handle, record } };
|
|
622
|
+
} catch (err) {
|
|
623
|
+
emit("hardware_key_create_failed", false, {
|
|
624
|
+
profile,
|
|
625
|
+
...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
|
|
626
|
+
message: err?.message ?? "unknown"
|
|
627
|
+
});
|
|
628
|
+
return { ok: false, reason: "create_failed", message: err?.message ?? "unknown" };
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async function signWithCandidate(candidate, opts) {
|
|
632
|
+
try {
|
|
633
|
+
return await (0, import_deviceKey.signProofWith)(candidate.key, opts);
|
|
634
|
+
} catch (err) {
|
|
635
|
+
emit("sign_failed", false, {
|
|
636
|
+
thumbprint: candidate.key.thumbprint,
|
|
637
|
+
custody: "hardware",
|
|
638
|
+
candidate: true,
|
|
639
|
+
method: opts.method,
|
|
640
|
+
...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
|
|
641
|
+
message: err?.message ?? "unknown"
|
|
642
|
+
});
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function retiredKeyCopies(profile, filename) {
|
|
647
|
+
const dir = import_node_path.default.dirname(filename === PUBLIC_KEY_FILENAME ? publicKeyPath(profile) : privateKeyPath(profile));
|
|
648
|
+
const prefix = `${filename}${import_profileStateDir.RETIRED_COPY_SUFFIX}.`;
|
|
649
|
+
try {
|
|
650
|
+
return import_node_fs.default.readdirSync(dir).filter((name) => name.startsWith(prefix)).sort().map((name) => import_node_path.default.join(dir, name));
|
|
651
|
+
} catch {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function retireSoftwareKey(profile) {
|
|
656
|
+
try {
|
|
657
|
+
const priv = privateKeyPath(profile);
|
|
658
|
+
const raw = import_node_fs.default.readFileSync(priv, "utf-8");
|
|
659
|
+
if ((0, import_deviceKeyHardware.parseHardwareKeyRecord)(raw))
|
|
660
|
+
return;
|
|
661
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
662
|
+
(0, import_stateFiles.atomicWrite0600)(`${priv}${import_profileStateDir.RETIRED_COPY_SUFFIX}.${stamp}`, raw, `keep the replaced software device key for profile ${profile}`);
|
|
663
|
+
try {
|
|
664
|
+
(0, import_stateFiles.atomicWrite0600)(`${publicKeyPath(profile)}${import_profileStateDir.RETIRED_COPY_SUFFIX}.${stamp}`, import_node_fs.default.readFileSync(publicKeyPath(profile), "utf-8"), `keep the replaced software device public key for profile ${profile}`);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
667
|
+
}
|
|
668
|
+
for (const filename of [PRIVATE_KEY_FILENAME, PUBLIC_KEY_FILENAME]) {
|
|
669
|
+
for (const stale of retiredKeyCopies(profile, filename).slice(0, -1)) {
|
|
670
|
+
try {
|
|
671
|
+
(0, import_stateFiles.removeStateFile)(stale, `prune a retired device key copy for profile ${profile}`);
|
|
672
|
+
} catch (err) {
|
|
673
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} catch (err) {
|
|
678
|
+
emit("retire_software_key_failed", false, { profile, message: err?.message ?? "unknown" });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async function commitDeviceKeyCandidate(profile, candidate) {
|
|
682
|
+
if (_initializing)
|
|
683
|
+
await _initializing;
|
|
684
|
+
await withDeviceIdentityLock(profile, async () => {
|
|
685
|
+
commitRecordLocked(profile, candidate.record, candidate.key);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function commitRecordLocked(profile, record, key) {
|
|
689
|
+
retireSoftwareKey(profile);
|
|
690
|
+
persistHardwareKey(profile, record);
|
|
691
|
+
clearEnrollmentMarker(profile);
|
|
692
|
+
removePendingHardwareKey(profile);
|
|
693
|
+
_state = { kind: "active", key, profile, envelopeProtected: false };
|
|
694
|
+
_unreadableSeen = null;
|
|
695
|
+
trackActiveKeyFiles(profile);
|
|
696
|
+
emit("upgraded_to_hardware", true, {
|
|
697
|
+
profile,
|
|
698
|
+
thumbprint: key.thumbprint,
|
|
699
|
+
custody: "hardware"
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
const PENDING_HARDWARE_FILENAME = "device-key.pending";
|
|
703
|
+
const PENDING_HARDWARE_KEY_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
704
|
+
function pendingHardwareKeyPath(profile) {
|
|
705
|
+
return import_node_path.default.join(profileConfigDir(profile), PENDING_HARDWARE_FILENAME);
|
|
706
|
+
}
|
|
707
|
+
function removePendingHardwareKey(profile) {
|
|
708
|
+
try {
|
|
709
|
+
(0, import_stateFiles.removeStateFile)(pendingHardwareKeyPath(profile), `remove the pending hardware key for profile ${profile}`);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
712
|
+
if (err.code !== "ENOENT") {
|
|
713
|
+
emit("pending_hardware_key_clear_failed", false, { profile, message: err?.message ?? "unknown" });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function persistPendingHardwareKey(profile, candidate) {
|
|
718
|
+
await withDeviceIdentityLock(profile, async () => {
|
|
719
|
+
(0, import_stateFiles.atomicWrite0600)(pendingHardwareKeyPath(profile), (0, import_deviceKeyHardware.serializeHardwareKeyRecord)(candidate.record), `park a pending hardware key for profile ${profile}`);
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function commitPendingHardwareKey(profile, thumbprint) {
|
|
723
|
+
if (_initializing)
|
|
724
|
+
await _initializing;
|
|
725
|
+
return withDeviceIdentityLock(profile, async () => {
|
|
726
|
+
const file = pendingHardwareKeyPath(profile);
|
|
727
|
+
let raw;
|
|
728
|
+
let modifiedAt;
|
|
729
|
+
try {
|
|
730
|
+
raw = import_node_fs.default.readFileSync(file, "utf-8");
|
|
731
|
+
modifiedAt = import_node_fs.default.statSync(file).mtimeMs;
|
|
732
|
+
} catch {
|
|
733
|
+
return "no_pending";
|
|
734
|
+
}
|
|
735
|
+
const record = (0, import_deviceKeyHardware.parseHardwareKeyRecord)(raw);
|
|
736
|
+
if (!record)
|
|
737
|
+
return "unreadable";
|
|
738
|
+
if (Date.now() - modifiedAt > PENDING_HARDWARE_KEY_MAX_AGE_MS) {
|
|
739
|
+
removePendingHardwareKey(profile);
|
|
740
|
+
return "expired";
|
|
741
|
+
}
|
|
742
|
+
if ((0, import_deviceKey.jwkThumbprint)(record.publicJwk) !== thumbprint)
|
|
743
|
+
return "mismatch";
|
|
744
|
+
commitRecordLocked(profile, record, (0, import_deviceKeyHardware.hardwareDeviceKeyHandle)(record));
|
|
745
|
+
return "committed";
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
async function discardPendingHardwareKey(profile) {
|
|
749
|
+
await withDeviceIdentityLock(profile, async () => {
|
|
750
|
+
removePendingHardwareKey(profile);
|
|
751
|
+
});
|
|
504
752
|
}
|
|
505
753
|
function isDeviceIdentityActive() {
|
|
506
754
|
reloadIfRecovered();
|
|
@@ -510,7 +758,12 @@ function getDevicePublicKey() {
|
|
|
510
758
|
reloadIfRecovered();
|
|
511
759
|
if (_state.kind !== "active")
|
|
512
760
|
return null;
|
|
513
|
-
return {
|
|
761
|
+
return {
|
|
762
|
+
jwk: _state.key.publicJwk,
|
|
763
|
+
thumbprint: _state.key.thumbprint,
|
|
764
|
+
alg: _state.key.alg,
|
|
765
|
+
custody: _state.key.custody
|
|
766
|
+
};
|
|
514
767
|
}
|
|
515
768
|
function describeDeviceIdentityState() {
|
|
516
769
|
reloadIfRecovered();
|
|
@@ -520,19 +773,41 @@ function describeDeviceIdentityState() {
|
|
|
520
773
|
if (_state.kind === "active") {
|
|
521
774
|
return {
|
|
522
775
|
kind: "active",
|
|
523
|
-
thumbprint: _state.
|
|
776
|
+
thumbprint: _state.key.thumbprint,
|
|
524
777
|
profile: _state.profile,
|
|
525
|
-
envelopeProtected: _state.envelopeProtected
|
|
778
|
+
envelopeProtected: _state.envelopeProtected,
|
|
779
|
+
alg: _state.key.alg,
|
|
780
|
+
custody: _state.key.custody,
|
|
781
|
+
canSign: _state.key.custody === "software" ? true : (0, import_deviceKeyHardware.resolveHardwareDeviceKeyHelper)() !== null
|
|
526
782
|
};
|
|
527
783
|
}
|
|
528
784
|
return { kind: "uninit" };
|
|
529
785
|
}
|
|
530
|
-
function
|
|
786
|
+
function describeStoredDeviceKey(profile) {
|
|
787
|
+
try {
|
|
788
|
+
const parsed = JSON.parse(import_node_fs.default.readFileSync(publicKeyPath(profile), "utf-8"));
|
|
789
|
+
if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
|
|
790
|
+
return { stored: false };
|
|
791
|
+
const alg = parsed.publicJwk.kty === "EC" ? "ES256" : "EdDSA";
|
|
792
|
+
const custody = alg === "ES256" ? "hardware" : "software";
|
|
793
|
+
return {
|
|
794
|
+
stored: true,
|
|
795
|
+
alg,
|
|
796
|
+
custody,
|
|
797
|
+
thumbprint: (0, import_deviceKey.jwkThumbprint)(parsed.publicJwk),
|
|
798
|
+
canSign: custody === "software" ? true : (0, import_deviceKeyHardware.resolveHardwareDeviceKeyHelper)() !== null
|
|
799
|
+
};
|
|
800
|
+
} catch {
|
|
801
|
+
return { stored: false };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async function signServerRequest(opts) {
|
|
531
805
|
reloadIfRecovered();
|
|
532
|
-
|
|
806
|
+
const state = _state;
|
|
807
|
+
if (state.kind !== "active")
|
|
533
808
|
return null;
|
|
534
809
|
try {
|
|
535
|
-
return (0, import_deviceKey.
|
|
810
|
+
return await (0, import_deviceKey.signProofWith)(state.key, {
|
|
536
811
|
method: opts.method,
|
|
537
812
|
url: opts.url,
|
|
538
813
|
refreshToken: opts.refreshToken,
|
|
@@ -540,9 +815,13 @@ function signServerRequest(opts) {
|
|
|
540
815
|
});
|
|
541
816
|
} catch (err) {
|
|
542
817
|
emit("sign_failed", false, {
|
|
543
|
-
profile:
|
|
544
|
-
thumbprint:
|
|
818
|
+
profile: state.profile,
|
|
819
|
+
thumbprint: state.key.thumbprint,
|
|
820
|
+
custody: state.key.custody,
|
|
545
821
|
method: opts.method,
|
|
822
|
+
// A hardware failure names its cause (helper missing, timed out, key
|
|
823
|
+
// mismatch) so a status line can say what to do about it.
|
|
824
|
+
...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
|
|
546
825
|
message: err?.message ?? "unknown"
|
|
547
826
|
});
|
|
548
827
|
return null;
|
|
@@ -563,8 +842,9 @@ function __enrollmentBackoffPathForTest(profile) {
|
|
|
563
842
|
function clearEnrollmentMarker(profile) {
|
|
564
843
|
for (const markerPath of [enrollmentMarkerPath(profile), enrollmentBackoffPath(profile)]) {
|
|
565
844
|
try {
|
|
566
|
-
|
|
845
|
+
(0, import_stateFiles.removeStateFile)(markerPath, `delete the enrollment file ${import_node_path.default.basename(markerPath)}`);
|
|
567
846
|
} catch (err) {
|
|
847
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
568
848
|
if (err.code !== "ENOENT") {
|
|
569
849
|
emit("enrollment_marker_clear_failed", false, {
|
|
570
850
|
profile,
|
|
@@ -581,7 +861,7 @@ async function enrollWithServer(opts) {
|
|
|
581
861
|
return { enrolled: false, reason: "not_active" };
|
|
582
862
|
}
|
|
583
863
|
const profile = _state.profile;
|
|
584
|
-
const
|
|
864
|
+
const key = _state.key;
|
|
585
865
|
const markerPath = enrollmentMarkerPath(profile);
|
|
586
866
|
const backoffPath = enrollmentBackoffPath(profile);
|
|
587
867
|
try {
|
|
@@ -593,27 +873,33 @@ async function enrollWithServer(opts) {
|
|
|
593
873
|
try {
|
|
594
874
|
const raw = JSON.parse(import_node_fs.default.readFileSync(backoffPath, "utf-8"));
|
|
595
875
|
const retryAfter = Date.parse(raw.retryAfter || "");
|
|
596
|
-
if (raw.serverUrl === serverUrl && raw.machineUuid === machineUuid && raw.thumbprint ===
|
|
876
|
+
if (raw.serverUrl === serverUrl && raw.machineUuid === machineUuid && raw.thumbprint === key.thumbprint && Number.isFinite(retryAfter) && retryAfter > Date.now()) {
|
|
597
877
|
return { enrolled: false, reason: "backoff_active" };
|
|
598
878
|
}
|
|
599
|
-
|
|
879
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the stale enrollment backoff");
|
|
600
880
|
} catch (err) {
|
|
601
881
|
if (err.code !== "ENOENT") {
|
|
602
882
|
try {
|
|
603
|
-
|
|
604
|
-
} catch {
|
|
883
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the corrupt enrollment backoff");
|
|
884
|
+
} catch (clearErr) {
|
|
885
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(clearErr);
|
|
605
886
|
}
|
|
606
887
|
}
|
|
607
888
|
}
|
|
608
889
|
const base = serverUrl.replace(/\/+$/, "");
|
|
609
890
|
const htu = `${base}/api/daemon-resources/enroll-device-key`;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
url: htu
|
|
613
|
-
})
|
|
891
|
+
let proofJws;
|
|
892
|
+
try {
|
|
893
|
+
proofJws = await (0, import_deviceKey.signProofWith)(key, { method: "POST", url: htu });
|
|
894
|
+
} catch (err) {
|
|
895
|
+
const message = err?.message ?? "unknown";
|
|
896
|
+
emit("enrollment_sign_failed", false, { profile, thumbprint: key.thumbprint, custody: key.custody, message });
|
|
897
|
+
return { enrolled: false, reason: "not_active", message };
|
|
898
|
+
}
|
|
614
899
|
const body = JSON.stringify({
|
|
615
900
|
machineUuid,
|
|
616
|
-
publicJwkX:
|
|
901
|
+
...key.publicJwk.kty === "OKP" ? { publicJwkX: key.publicJwk.x } : {},
|
|
902
|
+
publicJwk: key.publicJwk,
|
|
617
903
|
proofJws
|
|
618
904
|
});
|
|
619
905
|
const ownAbort = new AbortController();
|
|
@@ -634,7 +920,7 @@ async function enrollWithServer(opts) {
|
|
|
634
920
|
const message = err?.message ?? "unknown";
|
|
635
921
|
emit("enrollment_network_error", false, {
|
|
636
922
|
profile,
|
|
637
|
-
thumbprint:
|
|
923
|
+
thumbprint: key.thumbprint,
|
|
638
924
|
serverUrl,
|
|
639
925
|
message
|
|
640
926
|
});
|
|
@@ -654,13 +940,13 @@ async function enrollWithServer(opts) {
|
|
|
654
940
|
} catch {
|
|
655
941
|
}
|
|
656
942
|
}
|
|
657
|
-
const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !==
|
|
943
|
+
const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !== key.thumbprint;
|
|
658
944
|
const keyPinnedElsewhere = response.status === 409 && conflictCode === "ENROLLMENT_DUPLICATE_KEY";
|
|
659
945
|
const isBenignRejection = response.status === 404 && responseText.includes("DAEMON_NOT_FOUND") || response.status === 403 && responseText.includes("OPAQUE_AUTH_REFUSED") || response.status === 409 && !pinnedToOtherKey && !keyPinnedElsewhere;
|
|
660
946
|
if (pinnedToOtherKey) {
|
|
661
947
|
emit("enrollment_key_mismatch", false, {
|
|
662
948
|
profile,
|
|
663
|
-
thumbprint:
|
|
949
|
+
thumbprint: key.thumbprint,
|
|
664
950
|
enrolledThumbprint,
|
|
665
951
|
serverUrl,
|
|
666
952
|
message: "this machine is registered with a different device key than this profile signs with; its refreshes will be refused. An interactive daemon login (skrrd login) re-registers this key."
|
|
@@ -668,13 +954,14 @@ async function enrollWithServer(opts) {
|
|
|
668
954
|
}
|
|
669
955
|
if (response.status === 404 && responseText.includes("DAEMON_NOT_FOUND")) {
|
|
670
956
|
try {
|
|
671
|
-
atomicWrite0600(backoffPath, JSON.stringify({
|
|
957
|
+
(0, import_stateFiles.atomicWrite0600)(backoffPath, JSON.stringify({
|
|
672
958
|
serverUrl,
|
|
673
959
|
machineUuid,
|
|
674
|
-
thumbprint:
|
|
960
|
+
thumbprint: key.thumbprint,
|
|
675
961
|
retryAfter: new Date(Date.now() + DAEMON_NOT_FOUND_BACKOFF_MS).toISOString()
|
|
676
|
-
}));
|
|
962
|
+
}), "write the enrollment backoff");
|
|
677
963
|
} catch (err) {
|
|
964
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
678
965
|
emit("enrollment_backoff_write_failed", false, {
|
|
679
966
|
profile,
|
|
680
967
|
message: err?.message ?? "unknown"
|
|
@@ -683,7 +970,7 @@ async function enrollWithServer(opts) {
|
|
|
683
970
|
}
|
|
684
971
|
emit("enrollment_rejected", false, {
|
|
685
972
|
profile,
|
|
686
|
-
thumbprint:
|
|
973
|
+
thumbprint: key.thumbprint,
|
|
687
974
|
serverUrl,
|
|
688
975
|
status: response.status,
|
|
689
976
|
body: responseText
|
|
@@ -698,20 +985,21 @@ async function enrollWithServer(opts) {
|
|
|
698
985
|
const marker = JSON.stringify({
|
|
699
986
|
enrolledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
700
987
|
serverUrl,
|
|
701
|
-
thumbprint:
|
|
988
|
+
thumbprint: key.thumbprint,
|
|
702
989
|
machineUuid
|
|
703
990
|
});
|
|
704
991
|
try {
|
|
705
|
-
atomicWrite0600(markerPath, marker);
|
|
992
|
+
(0, import_stateFiles.atomicWrite0600)(markerPath, marker, "write the enrollment marker");
|
|
706
993
|
} catch (err) {
|
|
994
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
707
995
|
emit("enrollment_marker_write_failed", false, {
|
|
708
996
|
profile,
|
|
709
|
-
thumbprint:
|
|
997
|
+
thumbprint: key.thumbprint,
|
|
710
998
|
message: err?.message ?? "unknown"
|
|
711
999
|
});
|
|
712
1000
|
}
|
|
713
1001
|
try {
|
|
714
|
-
|
|
1002
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the enrollment backoff");
|
|
715
1003
|
} catch (err) {
|
|
716
1004
|
if (err.code !== "ENOENT") {
|
|
717
1005
|
emit("enrollment_backoff_clear_failed", false, {
|
|
@@ -722,13 +1010,15 @@ async function enrollWithServer(opts) {
|
|
|
722
1010
|
}
|
|
723
1011
|
emit("enrolled_with_server", true, {
|
|
724
1012
|
profile,
|
|
725
|
-
thumbprint:
|
|
1013
|
+
thumbprint: key.thumbprint,
|
|
726
1014
|
serverUrl
|
|
727
1015
|
});
|
|
728
|
-
return { enrolled: true, thumbprint:
|
|
1016
|
+
return { enrolled: true, thumbprint: key.thumbprint };
|
|
729
1017
|
}
|
|
730
1018
|
// Annotate the CommonJS export names for ESM import in node:
|
|
731
1019
|
0 && (module.exports = {
|
|
1020
|
+
DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT,
|
|
1021
|
+
PENDING_HARDWARE_KEY_MAX_AGE_MS,
|
|
732
1022
|
__enrollmentBackoffPathForTest,
|
|
733
1023
|
__enrollmentMarkerPathForTest,
|
|
734
1024
|
__getStateForTest,
|
|
@@ -737,12 +1027,19 @@ async function enrollWithServer(opts) {
|
|
|
737
1027
|
__setStateForTest,
|
|
738
1028
|
__setUnreadableReloadIntervalForTest,
|
|
739
1029
|
clearEnrollmentMarker,
|
|
1030
|
+
commitDeviceKeyCandidate,
|
|
1031
|
+
commitPendingHardwareKey,
|
|
740
1032
|
describeDeviceIdentityState,
|
|
741
1033
|
describeDeviceKeySplit,
|
|
1034
|
+
describeStoredDeviceKey,
|
|
1035
|
+
discardPendingHardwareKey,
|
|
742
1036
|
enrollWithServer,
|
|
743
1037
|
getDevicePublicKey,
|
|
744
1038
|
initDeviceIdentity,
|
|
745
1039
|
isDeviceIdentityActive,
|
|
1040
|
+
persistPendingHardwareKey,
|
|
1041
|
+
prepareHardwareKeyCandidate,
|
|
746
1042
|
resetDeviceIdentity,
|
|
747
|
-
signServerRequest
|
|
1043
|
+
signServerRequest,
|
|
1044
|
+
signWithCandidate
|
|
748
1045
|
});
|