@skrr-ai/cli 0.1.122 → 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/chat.d.ts +7 -0
- package/dist/commands/agents/chat.js +32 -6
- 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/followups/act.js +3 -0
- package/dist/commands/followups/checkin.js +3 -0
- package/dist/commands/followups/remind.js +3 -0
- package/dist/commands/followups/show.js +1 -1
- package/dist/commands/followups/watch.js +3 -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/agentic-stream.d.ts +46 -0
- package/dist/lib/agentic-stream.js +135 -14
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/auth-failure.js +12 -0
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/exec-runtime-binary.d.ts +9 -0
- package/dist/lib/exec-runtime-binary.js +15 -1
- package/dist/lib/followups.d.ts +13 -0
- package/dist/lib/followups.js +8 -0
- package/dist/lib/keychain.js +41 -4
- package/dist/lib/profile-machine-identity-guard.js +2 -1
- package/dist/lib/runtime-start-verification.js +6 -1
- 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 +69 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +440 -83
- 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 +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +13 -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 +11 -2
- 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 +14 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +67 -0
- 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/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +1 -0
- 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 +24 -16
- 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/cjs/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +7 -1
- 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 +122 -38
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +603 -90
- 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 +14 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -0
- 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 +18 -2
- 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 +23 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +134 -0
- 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/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +1 -0
- 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 +45 -16
- 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/dist/esm/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +40 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7349 -7325
- package/oclif.manifest.json +11 -2
- package/package.json +4 -1
|
@@ -28,30 +28,45 @@ 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,
|
|
34
36
|
__privateKeyPathForTest: () => __privateKeyPathForTest,
|
|
35
37
|
__publicKeyPathForTest: () => __publicKeyPathForTest,
|
|
36
38
|
__setStateForTest: () => __setStateForTest,
|
|
39
|
+
__setUnreadableReloadIntervalForTest: () => __setUnreadableReloadIntervalForTest,
|
|
37
40
|
clearEnrollmentMarker: () => clearEnrollmentMarker,
|
|
41
|
+
commitDeviceKeyCandidate: () => commitDeviceKeyCandidate,
|
|
42
|
+
commitPendingHardwareKey: () => commitPendingHardwareKey,
|
|
38
43
|
describeDeviceIdentityState: () => describeDeviceIdentityState,
|
|
39
44
|
describeDeviceKeySplit: () => describeDeviceKeySplit,
|
|
45
|
+
describeStoredDeviceKey: () => describeStoredDeviceKey,
|
|
46
|
+
discardPendingHardwareKey: () => discardPendingHardwareKey,
|
|
40
47
|
enrollWithServer: () => enrollWithServer,
|
|
41
48
|
getDevicePublicKey: () => getDevicePublicKey,
|
|
42
49
|
initDeviceIdentity: () => initDeviceIdentity,
|
|
43
50
|
isDeviceIdentityActive: () => isDeviceIdentityActive,
|
|
51
|
+
persistPendingHardwareKey: () => persistPendingHardwareKey,
|
|
52
|
+
prepareHardwareKeyCandidate: () => prepareHardwareKeyCandidate,
|
|
44
53
|
resetDeviceIdentity: () => resetDeviceIdentity,
|
|
45
|
-
signServerRequest: () => signServerRequest
|
|
54
|
+
signServerRequest: () => signServerRequest,
|
|
55
|
+
signWithCandidate: () => signWithCandidate
|
|
46
56
|
});
|
|
47
57
|
module.exports = __toCommonJS(deviceIdentityBridge_exports);
|
|
48
58
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
49
59
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
50
60
|
var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
51
61
|
var import_deviceKey = require("./deviceKey.js");
|
|
62
|
+
var import_deviceKeyHandle = require("./deviceKeyHandle.js");
|
|
63
|
+
var import_deviceKeyHardware = require("./deviceKeyHardware.js");
|
|
52
64
|
var import_credentialEnvelopeBridge = require("./credentialEnvelopeBridge.js");
|
|
53
65
|
var import_profileStateDir = require("./profileStateDir.js");
|
|
66
|
+
var import_liveState = require("./liveState.js");
|
|
54
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";
|
|
55
70
|
const PUBLIC_KEY_FILENAME = "device-public-key.jwk";
|
|
56
71
|
const PRIVATE_KEY_FILENAME = "device-private-key";
|
|
57
72
|
const ENROLLMENT_MARKER_FILENAME = "device-enrollment.json";
|
|
@@ -65,9 +80,114 @@ const DEVICE_IDENTITY_FILES = [
|
|
|
65
80
|
const DAEMON_NOT_FOUND_BACKOFF_MS = 15 * 60 * 1e3;
|
|
66
81
|
let _state = { kind: "uninit" };
|
|
67
82
|
let _initializing = null;
|
|
83
|
+
let _unreadableSeen = null;
|
|
84
|
+
let _unreadableReloadIntervalMs = 5e3;
|
|
85
|
+
function __setUnreadableReloadIntervalForTest(ms) {
|
|
86
|
+
_unreadableReloadIntervalMs = ms ?? 5e3;
|
|
87
|
+
}
|
|
88
|
+
function keyFilesStamp(profile) {
|
|
89
|
+
const part = (file) => {
|
|
90
|
+
try {
|
|
91
|
+
const s = import_node_fs.default.statSync(file);
|
|
92
|
+
return `${s.mtimeMs}:${s.size}`;
|
|
93
|
+
} catch {
|
|
94
|
+
return "absent";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return `${part(publicKeyPath(profile))}|${part(privateKeyPath(profile))}`;
|
|
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
|
+
}
|
|
107
|
+
function reloadIfRecovered() {
|
|
108
|
+
reloadIfActiveKeyChanged();
|
|
109
|
+
if (_state.kind !== "disabled" || _state.reason !== "unreadable_key")
|
|
110
|
+
return;
|
|
111
|
+
const seen = _unreadableSeen;
|
|
112
|
+
if (!seen || _initializing)
|
|
113
|
+
return;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
if (now - seen.checkedAt < _unreadableReloadIntervalMs)
|
|
116
|
+
return;
|
|
117
|
+
seen.checkedAt = now;
|
|
118
|
+
let stamp;
|
|
119
|
+
try {
|
|
120
|
+
stamp = keyFilesStamp(seen.profile);
|
|
121
|
+
} catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (stamp === seen.stamp)
|
|
125
|
+
return;
|
|
126
|
+
seen.stamp = stamp;
|
|
127
|
+
let key = null;
|
|
128
|
+
try {
|
|
129
|
+
key = loadStoredKey(seen.profile);
|
|
130
|
+
} catch {
|
|
131
|
+
key = null;
|
|
132
|
+
}
|
|
133
|
+
if (!key)
|
|
134
|
+
return;
|
|
135
|
+
_state = {
|
|
136
|
+
kind: "active",
|
|
137
|
+
key,
|
|
138
|
+
profile: seen.profile,
|
|
139
|
+
envelopeProtected: key.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
|
|
140
|
+
};
|
|
141
|
+
_unreadableSeen = null;
|
|
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
|
+
});
|
|
187
|
+
}
|
|
68
188
|
async function withDeviceIdentityLock(profile, body) {
|
|
69
189
|
const dir = (0, import_profileStateDir.profileStateDir)(profile);
|
|
70
|
-
|
|
190
|
+
(0, import_stateFiles.ensureStateDir)(dir, `take the device identity lock for profile ${profile}`);
|
|
71
191
|
const release = await import_proper_lockfile.default.lock(dir, {
|
|
72
192
|
realpath: false,
|
|
73
193
|
lockfilePath: import_node_path.default.join(dir, ".device-identity.lock"),
|
|
@@ -123,29 +243,6 @@ function enrollmentMarkerPath(profile) {
|
|
|
123
243
|
function enrollmentBackoffPath(profile) {
|
|
124
244
|
return import_node_path.default.join(profileConfigDir(profile), ENROLLMENT_BACKOFF_FILENAME);
|
|
125
245
|
}
|
|
126
|
-
function atomicWrite0600(target, value) {
|
|
127
|
-
const dir = import_node_path.default.dirname(target);
|
|
128
|
-
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
129
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
130
|
-
const buf = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
|
|
131
|
-
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
132
|
-
try {
|
|
133
|
-
import_node_fs.default.writeSync(fd, buf);
|
|
134
|
-
import_node_fs.default.fsyncSync(fd);
|
|
135
|
-
} finally {
|
|
136
|
-
import_node_fs.default.closeSync(fd);
|
|
137
|
-
}
|
|
138
|
-
import_node_fs.default.renameSync(tmp, target);
|
|
139
|
-
try {
|
|
140
|
-
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
141
|
-
try {
|
|
142
|
-
import_node_fs.default.fsyncSync(dirFd);
|
|
143
|
-
} finally {
|
|
144
|
-
import_node_fs.default.closeSync(dirFd);
|
|
145
|
-
}
|
|
146
|
-
} catch {
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
246
|
function emit(eventName, success, metadata, opts = {}) {
|
|
150
247
|
(0, import_runtime.emitAuthTelemetry)({
|
|
151
248
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -163,17 +260,31 @@ function emit(eventName, success, metadata, opts = {}) {
|
|
|
163
260
|
}
|
|
164
261
|
}
|
|
165
262
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
publicJwk
|
|
169
|
-
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",
|
|
170
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
|
|
171
280
|
});
|
|
172
|
-
|
|
281
|
+
}
|
|
282
|
+
function persistKeypair(profile, kp) {
|
|
283
|
+
writePublicKeyRecord(profile, kp.publicJwk);
|
|
173
284
|
const privatePlaintext = JSON.stringify(kp.privateJwk);
|
|
174
285
|
const wireForm = (0, import_credentialEnvelopeBridge.wrapIfActiveOrPassthrough)(privatePlaintext);
|
|
175
286
|
const envelopeProtected = wireForm !== privatePlaintext;
|
|
176
|
-
atomicWrite0600(privateKeyPath(profile), wireForm);
|
|
287
|
+
(0, import_stateFiles.atomicWrite0600)(privateKeyPath(profile), wireForm, `write the device private key for profile ${profile}`);
|
|
177
288
|
emit("keypair_persisted", true, {
|
|
178
289
|
profile,
|
|
179
290
|
thumbprint: kp.thumbprint,
|
|
@@ -214,7 +325,7 @@ function readIndependentIdentityFile(file, sealed) {
|
|
|
214
325
|
import_node_fs.default.closeSync(fd);
|
|
215
326
|
}
|
|
216
327
|
}
|
|
217
|
-
function
|
|
328
|
+
function loadStoredKey(profile) {
|
|
218
329
|
const pubPath = publicKeyPath(profile);
|
|
219
330
|
const privPath = privateKeyPath(profile);
|
|
220
331
|
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
@@ -228,6 +339,8 @@ function loadKeypair(profile) {
|
|
|
228
339
|
let storedThumbprint;
|
|
229
340
|
try {
|
|
230
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");
|
|
231
344
|
publicJwk = parsed.publicJwk;
|
|
232
345
|
storedThumbprint = parsed.thumbprint;
|
|
233
346
|
} catch (err) {
|
|
@@ -237,7 +350,22 @@ function loadKeypair(profile) {
|
|
|
237
350
|
});
|
|
238
351
|
return null;
|
|
239
352
|
}
|
|
240
|
-
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
|
+
});
|
|
241
369
|
if (decryptResult.plaintext === null) {
|
|
242
370
|
emit("private_key_decrypt_failed", false, { profile });
|
|
243
371
|
return null;
|
|
@@ -253,7 +381,7 @@ function loadKeypair(profile) {
|
|
|
253
381
|
});
|
|
254
382
|
return null;
|
|
255
383
|
}
|
|
256
|
-
if (publicJwk.x !== privateJwk.x) {
|
|
384
|
+
if (publicJwk.kty !== "OKP" || publicJwk.x !== privateJwk.x) {
|
|
257
385
|
emit("keypair_inconsistent", false, { profile });
|
|
258
386
|
return null;
|
|
259
387
|
}
|
|
@@ -265,16 +393,20 @@ function loadKeypair(profile) {
|
|
|
265
393
|
recomputed
|
|
266
394
|
});
|
|
267
395
|
}
|
|
268
|
-
|
|
269
|
-
publicJwk,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
}
|
|
273
405
|
}
|
|
274
406
|
function deviceIdentityOf(dir) {
|
|
275
407
|
try {
|
|
276
408
|
const parsed = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(dir, PUBLIC_KEY_FILENAME), "utf-8"));
|
|
277
|
-
if (!
|
|
409
|
+
if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
|
|
278
410
|
return null;
|
|
279
411
|
return (0, import_deviceKey.jwkThumbprint)(parsed.publicJwk);
|
|
280
412
|
} catch {
|
|
@@ -317,6 +449,7 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
317
449
|
onEvent: (name, success, meta) => emit(name, success, meta)
|
|
318
450
|
});
|
|
319
451
|
} catch (err) {
|
|
452
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
320
453
|
emit("path_consolidation.failed", false, {
|
|
321
454
|
profile,
|
|
322
455
|
message: err?.message ?? "unknown"
|
|
@@ -337,6 +470,7 @@ async function initDeviceIdentity(profile, options = {}) {
|
|
|
337
470
|
if (_state.kind === "uninit")
|
|
338
471
|
await initDeviceIdentityLocked(profile, options);
|
|
339
472
|
}).catch((err) => {
|
|
473
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
340
474
|
_state = { kind: "disabled", reason: "init_failed" };
|
|
341
475
|
emit("init_failed", false, {
|
|
342
476
|
profile,
|
|
@@ -356,22 +490,24 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
356
490
|
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
357
491
|
if (!independent)
|
|
358
492
|
await consolidateDeviceIdentity(profile);
|
|
359
|
-
let
|
|
493
|
+
let stored = null;
|
|
360
494
|
try {
|
|
361
|
-
|
|
495
|
+
stored = loadStoredKey(profile);
|
|
362
496
|
} catch (err) {
|
|
363
497
|
emit("load_failed", false, {
|
|
364
498
|
profile,
|
|
365
499
|
message: err?.message ?? "unknown"
|
|
366
500
|
});
|
|
367
|
-
|
|
501
|
+
stored = null;
|
|
368
502
|
}
|
|
369
|
-
if (
|
|
370
|
-
const envelopeProtected = (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)();
|
|
371
|
-
_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);
|
|
372
507
|
emit("active", true, {
|
|
373
508
|
profile,
|
|
374
|
-
thumbprint:
|
|
509
|
+
thumbprint: stored.thumbprint,
|
|
510
|
+
custody: stored.custody,
|
|
375
511
|
envelopeProtected,
|
|
376
512
|
regenerated: false
|
|
377
513
|
});
|
|
@@ -380,6 +516,7 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
380
516
|
const hasStoredKey = storedKeyMaterialExists(profile);
|
|
381
517
|
if (hasStoredKey && (!options.recoverUnreadable || independent)) {
|
|
382
518
|
_state = { kind: "disabled", reason: "unreadable_key" };
|
|
519
|
+
_unreadableSeen = independent ? null : { profile, stamp: keyFilesStamp(profile), checkedAt: Date.now() };
|
|
383
520
|
emit("recovery_required", false, { profile, reason: "stored_key_unreadable" });
|
|
384
521
|
return;
|
|
385
522
|
}
|
|
@@ -404,6 +541,7 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
404
541
|
try {
|
|
405
542
|
persistResult = persistKeypair(profile, fresh);
|
|
406
543
|
} catch (err) {
|
|
544
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
407
545
|
_state = { kind: "disabled", reason: "persist_failed" };
|
|
408
546
|
emit("persist_failed", false, {
|
|
409
547
|
profile,
|
|
@@ -411,15 +549,29 @@ async function initDeviceIdentityLocked(profile, options = {}) {
|
|
|
411
549
|
});
|
|
412
550
|
return;
|
|
413
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
|
+
}
|
|
414
564
|
_state = {
|
|
415
565
|
kind: "active",
|
|
416
|
-
|
|
566
|
+
key: freshKey,
|
|
417
567
|
profile,
|
|
418
568
|
envelopeProtected: persistResult.envelopeProtected
|
|
419
569
|
};
|
|
570
|
+
trackActiveKeyFiles(profile);
|
|
420
571
|
emit("active", true, {
|
|
421
572
|
profile,
|
|
422
573
|
thumbprint: fresh.thumbprint,
|
|
574
|
+
custody: "software",
|
|
423
575
|
envelopeProtected: persistResult.envelopeProtected,
|
|
424
576
|
regenerated: true
|
|
425
577
|
});
|
|
@@ -428,12 +580,18 @@ async function resetDeviceIdentity(opts) {
|
|
|
428
580
|
if (_initializing)
|
|
429
581
|
await _initializing;
|
|
430
582
|
if (opts?.clearOnDisk && opts.profile) {
|
|
431
|
-
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
|
+
];
|
|
432
589
|
await withDeviceIdentityLock(opts.profile, async () => {
|
|
433
590
|
for (const p of targets) {
|
|
434
591
|
try {
|
|
435
|
-
|
|
592
|
+
(0, import_stateFiles.removeStateFile)(p, `delete the device key file ${import_node_path.default.basename(p)}`);
|
|
436
593
|
} catch (err) {
|
|
594
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
437
595
|
if (err.code !== "ENOENT") {
|
|
438
596
|
emit("clear_failed", false, {
|
|
439
597
|
profile: opts.profile,
|
|
@@ -446,34 +604,210 @@ async function resetDeviceIdentity(opts) {
|
|
|
446
604
|
});
|
|
447
605
|
}
|
|
448
606
|
_state = { kind: "uninit" };
|
|
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
|
+
});
|
|
449
752
|
}
|
|
450
753
|
function isDeviceIdentityActive() {
|
|
754
|
+
reloadIfRecovered();
|
|
451
755
|
return _state.kind === "active";
|
|
452
756
|
}
|
|
453
757
|
function getDevicePublicKey() {
|
|
758
|
+
reloadIfRecovered();
|
|
454
759
|
if (_state.kind !== "active")
|
|
455
760
|
return null;
|
|
456
|
-
return {
|
|
761
|
+
return {
|
|
762
|
+
jwk: _state.key.publicJwk,
|
|
763
|
+
thumbprint: _state.key.thumbprint,
|
|
764
|
+
alg: _state.key.alg,
|
|
765
|
+
custody: _state.key.custody
|
|
766
|
+
};
|
|
457
767
|
}
|
|
458
768
|
function describeDeviceIdentityState() {
|
|
769
|
+
reloadIfRecovered();
|
|
459
770
|
if (_state.kind === "disabled") {
|
|
460
771
|
return { kind: "disabled", reason: _state.reason };
|
|
461
772
|
}
|
|
462
773
|
if (_state.kind === "active") {
|
|
463
774
|
return {
|
|
464
775
|
kind: "active",
|
|
465
|
-
thumbprint: _state.
|
|
776
|
+
thumbprint: _state.key.thumbprint,
|
|
466
777
|
profile: _state.profile,
|
|
467
|
-
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
|
|
468
782
|
};
|
|
469
783
|
}
|
|
470
784
|
return { kind: "uninit" };
|
|
471
785
|
}
|
|
472
|
-
function
|
|
473
|
-
|
|
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) {
|
|
805
|
+
reloadIfRecovered();
|
|
806
|
+
const state = _state;
|
|
807
|
+
if (state.kind !== "active")
|
|
474
808
|
return null;
|
|
475
809
|
try {
|
|
476
|
-
return (0, import_deviceKey.
|
|
810
|
+
return await (0, import_deviceKey.signProofWith)(state.key, {
|
|
477
811
|
method: opts.method,
|
|
478
812
|
url: opts.url,
|
|
479
813
|
refreshToken: opts.refreshToken,
|
|
@@ -481,9 +815,13 @@ function signServerRequest(opts) {
|
|
|
481
815
|
});
|
|
482
816
|
} catch (err) {
|
|
483
817
|
emit("sign_failed", false, {
|
|
484
|
-
profile:
|
|
485
|
-
thumbprint:
|
|
818
|
+
profile: state.profile,
|
|
819
|
+
thumbprint: state.key.thumbprint,
|
|
820
|
+
custody: state.key.custody,
|
|
486
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 } : {},
|
|
487
825
|
message: err?.message ?? "unknown"
|
|
488
826
|
});
|
|
489
827
|
return null;
|
|
@@ -504,8 +842,9 @@ function __enrollmentBackoffPathForTest(profile) {
|
|
|
504
842
|
function clearEnrollmentMarker(profile) {
|
|
505
843
|
for (const markerPath of [enrollmentMarkerPath(profile), enrollmentBackoffPath(profile)]) {
|
|
506
844
|
try {
|
|
507
|
-
|
|
845
|
+
(0, import_stateFiles.removeStateFile)(markerPath, `delete the enrollment file ${import_node_path.default.basename(markerPath)}`);
|
|
508
846
|
} catch (err) {
|
|
847
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
509
848
|
if (err.code !== "ENOENT") {
|
|
510
849
|
emit("enrollment_marker_clear_failed", false, {
|
|
511
850
|
profile,
|
|
@@ -522,7 +861,7 @@ async function enrollWithServer(opts) {
|
|
|
522
861
|
return { enrolled: false, reason: "not_active" };
|
|
523
862
|
}
|
|
524
863
|
const profile = _state.profile;
|
|
525
|
-
const
|
|
864
|
+
const key = _state.key;
|
|
526
865
|
const markerPath = enrollmentMarkerPath(profile);
|
|
527
866
|
const backoffPath = enrollmentBackoffPath(profile);
|
|
528
867
|
try {
|
|
@@ -534,27 +873,33 @@ async function enrollWithServer(opts) {
|
|
|
534
873
|
try {
|
|
535
874
|
const raw = JSON.parse(import_node_fs.default.readFileSync(backoffPath, "utf-8"));
|
|
536
875
|
const retryAfter = Date.parse(raw.retryAfter || "");
|
|
537
|
-
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()) {
|
|
538
877
|
return { enrolled: false, reason: "backoff_active" };
|
|
539
878
|
}
|
|
540
|
-
|
|
879
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the stale enrollment backoff");
|
|
541
880
|
} catch (err) {
|
|
542
881
|
if (err.code !== "ENOENT") {
|
|
543
882
|
try {
|
|
544
|
-
|
|
545
|
-
} catch {
|
|
883
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the corrupt enrollment backoff");
|
|
884
|
+
} catch (clearErr) {
|
|
885
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(clearErr);
|
|
546
886
|
}
|
|
547
887
|
}
|
|
548
888
|
}
|
|
549
889
|
const base = serverUrl.replace(/\/+$/, "");
|
|
550
890
|
const htu = `${base}/api/daemon-resources/enroll-device-key`;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
url: htu
|
|
554
|
-
})
|
|
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
|
+
}
|
|
555
899
|
const body = JSON.stringify({
|
|
556
900
|
machineUuid,
|
|
557
|
-
publicJwkX:
|
|
901
|
+
...key.publicJwk.kty === "OKP" ? { publicJwkX: key.publicJwk.x } : {},
|
|
902
|
+
publicJwk: key.publicJwk,
|
|
558
903
|
proofJws
|
|
559
904
|
});
|
|
560
905
|
const ownAbort = new AbortController();
|
|
@@ -575,7 +920,7 @@ async function enrollWithServer(opts) {
|
|
|
575
920
|
const message = err?.message ?? "unknown";
|
|
576
921
|
emit("enrollment_network_error", false, {
|
|
577
922
|
profile,
|
|
578
|
-
thumbprint:
|
|
923
|
+
thumbprint: key.thumbprint,
|
|
579
924
|
serverUrl,
|
|
580
925
|
message
|
|
581
926
|
});
|
|
@@ -595,13 +940,13 @@ async function enrollWithServer(opts) {
|
|
|
595
940
|
} catch {
|
|
596
941
|
}
|
|
597
942
|
}
|
|
598
|
-
const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !==
|
|
943
|
+
const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !== key.thumbprint;
|
|
599
944
|
const keyPinnedElsewhere = response.status === 409 && conflictCode === "ENROLLMENT_DUPLICATE_KEY";
|
|
600
945
|
const isBenignRejection = response.status === 404 && responseText.includes("DAEMON_NOT_FOUND") || response.status === 403 && responseText.includes("OPAQUE_AUTH_REFUSED") || response.status === 409 && !pinnedToOtherKey && !keyPinnedElsewhere;
|
|
601
946
|
if (pinnedToOtherKey) {
|
|
602
947
|
emit("enrollment_key_mismatch", false, {
|
|
603
948
|
profile,
|
|
604
|
-
thumbprint:
|
|
949
|
+
thumbprint: key.thumbprint,
|
|
605
950
|
enrolledThumbprint,
|
|
606
951
|
serverUrl,
|
|
607
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."
|
|
@@ -609,13 +954,14 @@ async function enrollWithServer(opts) {
|
|
|
609
954
|
}
|
|
610
955
|
if (response.status === 404 && responseText.includes("DAEMON_NOT_FOUND")) {
|
|
611
956
|
try {
|
|
612
|
-
atomicWrite0600(backoffPath, JSON.stringify({
|
|
957
|
+
(0, import_stateFiles.atomicWrite0600)(backoffPath, JSON.stringify({
|
|
613
958
|
serverUrl,
|
|
614
959
|
machineUuid,
|
|
615
|
-
thumbprint:
|
|
960
|
+
thumbprint: key.thumbprint,
|
|
616
961
|
retryAfter: new Date(Date.now() + DAEMON_NOT_FOUND_BACKOFF_MS).toISOString()
|
|
617
|
-
}));
|
|
962
|
+
}), "write the enrollment backoff");
|
|
618
963
|
} catch (err) {
|
|
964
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
619
965
|
emit("enrollment_backoff_write_failed", false, {
|
|
620
966
|
profile,
|
|
621
967
|
message: err?.message ?? "unknown"
|
|
@@ -624,7 +970,7 @@ async function enrollWithServer(opts) {
|
|
|
624
970
|
}
|
|
625
971
|
emit("enrollment_rejected", false, {
|
|
626
972
|
profile,
|
|
627
|
-
thumbprint:
|
|
973
|
+
thumbprint: key.thumbprint,
|
|
628
974
|
serverUrl,
|
|
629
975
|
status: response.status,
|
|
630
976
|
body: responseText
|
|
@@ -639,20 +985,21 @@ async function enrollWithServer(opts) {
|
|
|
639
985
|
const marker = JSON.stringify({
|
|
640
986
|
enrolledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
641
987
|
serverUrl,
|
|
642
|
-
thumbprint:
|
|
988
|
+
thumbprint: key.thumbprint,
|
|
643
989
|
machineUuid
|
|
644
990
|
});
|
|
645
991
|
try {
|
|
646
|
-
atomicWrite0600(markerPath, marker);
|
|
992
|
+
(0, import_stateFiles.atomicWrite0600)(markerPath, marker, "write the enrollment marker");
|
|
647
993
|
} catch (err) {
|
|
994
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
648
995
|
emit("enrollment_marker_write_failed", false, {
|
|
649
996
|
profile,
|
|
650
|
-
thumbprint:
|
|
997
|
+
thumbprint: key.thumbprint,
|
|
651
998
|
message: err?.message ?? "unknown"
|
|
652
999
|
});
|
|
653
1000
|
}
|
|
654
1001
|
try {
|
|
655
|
-
|
|
1002
|
+
(0, import_stateFiles.removeStateFile)(backoffPath, "delete the enrollment backoff");
|
|
656
1003
|
} catch (err) {
|
|
657
1004
|
if (err.code !== "ENOENT") {
|
|
658
1005
|
emit("enrollment_backoff_clear_failed", false, {
|
|
@@ -663,26 +1010,36 @@ async function enrollWithServer(opts) {
|
|
|
663
1010
|
}
|
|
664
1011
|
emit("enrolled_with_server", true, {
|
|
665
1012
|
profile,
|
|
666
|
-
thumbprint:
|
|
1013
|
+
thumbprint: key.thumbprint,
|
|
667
1014
|
serverUrl
|
|
668
1015
|
});
|
|
669
|
-
return { enrolled: true, thumbprint:
|
|
1016
|
+
return { enrolled: true, thumbprint: key.thumbprint };
|
|
670
1017
|
}
|
|
671
1018
|
// Annotate the CommonJS export names for ESM import in node:
|
|
672
1019
|
0 && (module.exports = {
|
|
1020
|
+
DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT,
|
|
1021
|
+
PENDING_HARDWARE_KEY_MAX_AGE_MS,
|
|
673
1022
|
__enrollmentBackoffPathForTest,
|
|
674
1023
|
__enrollmentMarkerPathForTest,
|
|
675
1024
|
__getStateForTest,
|
|
676
1025
|
__privateKeyPathForTest,
|
|
677
1026
|
__publicKeyPathForTest,
|
|
678
1027
|
__setStateForTest,
|
|
1028
|
+
__setUnreadableReloadIntervalForTest,
|
|
679
1029
|
clearEnrollmentMarker,
|
|
1030
|
+
commitDeviceKeyCandidate,
|
|
1031
|
+
commitPendingHardwareKey,
|
|
680
1032
|
describeDeviceIdentityState,
|
|
681
1033
|
describeDeviceKeySplit,
|
|
1034
|
+
describeStoredDeviceKey,
|
|
1035
|
+
discardPendingHardwareKey,
|
|
682
1036
|
enrollWithServer,
|
|
683
1037
|
getDevicePublicKey,
|
|
684
1038
|
initDeviceIdentity,
|
|
685
1039
|
isDeviceIdentityActive,
|
|
1040
|
+
persistPendingHardwareKey,
|
|
1041
|
+
prepareHardwareKeyCandidate,
|
|
686
1042
|
resetDeviceIdentity,
|
|
687
|
-
signServerRequest
|
|
1043
|
+
signServerRequest,
|
|
1044
|
+
signWithCandidate
|
|
688
1045
|
});
|