sliftutils 1.7.128 → 1.7.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/bin/addmachine.js +9 -0
  2. package/bin/machineclient.js +9 -0
  3. package/bin/machineserver.js +10 -0
  4. package/index.d.ts +15 -0
  5. package/misc/dist/ownIPs.ts.cache +69 -0
  6. package/misc/ownIPs.d.ts +11 -0
  7. package/misc/ownIPs.ts +62 -0
  8. package/package.json +9 -3
  9. package/security/authorizedKeys/authorizedKeys.ts +34 -0
  10. package/security/authorizedKeys/daemon/authLog.ts +12 -5
  11. package/security/authorizedKeys/daemon/changes.ts +16 -1
  12. package/security/authorizedKeys/daemon/daemon.ts +43 -34
  13. package/security/authorizedKeys/daemon/dist/changes.ts.cache +40 -0
  14. package/security/authorizedKeys/daemon/dist/git.ts.cache +121 -0
  15. package/security/authorizedKeys/daemon/dist/notify.ts.cache +39 -0
  16. package/security/authorizedKeys/daemon/dist/paths.ts.cache +26 -0
  17. package/security/authorizedKeys/daemon/dist/repoFiles.ts.cache +114 -0
  18. package/security/authorizedKeys/daemon/dist/revocation.ts.cache +0 -0
  19. package/security/authorizedKeys/daemon/dist/sessions.ts.cache +147 -0
  20. package/security/authorizedKeys/daemon/dist/state.ts.cache +76 -0
  21. package/security/authorizedKeys/daemon/dist/trust.ts.cache +295 -0
  22. package/security/authorizedKeys/daemon/notify.ts +8 -2
  23. package/security/authorizedKeys/daemon/paths.ts +0 -3
  24. package/security/authorizedKeys/daemon/repoFiles.ts +118 -0
  25. package/security/authorizedKeys/daemon/revocation.ts +0 -0
  26. package/security/authorizedKeys/daemon/rootKeys.ts +77 -10
  27. package/security/authorizedKeys/daemon/state.ts +21 -7
  28. package/security/authorizedKeys/daemon/trust.ts +37 -16
  29. package/security/authorizedKeys/daemon/userKeys.ts +5 -4
  30. package/security/authorizedKeys/dist/authorizedKeys.ts.cache +37 -3
  31. package/security/authorizedKeys/dist/keysRepo.ts.cache +111 -0
  32. package/security/authorizedKeys/dist/revokeSource.ts.cache +17 -4
  33. package/security/authorizedKeys/dist/secureSSH.ts.cache +147 -71
  34. package/security/authorizedKeys/dist/sources.ts.cache +52 -8
  35. package/security/authorizedKeys/dist/unrevoke.ts.cache +159 -44
  36. package/security/authorizedKeys/keysRepo.ts +111 -0
  37. package/security/authorizedKeys/secureSSH.ts +66 -58
  38. package/security/authorizedKeys/unrevoke.ts +160 -36
  39. package/security/helpers/dist/remoteSSH.ts.cache +19 -9
  40. package/security/machines/addMachine.ts +84 -0
  41. package/security/machines/dist/addMachine.ts.cache +88 -0
  42. package/security/machines/dist/identity.ts.cache +176 -0
  43. package/security/machines/dist/machines.ts.cache +264 -0
  44. package/security/machines/dist/trustClient.ts.cache +48 -0
  45. package/security/machines/dist/trustState.ts.cache +57 -0
  46. package/security/machines/identity.ts +199 -0
  47. package/security/machines/machines.ts +304 -0
  48. package/security/machines/trustClient.ts +51 -0
  49. package/security/machines/trustServer.ts +87 -0
  50. package/security/notifications/discord.ts +14 -5
  51. package/security/notifications/dist/discord.ts.cache +17 -8
  52. package/security/notifications/dist/remoteWebhook.ts.cache +78 -0
  53. package/security/notifications/dist/setupNotify.ts.cache +39 -0
  54. package/security/signedFiles/dist/signFiles.ts.cache +8 -10
  55. package/security/signedFiles/signFiles.ts +6 -10
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
6
+ //exports.isMachineAccepted = exports.setMachines = exports.getMachines = exports.MACHINES_DIR = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const keysRepo_1 = require("../authorizedKeys/keysRepo");
11
+ const repoFiles_1 = require("../authorizedKeys/daemon/repoFiles");
12
+ const repoFiles_2 = require("../authorizedKeys/daemon/repoFiles");
13
+ const revocation_1 = require("../authorizedKeys/daemon/revocation");
14
+ const git_1 = require("../authorizedKeys/daemon/git");
15
+ const repoFiles_3 = require("../authorizedKeys/daemon/repoFiles");
16
+ const trust_1 = require("../authorizedKeys/daemon/trust");
17
+ const revokeSource_1 = require("../authorizedKeys/revokeSource");
18
+ const notify_1 = require("../authorizedKeys/daemon/notify");
19
+ const discord_1 = require("../notifications/discord");
20
+ // Which machines this system talks to, kept in the same repo as the ssh keys. That repo is already
21
+ // signed, already distributed to every machine, and already has somewhere to record a rejection,
22
+ // so a second one would only be a second thing to keep in step.
23
+ exports.MACHINES_DIR = "machines";
24
+ const REVOCATIONS_DIR = "revocations";
25
+ const REVOCATION_REASON = "a machine talked to us from an unapproved IP";
26
+ // The revoke repo is pulled at most this often. A check happens per request, and a request must
27
+ // not cost a round trip to github.
28
+ const REVOKE_SYNC_INTERVAL = 60 * 1000;
29
+ async function pathExists(filePath) {
30
+ try {
31
+ await promises_1.default.access(filePath);
32
+ return true;
33
+ }
34
+ catch (e) {
35
+ return false;
36
+ }
37
+ }
38
+ function machineFilePath(repoPath, machineId) {
39
+ return path_1.default.join(repoPath, exports.MACHINES_DIR, `${machineId}.json`);
40
+ }
41
+ /** Every machine a checkout lists. */
42
+ async function getMachines(repoPath) {
43
+ return [...(await readMachines(repoPath)).values()];
44
+ }
45
+ exports.getMachines = getMachines;
46
+ /** Makes a checkout list exactly these machines: those named are written with the addresses given,
47
+ and anything not named is removed. So read them, change what you want, and write the result.
48
+
49
+ The addresses are part of setting rather than a separate step, because a machine with no
50
+ address it may talk from is not a machine that would ever be accepted - there is no state where
51
+ naming one without them means anything.
52
+
53
+ Nothing here signs or commits. The signature is what every other machine checks before
54
+ believing any of this, and it takes the hardware key, so `yarn signfiles git` is still yours to
55
+ run afterwards. */
56
+ async function setMachines(config) {
57
+ var _a;
58
+ let { repoPath, machines } = config;
59
+ let existing = await readMachines(repoPath);
60
+ let directory = path_1.default.join(repoPath, exports.MACHINES_DIR);
61
+ let written = [];
62
+ for (let machine of machines) {
63
+ if (!machine.machineId) {
64
+ throw new Error(`Expected a machineId, was ${JSON.stringify(machine.machineId)}`);
65
+ }
66
+ if (!machine.ips.length) {
67
+ throw new Error(`Expected addresses for ${machine.machineId}, was none. A machine is trusted from`
68
+ + ` the addresses it may talk from, so leave it out of the list to remove it.`);
69
+ }
70
+ let ips = machine.ips.filter((ip, index) => ip && machine.ips.indexOf(ip) === index);
71
+ let state = {
72
+ machineId: machine.machineId,
73
+ // In the order given, without duplicates, so the file reads the way it was set.
74
+ ips,
75
+ addedAt: ((_a = existing.get(machine.machineId)) === null || _a === void 0 ? void 0 : _a.addedAt) || new Date().toISOString(),
76
+ };
77
+ await promises_1.default.mkdir(directory, { recursive: true });
78
+ await promises_1.default.writeFile(machineFilePath(repoPath, state.machineId), JSON.stringify(state, undefined, 4) + "\n");
79
+ written.push(state);
80
+ }
81
+ // Whatever the repo had and the caller did not name is no longer trusted.
82
+ for (let machineId of existing.keys()) {
83
+ if (!machines.some(machine => machine.machineId === machineId)) {
84
+ await promises_1.default.rm(machineFilePath(repoPath, machineId), { force: true });
85
+ }
86
+ }
87
+ return written;
88
+ }
89
+ exports.setMachines = setMachines;
90
+ /** Every machine the repo lists. */
91
+ async function readMachines(repoPath) {
92
+ let machines = new Map();
93
+ let directory = path_1.default.join(repoPath, exports.MACHINES_DIR);
94
+ let names = await promises_1.default.readdir(directory).catch(() => []);
95
+ for (let name of names.sort()) {
96
+ if (!name.endsWith(".json")) {
97
+ continue;
98
+ }
99
+ try {
100
+ let parsed = JSON.parse(await promises_1.default.readFile(path_1.default.join(directory, name), "utf8"));
101
+ let machineId = parsed.machineId || name.replace(/\.json$/, "");
102
+ machines.set(machineId, { machineId, ips: parsed.ips || [], addedAt: parsed.addedAt || "" });
103
+ }
104
+ catch (e) {
105
+ console.log(`Ignoring unreadable machine file ${name}. ${e}`);
106
+ }
107
+ }
108
+ return machines;
109
+ }
110
+ let lastRevokeSync = 0;
111
+ /** Pulled at most once a REVOKE_SYNC_INTERVAL, because this is asked per request. A sync that
112
+ fails leaves the checkout we already have, which is the safe direction: revocations we know
113
+ about stay known. */
114
+ async function syncRevocations(sourceURL) {
115
+ if (Date.now() - lastRevokeSync < REVOKE_SYNC_INTERVAL) {
116
+ return;
117
+ }
118
+ lastRevokeSync = Date.now();
119
+ try {
120
+ await (0, repoFiles_1.syncRepoFiles)((0, repoFiles_1.revokeRepo)(sourceURL));
121
+ }
122
+ catch (e) {
123
+ console.log(`Could not read ${(0, revokeSource_1.revokeRepoURL)(sourceURL)}, using the revocations already here. ${e}`);
124
+ }
125
+ }
126
+ /** Machine revocations, read out of the revoke repo the same way key revocations are. */
127
+ async function readMachineRevocations(sourceURL) {
128
+ let repo = (0, repoFiles_1.revokeRepo)(sourceURL);
129
+ let revocations = [];
130
+ for (let name of await (0, repoFiles_2.listRepoDir)(repo, REVOCATIONS_DIR)) {
131
+ if (!name.endsWith(".json")) {
132
+ continue;
133
+ }
134
+ try {
135
+ let parsed = JSON.parse(await (0, repoFiles_2.readRepoFile)(repo, path_1.default.join(REVOCATIONS_DIR, name)) || "");
136
+ if (parsed.machineId) {
137
+ revocations.push({
138
+ revocationId: parsed.revocationId || name.replace(/\.json$/, ""),
139
+ machineId: parsed.machineId,
140
+ ip: parsed.ip || "",
141
+ });
142
+ }
143
+ }
144
+ catch (e) {
145
+ console.log(`Ignoring unreadable revocation ${name}. ${e}`);
146
+ }
147
+ }
148
+ return revocations;
149
+ }
150
+ /** Sends the one notification this file is allowed to send, when it can.
151
+
152
+ The daemon configures notifications on startup and would simply send. This can also run in some
153
+ other process, which has not, so the webhook is picked up here if it is readable. A machine
154
+ with no webhook set up still records the revocation - being unable to tell anyone is not a
155
+ reason to keep accepting a machine that is being misused. */
156
+ async function notifyBestEffort(headline, body) {
157
+ try {
158
+ if (!(0, discord_1.areDiscordNotificationsConfigured)()) {
159
+ // Read first: configureDiscordNotifications exits the process when the file is missing,
160
+ // which is right for the daemon at startup and wrong for a library call.
161
+ await promises_1.default.readFile(discord_1.DEFAULT_WEBHOOK_FILE_PATH, "utf8");
162
+ await (0, discord_1.configureDiscordNotifications)({ filePath: discord_1.DEFAULT_WEBHOOK_FILE_PATH });
163
+ }
164
+ await (0, notify_1.notify)(headline, body);
165
+ }
166
+ catch (e) {
167
+ console.log(`Could not send a notification about this, it is only in the log. ${e}`);
168
+ }
169
+ }
170
+ /** Records that a machine we accept talked to us from an address it is not allowed from. One per
171
+ machine and address, so a second address is a second revocation and an unrevoke of the first
172
+ does not cover it. */
173
+ async function recordMachineRevocation(config) {
174
+ let { sourceURL, machineId, ip, hostLabel } = config;
175
+ let repoPath = (0, revokeSource_1.revokeRepoPath)(sourceURL);
176
+ let keyPath = await (0, repoFiles_3.ensureRevokeKey)(sourceURL);
177
+ let revocationId = (0, revocation_1.newRevocationId)(machineId);
178
+ let directory = path_1.default.join(repoPath, REVOCATIONS_DIR);
179
+ await promises_1.default.mkdir(directory, { recursive: true });
180
+ await promises_1.default.writeFile(path_1.default.join(directory, `${revocationId}.json`), JSON.stringify({
181
+ revocationId,
182
+ machineId,
183
+ ip,
184
+ revokedAt: new Date().toISOString(),
185
+ revokedBy: hostLabel,
186
+ reason: REVOCATION_REASON,
187
+ }, undefined, 4) + "\n");
188
+ await (0, git_1.runGit)({ args: ["add", "-A"], cwd: repoPath, keyPath });
189
+ await (0, git_1.runGit)({
190
+ args: ["-c", "user.email=portsecure@localhost", "-c", "user.name=portsecure", "commit", "-m", `revoke ${revocationId}`],
191
+ cwd: repoPath, keyPath,
192
+ });
193
+ let push = await (0, git_1.runGit)({ args: ["push", "origin", "HEAD"], cwd: repoPath, keyPath, allowFailure: true });
194
+ if (push.status !== 0) {
195
+ // Another machine most likely recorded the same thing first, and the next read picks it up.
196
+ console.log(`Could not push the revocation of ${machineId} from ${ip}. ${(push.stdout + push.stderr).trim()}`);
197
+ return;
198
+ }
199
+ console.log(`Revoked ${machineId} from ${ip}, ${revocationId}`);
200
+ // Said by whoever wrote the revocation, once, the same as for an ssh key. Machines that only
201
+ // read it later say nothing, or one event would be reported by every machine that saw it.
202
+ await notifyBestEffort(`SUSPICIOUS IP ${ip} FROZE MACHINE ${machineId}`, `A machine we trust talked to us from ${ip}, which is not an address it is allowed to talk`
203
+ + ` from. It proved it holds that machine's key, so either someone else has a copy of it,`
204
+ + ` or that machine's address changed.`
205
+ + `\n\nIt is frozen everywhere now, and nothing accepts it.`
206
+ + `\n\nIf this was an attack, remove \`machines/${machineId}.json\` from \`${sourceURL}\` now.`
207
+ + `\nIf it was legitimate, run \`yarn unrevoke git\` in that repo. It allows ${ip} for that`
208
+ + ` machine, and takes effect as each machine picks it up.`
209
+ + `\n\nmachine: \`${machineId}\``
210
+ + `\nfrozen by: \`${hostLabel}\``);
211
+ }
212
+ async function isMachineAccepted(config) {
213
+ let { machineId, ip } = config;
214
+ if (!machineId || !ip) {
215
+ throw new Error(`Expected a machineId and an ip, was ${JSON.stringify(machineId)} and ${JSON.stringify(ip)}`);
216
+ }
217
+ let { repoPath, sourceURL } = await (0, keysRepo_1.resolveKeysRepo)();
218
+ // Unsigned, or signed over different contents, means the machine list is not evidence of
219
+ // anything. Same gate the ssh keys go through.
220
+ await (0, trust_1.verifyCheckout)(repoPath);
221
+ let machine = (await readMachines(repoPath)).get(machineId);
222
+ if (!machine) {
223
+ return { accepted: false, reason: "that machine is not trusted" };
224
+ }
225
+ await syncRevocations(sourceURL);
226
+ let unrevokes = await (0, revocation_1.readUnrevokes)(sourceURL).catch(() => ({ pairs: new Map(), legacyIds: new Map() }));
227
+ // An unrevoke counts as soon as it is seen. It is signed, so writing one already takes the
228
+ // hardware key, and anyone holding that can sign anything at all - a wait before honouring it
229
+ // guards against an attacker who has no need of it.
230
+ let allowedAgain = (pair) => { var _a; return !!((_a = unrevokes.pairs.get(pair)) === null || _a === void 0 ? void 0 : _a.length); };
231
+ let revocations = await readMachineRevocations(sourceURL);
232
+ // Any revocation nothing has undone keeps the machine out, from everywhere, the way a revoked
233
+ // ssh key is out everywhere rather than only from the address it was misused from.
234
+ let frozenFrom = "";
235
+ for (let revocation of revocations) {
236
+ if (revocation.machineId !== machineId) {
237
+ continue;
238
+ }
239
+ if (!allowedAgain((0, revocation_1.pairKey)({ fingerprint: revocation.machineId, ip: revocation.ip }))) {
240
+ frozenFrom = revocation.ip;
241
+ }
242
+ }
243
+ if (frozenFrom) {
244
+ return {
245
+ accepted: false,
246
+ reason: `that machine is frozen, it was used from ${frozenFrom} which it is not allowed from`,
247
+ };
248
+ }
249
+ if (machine.ips.includes(ip)) {
250
+ return { accepted: true, reason: "" };
251
+ }
252
+ // Listed, but talking to us from somewhere it should not be. Recorded once for this machine
253
+ // and address, so being talked to repeatedly does not write repeatedly.
254
+ let pair = (0, revocation_1.pairKey)({ fingerprint: machineId, ip });
255
+ let alreadyRecorded = revocations.some(revocation => (0, revocation_1.pairKey)({ fingerprint: revocation.machineId, ip: revocation.ip }) === pair);
256
+ if (!alreadyRecorded && !allowedAgain(pair)) {
257
+ await recordMachineRevocation({ sourceURL, machineId, ip, hostLabel: os_1.default.hostname() });
258
+ return { accepted: false, reason: `that machine is not allowed from ${ip}, so it is now frozen everywhere` };
259
+ }
260
+ return { accepted: false, reason: `that machine is not allowed from ${ip}` };
261
+ }
262
+ exports.isMachineAccepted = isMachineAccepted;
263
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFjaGluZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJtYWNoaW5lcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSwyREFBNkI7QUFDN0IsNENBQW9CO0FBQ3BCLGdEQUF3QjtBQUN4Qix5REFBNkQ7QUFDN0Qsa0VBQStFO0FBQy9FLGtFQUErRTtBQUMvRSxvRUFBbUg7QUFDbkgsc0RBQXNEO0FBQ3RELGtFQUFxRTtBQUNyRSwwREFBZ0U7QUFDaEUsaUVBQStFO0FBQy9FLDREQUF5RDtBQUN6RCxzREFBdUk7QUFHdkksbUdBQW1HO0FBQ25HLGlHQUFpRztBQUNqRyxnRUFBZ0U7QUFDbkQsUUFBQSxZQUFZLEdBQUcsVUFBVSxDQUFDO0FBQ3ZDLE1BQU0sZUFBZSxHQUFHLGFBQWEsQ0FBQztBQUN0QyxNQUFNLGlCQUFpQixHQUFHLDhDQUE4QyxDQUFDO0FBQ3pFLGdHQUFnRztBQUNoRyxtQ0FBbUM7QUFDbkMsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO0FBU3ZDLEtBQUssVUFBVSxVQUFVLENBQUMsUUFBZ0I7SUFDdEMsSUFBSSxDQUFDO1FBQ0QsTUFBTSxrQkFBRSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUMxQixPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNULE9BQU8sS0FBSyxDQUFDO0lBQ2pCLENBQUM7QUFDTCxDQUFDO0FBRUQsU0FBUyxlQUFlLENBQUMsUUFBZ0IsRUFBRSxTQUFpQjtJQUN4RCxPQUFPLGNBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLG9CQUFZLEVBQUUsR0FBRyxTQUFTLE9BQU8sQ0FBQyxDQUFDO0FBQ2xFLENBQUM7QUFFRCxzQ0FBc0M7QUFDL0IsS0FBSyxVQUFVLFdBQVcsQ0FBQyxRQUFnQjtJQUM5QyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMs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cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZzIGZyb20gXCJmcy9wcm9taXNlc1wiO1xuaW1wb3J0IG9zIGZyb20gXCJvc1wiO1xuaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IHJlc29sdmVLZXlzUmVwbyB9IGZyb20gXCIuLi9hdXRob3JpemVkS2V5cy9rZXlzUmVwb1wiO1xuaW1wb3J0IHsgcmV2b2tlUmVwbywgc3luY1JlcG9GaWxlcyB9IGZyb20gXCIuLi9hdXRob3JpemVkS2V5cy9kYWVtb24vcmVwb0ZpbGVzXCI7XG5pbXBvcnQgeyBsaXN0UmVwb0RpciwgcmVhZFJlcG9GaWxlIH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL2RhZW1vbi9yZXBvRmlsZXNcIjtcbmltcG9ydCB7IG5ld1Jldm9jYXRpb25JZCwgcGFpcktleSwgcmVhZFJldm9jYXRpb25GaWxlcywgcmVhZFVucmV2b2tlcyB9IGZyb20gXCIuLi9hdXRob3JpemVkS2V5cy9kYWVtb24vcmV2b2NhdGlvblwiO1xuaW1wb3J0IHsgcnVuR2l0IH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL2RhZW1vbi9naXRcIjtcbmltcG9ydCB7IGVuc3VyZVJldm9rZUtleSB9IGZyb20gXCIuLi9hdXRob3JpemVkS2V5cy9kYWVtb24vcmVwb0ZpbGVzXCI7XG5pbXBvcnQgeyB2ZXJpZnlDaGVja291dCB9IGZyb20gXCIuLi9hdXRob3JpemVkS2V5cy9kYWVtb24vdHJ1c3RcIjtcbmltcG9ydCB7IHJldm9rZVJlcG9QYXRoLCByZXZva2VSZXBvVVJMIH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL3Jldm9rZVNvdXJjZVwiO1xuaW1wb3J0IHsgbm90aWZ5IH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL2RhZW1vbi9ub3RpZnlcIjtcbmltcG9ydCB7IGFyZURpc2NvcmROb3RpZmljYXRpb25zQ29uZmlndXJlZCwgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMsIERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEggfSBmcm9tIFwiLi4vbm90aWZpY2F0aW9ucy9kaXNjb3JkXCI7XG5pbXBvcnQgeyBzcGF3blByb21pc2UgfSBmcm9tIFwiLi4vaGVscGVycy9zcGF3blwiO1xuXG4vLyBXaGljaCBtYWNoaW5lcyB0aGlzIHN5c3RlbSB0YWxrcyB0bywga2VwdCBpbiB0aGUgc2FtZSByZXBvIGFzIHRoZSBzc2gga2V5cy4gVGhhdCByZXBvIGlzIGFscmVhZHlcbi8vIHNpZ25lZCwgYWxyZWFkeSBkaXN0cmlidXRlZCB0byBldmVyeSBtYWNoaW5lLCBhbmQgYWxyZWFkeSBoYXMgc29tZXdoZXJlIHRvIHJlY29yZCBhIHJlamVjdGlvbixcbi8vIHNvIGEgc2Vjb25kIG9uZSB3b3VsZCBvbmx5IGJlIGEgc2Vjb25kIHRoaW5nIHRvIGtlZXAgaW4gc3RlcC5cbmV4cG9ydCBjb25zdCBNQUNISU5FU19ESVIgPSBcIm1hY2hpbmVzXCI7XG5jb25zdCBSRVZPQ0FUSU9OU19ESVIgPSBcInJldm9jYXRpb25zXCI7XG5jb25zdCBSRVZPQ0FUSU9OX1JFQVNPTiA9IFwiYSBtYWNoaW5lIHRhbGtlZCB0byB1cyBmcm9tIGFuIHVuYXBwcm92ZWQgSVBcIjtcbi8vIFRoZSByZXZva2UgcmVwbyBpcyBwdWxsZWQgYXQgbW9zdCB0aGlzIG9mdGVuLiBBIGNoZWNrIGhhcHBlbnMgcGVyIHJlcXVlc3QsIGFuZCBhIHJlcXVlc3QgbXVzdFxuLy8gbm90IGNvc3QgYSByb3VuZCB0cmlwIHRvIGdpdGh1Yi5cbmNvbnN0IFJFVk9LRV9TWU5DX0lOVEVSVkFMID0gNjAgKiAxMDAwO1xuXG4vKiogT25lIG1hY2hpbmUgd2UgYXJlIHdpbGxpbmcgdG8gdGFsayB0bywgYW5kIHRoZSBhZGRyZXNzZXMgaXQgbWF5IHRhbGsgdG8gdXMgZnJvbS4gKi9cbmV4cG9ydCB0eXBlIE1hY2hpbmVTdGF0ZSA9IHtcbiAgICBtYWNoaW5lSWQ6IHN0cmluZztcbiAgICBpcHM6IHN0cmluZ1tdO1xuICAgIGFkZGVkQXQ6IHN0cmluZztcbn07XG5cbmFzeW5jIGZ1bmN0aW9uIHBhdGhFeGlzdHMoZmlsZVBhdGg6IHN0cmluZykge1xuICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGZzLmFjY2VzcyhmaWxlUGF0aCk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gbWFjaGluZUZpbGVQYXRoKHJlcG9QYXRoOiBzdHJpbmcsIG1hY2hpbmVJZDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHBhdGguam9pbihyZXBvUGF0aCwgTUFDSElORVNfRElSLCBgJHttYWNoaW5lSWR9Lmpzb25gKTtcbn1cblxuLyoqIEV2ZXJ5IG1hY2hpbmUgYSBjaGVja291dCBsaXN0cy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRNYWNoaW5lcyhyZXBvUGF0aDogc3RyaW5nKTogUHJvbWlzZTxNYWNoaW5lU3RhdGVbXT4ge1xuICAgIHJldHVybiBbLi4uKGF3YWl0IHJlYWRNYWNoaW5lcyhyZXBvUGF0aCkpLnZhbHVlcygpXTtcbn1cblxuLyoqIE1ha2VzIGEgY2hlY2tvdXQgbGlzdCBleGFjdGx5IHRoZXNlIG1hY2hpbmVzOiB0aG9zZSBuYW1lZCBhcmUgd3JpdHRlbiB3aXRoIHRoZSBhZGRyZXNzZXMgZ2l2ZW4sXG4gICAgYW5kIGFueXRoaW5nIG5vdCBuYW1lZCBpcyByZW1vdmVkLiBTbyByZWFkIHRoZW0sIGNoYW5nZSB3aGF0IHlvdSB3YW50LCBhbmQgd3JpdGUgdGhlIHJlc3VsdC5cblxuICAgIFRoZSBhZGRyZXNzZXMgYXJlIHBhcnQgb2Ygc2V0dGluZyByYXRoZXIgdGhhbiBhIHNlcGFyYXRlIHN0ZXAsIGJlY2F1c2UgYSBtYWNoaW5lIHdpdGggbm9cbiAgICBhZGRyZXNzIGl0IG1heSB0YWxrIGZyb20gaXMgbm90IGEgbWFjaGluZSB0aGF0IHdvdWxkIGV2ZXIgYmUgYWNjZXB0ZWQgLSB0aGVyZSBpcyBubyBzdGF0ZSB3aGVyZVxuICAgIG5hbWluZyBvbmUgd2l0aG91dCB0aGVtIG1lYW5zIGFueXRoaW5nLlxuXG4gICAgTm90aGluZyBoZXJlIHNpZ25zIG9yIGNvbW1pdHMuIFRoZSBzaWduYXR1cmUgaXMgd2hhdCBldmVyeSBvdGhlciBtYWNoaW5lIGNoZWNrcyBiZWZvcmVcbiAgICBiZWxpZXZpbmcgYW55IG9mIHRoaXMsIGFuZCBpdCB0YWtlcyB0aGUgaGFyZHdhcmUga2V5LCBzbyBgeWFybiBzaWduZmlsZXMgZ2l0YCBpcyBzdGlsbCB5b3VycyB0b1xuICAgIHJ1biBhZnRlcndhcmRzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNldE1hY2hpbmVzKGNvbmZpZzoge1xuICAgIHJlcG9QYXRoOiBzdHJpbmc7XG4gICAgbWFjaGluZXM6IHsgbWFjaGluZUlkOiBzdHJpbmc7IGlwczogc3RyaW5nW10gfVtdO1xufSk6IFByb21pc2U8TWFjaGluZVN0YXRlW10+IHtcbiAgICBsZXQgeyByZXBvUGF0aCwgbWFjaGluZXMgfSA9IGNvbmZpZztcbiAgICBsZXQgZXhpc3RpbmcgPSBhd2FpdCByZWFkTWFjaGluZXMocmVwb1BhdGgpO1xuICAgIGxldCBkaXJlY3RvcnkgPSBwYXRoLmpvaW4ocmVwb1BhdGgsIE1BQ0hJTkVTX0RJUik7XG4gICAgbGV0IHdyaXR0ZW46IE1hY2hpbmVTdGF0ZVtdID0gW107XG4gICAgZm9yIChsZXQgbWFjaGluZSBvZiBtYWNoaW5lcykge1xuICAgICAgICBpZiAoIW1hY2hpbmUubWFjaGluZUlkKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgbWFjaGluZUlkLCB3YXMgJHtKU09OLnN0cmluZ2lmeShtYWNoaW5lLm1hY2hpbmVJZCl9YCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFtYWNoaW5lLmlwcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBgRXhwZWN0ZWQgYWRkcmVzc2VzIGZvciAke21hY2hpbmUubWFjaGluZUlkfSwgd2FzIG5vbmUuIEEgbWFjaGluZSBpcyB0cnVzdGVkIGZyb21gXG4gICAgICAgICAgICAgICAgKyBgIHRoZSBhZGRyZXNzZXMgaXQgbWF5IHRhbGsgZnJvbSwgc28gbGVhdmUgaXQgb3V0IG9mIHRoZSBsaXN0IHRvIHJlbW92ZSBpdC5gXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIGxldCBpcHMgPSBtYWNoaW5lLmlwcy5maWx0ZXIoKGlwLCBpbmRleCkgPT4gaXAgJiYgbWFjaGluZS5pcHMuaW5kZXhPZihpcCkgPT09IGluZGV4KTtcbiAgICAgICAgbGV0IHN0YXRlOiBNYWNoaW5lU3RhdGUgPSB7XG4gICAgICAgICAgICBtYWNoaW5lSWQ6IG1hY2hpbmUubWFjaGluZUlkLFxuICAgICAgICAgICAgLy8gSW4gdGhlIG9yZGVyIGdpdmVuLCB3aXRob3V0IGR1cGxpY2F0ZXMsIHNvIHRoZSBmaWxlIHJlYWRzIHRoZSB3YXkgaXQgd2FzIHNldC5cbiAgICAgICAgICAgIGlwcyxcbiAgICAgICAgICAgIGFkZGVkQXQ6IGV4aXN0aW5nLmdldChtYWNoaW5lLm1hY2hpbmVJZCk/LmFkZGVkQXQgfHwgbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgICB9O1xuICAgICAgICBhd2FpdCBmcy5ta2RpcihkaXJlY3RvcnksIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgICAgICBhd2FpdCBmcy53cml0ZUZpbGUobWFjaGluZUZpbGVQYXRoKHJlcG9QYXRoLCBzdGF0ZS5tYWNoaW5lSWQpLCBKU09OLnN0cmluZ2lmeShzdGF0ZSwgdW5kZWZpbmVkLCA0KSArIFwiXFxuXCIpO1xuICAgICAgICB3cml0dGVuLnB1c2goc3RhdGUpO1xuICAgIH1cblxuICAgIC8vIFdoYXRldmVyIHRoZSByZXBvIGhhZCBhbmQgdGhlIGNhbGxlciBkaWQgbm90IG5hbWUgaXMgbm8gbG9uZ2VyIHRydXN0ZWQuXG4gICAgZm9yIChsZXQgbWFjaGluZUlkIG9mIGV4aXN0aW5nLmtleXMoKSkge1xuICAgICAgICBpZiAoIW1hY2hpbmVzLnNvbWUobWFjaGluZSA9PiBtYWNoaW5lLm1hY2hpbmVJZCA9PT0gbWFjaGluZUlkKSkge1xuICAgICAgICAgICAgYXdhaXQgZnMucm0obWFjaGluZUZpbGVQYXRoKHJlcG9QYXRoLCBtYWNoaW5lSWQpLCB7IGZvcmNlOiB0cnVlIH0pO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiB3cml0dGVuO1xufVxuXG4vKiogRXZlcnkgbWFjaGluZSB0aGUgcmVwbyBsaXN0cy4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlYWRNYWNoaW5lcyhyZXBvUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IG1hY2hpbmVzID0gbmV3IE1hcDxzdHJpbmcsIE1hY2hpbmVTdGF0ZT4oKTtcbiAgICBsZXQgZGlyZWN0b3J5ID0gcGF0aC5qb2luKHJlcG9QYXRoLCBNQUNISU5FU19ESVIpO1xuICAgIGxldCBuYW1lcyA9IGF3YWl0IGZzLnJlYWRkaXIoZGlyZWN0b3J5KS5jYXRjaCgoKSA9PiBbXSBhcyBzdHJpbmdbXSk7XG4gICAgZm9yIChsZXQgbmFtZSBvZiBuYW1lcy5zb3J0KCkpIHtcbiAgICAgICAgaWYgKCFuYW1lLmVuZHNXaXRoKFwiLmpzb25cIikpIHtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBsZXQgcGFyc2VkID0gSlNPTi5wYXJzZShhd2FpdCBmcy5yZWFkRmlsZShwYXRoLmpvaW4oZGlyZWN0b3J5LCBuYW1lKSwgXCJ1dGY4XCIpKTtcbiAgICAgICAgICAgIGxldCBtYWNoaW5lSWQgPSBwYXJzZWQubWFjaGluZUlkIHx8IG5hbWUucmVwbGFjZSgvXFwuanNvbiQvLCBcIlwiKTtcbiAgICAgICAgICAgIG1hY2hpbmVzLnNldChtYWNoaW5lSWQsIHsgbWFjaGluZUlkLCBpcHM6IHBhcnNlZC5pcHMgfHwgW10sIGFkZGVkQXQ6IHBhcnNlZC5hZGRlZEF0IHx8IFwiXCIgfSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKGBJZ25vcmluZyB1bnJlYWRhYmxlIG1hY2hpbmUgZmlsZSAke25hbWV9LiAke2V9YCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG1hY2hpbmVzO1xufVxuXG5sZXQgbGFzdFJldm9rZVN5bmMgPSAwO1xuXG4vKiogUHVsbGVkIGF0IG1vc3Qgb25jZSBhIFJFVk9LRV9TWU5DX0lOVEVSVkFMLCBiZWNhdXNlIHRoaXMgaXMgYXNrZWQgcGVyIHJlcXVlc3QuIEEgc3luYyB0aGF0XG4gICAgZmFpbHMgbGVhdmVzIHRoZSBjaGVja291dCB3ZSBhbHJlYWR5IGhhdmUsIHdoaWNoIGlzIHRoZSBzYWZlIGRpcmVjdGlvbjogcmV2b2NhdGlvbnMgd2Uga25vd1xuICAgIGFib3V0IHN0YXkga25vd24uICovXG5hc3luYyBmdW5jdGlvbiBzeW5jUmV2b2NhdGlvbnMoc291cmNlVVJMOiBzdHJpbmcpIHtcbiAgICBpZiAoRGF0ZS5ub3coKSAtIGxhc3RSZXZva2VTeW5jIDwgUkVWT0tFX1NZTkNfSU5URVJWQUwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsYXN0UmV2b2tlU3luYyA9IERhdGUubm93KCk7XG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgc3luY1JlcG9GaWxlcyhyZXZva2VSZXBvKHNvdXJjZVVSTCkpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5sb2coYENvdWxkIG5vdCByZWFkICR7cmV2b2tlUmVwb1VSTChzb3VyY2VVUkwpfSwgdXNpbmcgdGhlIHJldm9jYXRpb25zIGFscmVhZHkgaGVyZS4gJHtlfWApO1xuICAgIH1cbn1cblxuLyoqIE1hY2hpbmUgcmV2b2NhdGlvbnMsIHJlYWQgb3V0IG9mIHRoZSByZXZva2UgcmVwbyB0aGUgc2FtZSB3YXkga2V5IHJldm9jYXRpb25zIGFyZS4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlYWRNYWNoaW5lUmV2b2NhdGlvbnMoc291cmNlVVJMOiBzdHJpbmcpIHtcbiAgICBsZXQgcmVwbyA9IHJldm9rZVJlcG8oc291cmNlVVJMKTtcbiAgICBsZXQgcmV2b2NhdGlvbnM6IHsgcmV2b2NhdGlvbklkOiBzdHJpbmc7IG1hY2hpbmVJZDogc3RyaW5nOyBpcDogc3RyaW5nIH1bXSA9IFtdO1xuICAgIGZvciAobGV0IG5hbWUgb2YgYXdhaXQgbGlzdFJlcG9EaXIocmVwbywgUkVWT0NBVElPTlNfRElSKSkge1xuICAgICAgICBpZiAoIW5hbWUuZW5kc1dpdGgoXCIuanNvblwiKSkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGxldCBwYXJzZWQgPSBKU09OLnBhcnNlKGF3YWl0IHJlYWRSZXBvRmlsZShyZXBvLCBwYXRoLmpvaW4oUkVWT0NBVElPTlNfRElSLCBuYW1lKSkgfHwgXCJcIik7XG4gICAgICAgICAgICBpZiAocGFyc2VkLm1hY2hpbmVJZCkge1xuICAgICAgICAgICAgICAgIHJldm9jYXRpb25zLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICByZXZvY2F0aW9uSWQ6IHBhcnNlZC5yZXZvY2F0aW9uSWQgfHwgbmFtZS5yZXBsYWNlKC9cXC5qc29uJC8sIFwiXCIpLFxuICAgICAgICAgICAgICAgICAgICBtYWNoaW5lSWQ6IHBhcnNlZC5tYWNoaW5lSWQsXG4gICAgICAgICAgICAgICAgICAgIGlwOiBwYXJzZWQuaXAgfHwgXCJcIixcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgY29uc29sZS5sb2coYElnbm9yaW5nIHVucmVhZGFibGUgcmV2b2NhdGlvbiAke25hbWV9LiAke2V9YCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJldm9jYXRpb25zO1xufVxuXG4vKiogU2VuZHMgdGhlIG9uZSBub3RpZmljYXRpb24gdGhpcyBmaWxlIGlzIGFsbG93ZWQgdG8gc2VuZCwgd2hlbiBpdCBjYW4uXG5cbiAgICBUaGUgZGFlbW9uIGNvbmZpZ3VyZXMgbm90aWZpY2F0aW9ucyBvbiBzdGFydHVwIGFuZCB3b3VsZCBzaW1wbHkgc2VuZC4gVGhpcyBjYW4gYWxzbyBydW4gaW4gc29tZVxuICAgIG90aGVyIHByb2Nlc3MsIHdoaWNoIGhhcyBub3QsIHNvIHRoZSB3ZWJob29rIGlzIHBpY2tlZCB1cCBoZXJlIGlmIGl0IGlzIHJlYWRhYmxlLiBBIG1hY2hpbmVcbiAgICB3aXRoIG5vIHdlYmhvb2sgc2V0IHVwIHN0aWxsIHJlY29yZHMgdGhlIHJldm9jYXRpb24gLSBiZWluZyB1bmFibGUgdG8gdGVsbCBhbnlvbmUgaXMgbm90IGFcbiAgICByZWFzb24gdG8ga2VlcCBhY2NlcHRpbmcgYSBtYWNoaW5lIHRoYXQgaXMgYmVpbmcgbWlzdXNlZC4gKi9cbmFzeW5jIGZ1bmN0aW9uIG5vdGlmeUJlc3RFZmZvcnQoaGVhZGxpbmU6IHN0cmluZywgYm9keTogc3RyaW5nKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgaWYgKCFhcmVEaXNjb3JkTm90aWZpY2F0aW9uc0NvbmZpZ3VyZWQoKSkge1xuICAgICAgICAgICAgLy8gUmVhZCBmaXJzdDogY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMgZXhpdHMgdGhlIHByb2Nlc3Mgd2hlbiB0aGUgZmlsZSBpcyBtaXNzaW5nLFxuICAgICAgICAgICAgLy8gd2hpY2ggaXMgcmlnaHQgZm9yIHRoZSBkYWVtb24gYXQgc3RhcnR1cCBhbmQgd3JvbmcgZm9yIGEgbGlicmFyeSBjYWxsLlxuICAgICAgICAgICAgYXdhaXQgZnMucmVhZEZpbGUoREVGQVVMVF9XRUJIT09LX0ZJTEVfUEFUSCwgXCJ1dGY4XCIpO1xuICAgICAgICAgICAgYXdhaXQgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMoeyBmaWxlUGF0aDogREVGQVVMVF9XRUJIT09LX0ZJTEVfUEFUSCB9KTtcbiAgICAgICAgfVxuICAgICAgICBhd2FpdCBub3RpZnkoaGVhZGxpbmUsIGJvZHkpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5sb2coYENvdWxkIG5vdCBzZW5kIGEgbm90aWZpY2F0aW9uIGFib3V0IHRoaXMsIGl0IGlzIG9ubHkgaW4gdGhlIGxvZy4gJHtlfWApO1xuICAgIH1cbn1cblxuLyoqIFJlY29yZHMgdGhhdCBhIG1hY2hpbmUgd2UgYWNjZXB0IHRhbGtlZCB0byB1cyBmcm9tIGFuIGFkZHJlc3MgaXQgaXMgbm90IGFsbG93ZWQgZnJvbS4gT25lIHBlclxuICAgIG1hY2hpbmUgYW5kIGFkZHJlc3MsIHNvIGEgc2Vjb25kIGFkZHJlc3MgaXMgYSBzZWNvbmQgcmV2b2NhdGlvbiBhbmQgYW4gdW5yZXZva2Ugb2YgdGhlIGZpcnN0XG4gICAgZG9lcyBub3QgY292ZXIgaXQuICovXG5hc3luYyBmdW5jdGlvbiByZWNvcmRNYWNoaW5lUmV2b2NhdGlvbihjb25maWc6IHtcbiAgICBzb3VyY2VVUkw6IHN0cmluZztcbiAgICBtYWNoaW5lSWQ6IHN0cmluZztcbiAgICBpcDogc3RyaW5nO1xuICAgIGhvc3RMYWJlbDogc3RyaW5nO1xufSkge1xuICAgIGxldCB7IHNvdXJjZVVSTCwgbWFjaGluZUlkLCBpcCwgaG9zdExhYmVsIH0gPSBjb25maWc7XG4gICAgbGV0IHJlcG9QYXRoID0gcmV2b2tlUmVwb1BhdGgoc291cmNlVVJMKTtcbiAgICBsZXQga2V5UGF0aCA9IGF3YWl0IGVuc3VyZVJldm9rZUtleShzb3VyY2VVUkwpO1xuICAgIGxldCByZXZvY2F0aW9uSWQgPSBuZXdSZXZvY2F0aW9uSWQobWFjaGluZUlkKTtcbiAgICBsZXQgZGlyZWN0b3J5ID0gcGF0aC5qb2luKHJlcG9QYXRoLCBSRVZPQ0FUSU9OU19ESVIpO1xuICAgIGF3YWl0IGZzLm1rZGlyKGRpcmVjdG9yeSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKHBhdGguam9pbihkaXJlY3RvcnksIGAke3Jldm9jYXRpb25JZH0uanNvbmApLCBKU09OLnN0cmluZ2lmeSh7XG4gICAgICAgIHJldm9jYXRpb25JZCxcbiAgICAgICAgbWFjaGluZUlkLFxuICAgICAgICBpcCxcbiAgICAgICAgcmV2b2tlZEF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICAgIHJldm9rZWRCeTogaG9zdExhYmVsLFxuICAgICAgICByZWFzb246IFJFVk9DQVRJT05fUkVBU09OLFxuICAgIH0sIHVuZGVmaW5lZCwgNCkgKyBcIlxcblwiKTtcblxuICAgIGF3YWl0IHJ1bkdpdCh7IGFyZ3M6IFtcImFkZFwiLCBcIi1BXCJdLCBjd2Q6IHJlcG9QYXRoLCBrZXlQYXRoIH0pO1xuICAgIGF3YWl0IHJ1bkdpdCh7XG4gICAgICAgIGFyZ3M6IFtcIi1jXCIsIFwidXNlci5lbWFpbD1wb3J0c2VjdXJlQGxvY2FsaG9zdFwiLCBcIi1jXCIsIFwidXNlci5uYW1lPXBvcnRzZWN1cmVcIiwgXCJjb21taXRcIiwgXCItbVwiLCBgcmV2b2tlICR7cmV2b2NhdGlvbklkfWBdLFxuICAgICAgICBjd2Q6IHJlcG9QYXRoLCBrZXlQYXRoLFxuICAgIH0pO1xuICAgIGxldCBwdXNoID0gYXdhaXQgcnVuR2l0KHsgYXJnczogW1wicHVzaFwiLCBcIm9yaWdpblwiLCBcIkhFQURcIl0sIGN3ZDogcmVwb1BhdGgsIGtleVBhdGgsIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICBpZiAocHVzaC5zdGF0dXMgIT09IDApIHtcbiAgICAgICAgLy8gQW5vdGhlciBtYWNoaW5lIG1vc3QgbGlrZWx5IHJlY29yZGVkIHRoZSBzYW1lIHRoaW5nIGZpcnN0LCBhbmQgdGhlIG5leHQgcmVhZCBwaWNrcyBpdCB1cC5cbiAgICAgICAgY29uc29sZS5sb2coYENvdWxkIG5vdCBwdXNoIHRoZSByZXZvY2F0aW9uIG9mICR7bWFjaGluZUlkfSBmcm9tICR7aXB9LiAkeyhwdXNoLnN0ZG91dCArIHB1c2guc3RkZXJyKS50cmltKCl9YCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc29sZS5sb2coYFJldm9rZWQgJHttYWNoaW5lSWR9IGZyb20gJHtpcH0sICR7cmV2b2NhdGlvbklkfWApO1xuXG4gICAgLy8gU2FpZCBieSB3aG9ldmVyIHdyb3RlIHRoZSByZXZvY2F0aW9uLCBvbmNlLCB0aGUgc2FtZSBhcyBmb3IgYW4gc3NoIGtleS4gTWFjaGluZXMgdGhhdCBvbmx5XG4gICAgLy8gcmVhZCBpdCBsYXRlciBzYXkgbm90aGluZywgb3Igb25lIGV2ZW50IHdvdWxkIGJlIHJlcG9ydGVkIGJ5IGV2ZXJ5IG1hY2hpbmUgdGhhdCBzYXcgaXQuXG4gICAgYXdhaXQgbm90aWZ5QmVzdEVmZm9ydChcbiAgICAgICAgYFNVU1BJQ0lPVVMgSVAgJHtpcH0gRlJPWkUgTUFDSElORSAke21hY2hpbmVJZH1gLFxuICAgICAgICBgQSBtYWNoaW5lIHdlIHRydXN0IHRhbGtlZCB0byB1cyBmcm9tICR7aXB9LCB3aGljaCBpcyBub3QgYW4gYWRkcmVzcyBpdCBpcyBhbGxvd2VkIHRvIHRhbGtgXG4gICAgICAgICsgYCBmcm9tLiBJdCBwcm92ZWQgaXQgaG9sZHMgdGhhdCBtYWNoaW5lJ3Mga2V5LCBzbyBlaXRoZXIgc29tZW9uZSBlbHNlIGhhcyBhIGNvcHkgb2YgaXQsYFxuICAgICAgICArIGAgb3IgdGhhdCBtYWNoaW5lJ3MgYWRkcmVzcyBjaGFuZ2VkLmBcbiAgICAgICAgKyBgXFxuXFxuSXQgaXMgZnJvemVuIGV2ZXJ5d2hlcmUgbm93LCBhbmQgbm90aGluZyBhY2NlcHRzIGl0LmBcbiAgICAgICAgKyBgXFxuXFxuSWYgdGhpcyB3YXMgYW4gYXR0YWNrLCByZW1vdmUgXFxgbWFjaGluZXMvJHttYWNoaW5lSWR9Lmpzb25cXGAgZnJvbSBcXGAke3NvdXJjZVVSTH1cXGAgbm93LmBcbiAgICAgICAgKyBgXFxuSWYgaXQgd2FzIGxlZ2l0aW1hdGUsIHJ1biBcXGB5YXJuIHVucmV2b2tlIGdpdFxcYCBpbiB0aGF0IHJlcG8uIEl0IGFsbG93cyAke2lwfSBmb3IgdGhhdGBcbiAgICAgICAgKyBgIG1hY2hpbmUsIGFuZCB0YWtlcyBlZmZlY3QgYXMgZWFjaCBtYWNoaW5lIHBpY2tzIGl0IHVwLmBcbiAgICAgICAgKyBgXFxuXFxubWFjaGluZTogXFxgJHttYWNoaW5lSWR9XFxgYFxuICAgICAgICArIGBcXG5mcm96ZW4gYnk6IFxcYCR7aG9zdExhYmVsfVxcYGBcbiAgICApO1xufVxuXG4vKiogV2hldGhlciB3ZSB3aWxsIHRhbGsgdG8gdGhpcyBtYWNoaW5lLCBjb21pbmcgZnJvbSB0aGlzIGFkZHJlc3MuXG5cbiAgICBCb3RoIGhhbHZlcyBhcmUgcmVxdWlyZWQgYW5kIG5laXRoZXIgaXMgYSBndWVzczogdGhlIGNhbGxlciBrbm93cyB0aGUgbWFjaGluZSBpZCBiZWNhdXNlIHRoZVxuICAgIGNvbm5lY3Rpb24gcHJvdmVkIGl0LCBhbmQga25vd3MgdGhlIGFkZHJlc3MgYmVjYXVzZSB0aGUgcGFja2V0cyBjYW1lIGZyb20gdGhlcmUuIEEgbWFjaGluZSB3ZVxuICAgIGRvIG5vdCBsaXN0IGlzIHNpbXBseSBub3QgYWNjZXB0ZWQuIEEgbWFjaGluZSB3ZSBkbyBsaXN0LCBhcnJpdmluZyBmcm9tIGFuIGFkZHJlc3MgaXQgZG9lcyBub3RcbiAgICBoYXZlLCBpcyB0cmVhdGVkIGFzIHRoZSBzYW1lIGtpbmQgb2YgZXZlbnQgYXMgYSBzdG9sZW4gc3NoIGtleSAtIGl0IGlzIHJldm9rZWQgZXZlcnl3aGVyZSwgYW5kXG4gICAgc3RheXMgcmV2b2tlZCB1bnRpbCBhbiB1bnJldm9rZSBhbGxvd3MgdGhhdCBtYWNoaW5lIGZyb20gdGhhdCBhZGRyZXNzLlxuXG4gICAgU2F5cyB3aHkgd2hlbiB0aGUgYW5zd2VyIGlzIG5vLCBiZWNhdXNlIHRoZSB0aHJlZSB3YXlzIHRvIGJlIHJlZnVzZWQgYXJlIGVudGlyZWx5IGRpZmZlcmVudFxuICAgIHRoaW5ncyB0byBhIHBlcnNvbiByZWFkaW5nIGl0OiBuZXZlciB0cnVzdGVkIGF0IGFsbCwgdHJ1c3RlZCBidXQgZnJvemVuLCBvciB0cnVzdGVkIGFuZCB0YWxraW5nXG4gICAgZnJvbSBzb21ld2hlcmUgaXQgc2hvdWxkIG5vdCBiZS5cblxuICAgIFRocm93cyB3aGVuIHRoZSByZXBvIGl0c2VsZiBjYW5ub3QgYmUgcmVhZCBvciBpcyBub3Qgc2lnbmVkLCByYXRoZXIgdGhhbiBhbnN3ZXJpbmcgZmFsc2U6IHRoYXRcbiAgICBpcyBhIGJyb2tlbiBpbnN0YWxsYXRpb24sIG5vdCBhIHJlamVjdGVkIG1hY2hpbmUsIGFuZCB0aGUgdHdvIGRlc2VydmUgZGlmZmVyZW50IGhhbmRsaW5nLiAqL1xuZXhwb3J0IHR5cGUgTWFjaGluZVZlcmRpY3QgPSB7IGFjY2VwdGVkOiBib29sZWFuOyByZWFzb246IHN0cmluZyB9O1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaXNNYWNoaW5lQWNjZXB0ZWQoY29uZmlnOiB7IG1hY2hpbmVJZDogc3RyaW5nOyBpcDogc3RyaW5nIH0pOiBQcm9taXNlPE1hY2hpbmVWZXJkaWN0PiB7XG4gICAgbGV0IHsgbWFjaGluZUlkLCBpcCB9ID0gY29uZmlnO1xuICAgIGlmICghbWFjaGluZUlkIHx8ICFpcCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgbWFjaGluZUlkIGFuZCBhbiBpcCwgd2FzICR7SlNPTi5zdHJpbmdpZnkobWFjaGluZUlkKX0gYW5kICR7SlNPTi5zdHJpbmdpZnkoaXApfWApO1xuICAgIH1cbiAgICBsZXQgeyByZXBvUGF0aCwgc291cmNlVVJMIH0gPSBhd2FpdCByZXNvbHZlS2V5c1JlcG8oKTtcbiAgICAvLyBVbnNpZ25lZCwgb3Igc2lnbmVkIG92ZXIgZGlmZmVyZW50IGNvbnRlbnRzLCBtZWFucyB0aGUgbWFjaGluZSBsaXN0IGlzIG5vdCBldmlkZW5jZSBvZlxuICAgIC8vIGFueXRoaW5nLiBTYW1lIGdhdGUgdGhlIHNzaCBrZXlzIGdvIHRocm91Z2guXG4gICAgYXdhaXQgdmVyaWZ5Q2hlY2tvdXQocmVwb1BhdGgpO1xuXG4gICAgbGV0IG1hY2hpbmUgPSAoYXdhaXQgcmVhZE1hY2hpbmVzKHJlcG9QYXRoKSkuZ2V0KG1hY2hpbmVJZCk7XG4gICAgaWYgKCFtYWNoaW5lKSB7XG4gICAgICAgIHJldHVybiB7IGFjY2VwdGVkOiBmYWxzZSwgcmVhc29uOiBcInRoYXQgbWFjaGluZSBpcyBub3QgdHJ1c3RlZFwiIH07XG4gICAgfVxuXG4gICAgYXdhaXQgc3luY1Jldm9jYXRpb25zKHNvdXJjZVVSTCk7XG4gICAgbGV0IHVucmV2b2tlcyA9IGF3YWl0IHJlYWRVbnJldm9rZXMoc291cmNlVVJMKS5jYXRjaCgoKSA9PiAoeyBwYWlyczogbmV3IE1hcCgpLCBsZWdhY3lJZHM6IG5ldyBNYXAoKSB9KSk7XG4gICAgLy8gQW4gdW5yZXZva2UgY291bnRzIGFzIHNvb24gYXMgaXQgaXMgc2Vlbi4gSXQgaXMgc2lnbmVkLCBzbyB3cml0aW5nIG9uZSBhbHJlYWR5IHRha2VzIHRoZVxuICAgIC8vIGhhcmR3YXJlIGtleSwgYW5kIGFueW9uZSBob2xkaW5nIHRoYXQgY2FuIHNpZ24gYW55dGhpbmcgYXQgYWxsIC0gYSB3YWl0IGJlZm9yZSBob25vdXJpbmcgaXRcbiAgICAvLyBndWFyZHMgYWdhaW5zdCBhbiBhdHRhY2tlciB3aG8gaGFzIG5vIG5lZWQgb2YgaXQuXG4gICAgbGV0IGFsbG93ZWRBZ2FpbiA9IChwYWlyOiBzdHJpbmcpID0+ICEhdW5yZXZva2VzLnBhaXJzLmdldChwYWlyKT8ubGVuZ3RoO1xuICAgIGxldCByZXZvY2F0aW9ucyA9IGF3YWl0IHJlYWRNYWNoaW5lUmV2b2NhdGlvbnMoc291cmNlVVJMKTtcbiAgICAvLyBBbnkgcmV2b2NhdGlvbiBub3RoaW5nIGhhcyB1bmRvbmUga2VlcHMgdGhlIG1hY2hpbmUgb3V0LCBmcm9tIGV2ZXJ5d2hlcmUsIHRoZSB3YXkgYSByZXZva2VkXG4gICAgLy8gc3NoIGtleSBpcyBvdXQgZXZlcnl3aGVyZSByYXRoZXIgdGhhbiBvbmx5IGZyb20gdGhlIGFkZHJlc3MgaXQgd2FzIG1pc3VzZWQgZnJvbS5cbiAgICBsZXQgZnJvemVuRnJvbSA9IFwiXCI7XG4gICAgZm9yIChsZXQgcmV2b2NhdGlvbiBvZiByZXZvY2F0aW9ucykge1xuICAgICAgICBpZiAocmV2b2NhdGlvbi5tYWNoaW5lSWQgIT09IG1hY2hpbmVJZCkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFhbGxvd2VkQWdhaW4ocGFpcktleSh7IGZpbmdlcnByaW50OiByZXZvY2F0aW9uLm1hY2hpbmVJZCwgaXA6IHJldm9jYXRpb24uaXAgfSkpKSB7XG4gICAgICAgICAgICBmcm96ZW5Gcm9tID0gcmV2b2NhdGlvbi5pcDtcbiAgICAgICAgfVxuICAgIH1cbiAgICBpZiAoZnJvemVuRnJvbSkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYWNjZXB0ZWQ6IGZhbHNlLFxuICAgICAgICAgICAgcmVhc29uOiBgdGhhdCBtYWNoaW5lIGlzIGZyb3plbiwgaXQgd2FzIHVzZWQgZnJvbSAke2Zyb3plbkZyb219IHdoaWNoIGl0IGlzIG5vdCBhbGxvd2VkIGZyb21gLFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIGlmIChtYWNoaW5lLmlwcy5pbmNsdWRlcyhpcCkpIHtcbiAgICAgICAgcmV0dXJuIHsgYWNjZXB0ZWQ6IHRydWUsIHJlYXNvbjogXCJcIiB9O1xuICAgIH1cblxuICAgIC8vIExpc3RlZCwgYnV0IHRhbGtpbmcgdG8gdXMgZnJvbSBzb21ld2hlcmUgaXQgc2hvdWxkIG5vdCBiZS4gUmVjb3JkZWQgb25jZSBmb3IgdGhpcyBtYWNoaW5lXG4gICAgLy8gYW5kIGFkZHJlc3MsIHNvIGJlaW5nIHRhbGtlZCB0byByZXBlYXRlZGx5IGRvZXMgbm90IHdyaXRlIHJlcGVhdGVkbHkuXG4gICAgbGV0IHBhaXIgPSBwYWlyS2V5KHsgZmluZ2VycHJpbnQ6IG1hY2hpbmVJZCwgaXAgfSk7XG4gICAgbGV0IGFscmVhZHlSZWNvcmRlZCA9IHJldm9jYXRpb25zLnNvbWUocmV2b2NhdGlvbiA9PlxuICAgICAgICBwYWlyS2V5KHsgZmluZ2VycHJpbnQ6IHJldm9jYXRpb24ubWFjaGluZUlkLCBpcDogcmV2b2NhdGlvbi5pcCB9KSA9PT0gcGFpcik7XG4gICAgaWYgKCFhbHJlYWR5UmVjb3JkZWQgJiYgIWFsbG93ZWRBZ2FpbihwYWlyKSkge1xuICAgICAgICBhd2FpdCByZWNvcmRNYWNoaW5lUmV2b2NhdGlvbih7IHNvdXJjZVVSTCwgbWFjaGluZUlkLCBpcCwgaG9zdExhYmVsOiBvcy5ob3N0bmFtZSgpIH0pO1xuICAgICAgICByZXR1cm4geyBhY2NlcHRlZDogZmFsc2UsIHJlYXNvbjogYHRoYXQgbWFjaGluZSBpcyBub3QgYWxsb3dlZCBmcm9tICR7aXB9LCBzbyBpdCBpcyBub3cgZnJvemVuIGV2ZXJ5d2hlcmVgIH07XG4gICAgfVxuICAgIHJldHVybiB7IGFjY2VwdGVkOiBmYWxzZSwgcmVhc29uOiBgdGhhdCBtYWNoaW5lIGlzIG5vdCBhbGxvd2VkIGZyb20gJHtpcH1gIH07XG59XG4iXX0=
264
+ /* _JS_SOURCE_HASH = "4f49b3c9f4a58987204c92820f6aca5ff7cf73325e697e849445299f06638103"; */
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
+ //exports.main = exports.askIfTrusted = void 0;
4
+ const identity_1 = require("./identity");
5
+ // The other half of the demonstration: asks a server whether it trusts this machine. The answer
6
+ // depends on the address this machine reaches the server from, which is why the client cannot
7
+ // work it out alone.
8
+ const TRUST_PATH = "/trusted";
9
+ const USAGE = `Usage: yarn machineclient <url> <domain>
10
+
11
+ Asks that server whether this machine is trusted, proving which machine it is with a signed packet.
12
+ The server decides using the address we reach it from, so the answer is about how we are talking to
13
+ it, not only about who we are.
14
+
15
+ The domain is required: this machine has a different identity under each one, and the answer is
16
+ about the identity we sign with.`;
17
+ /** Asks one server whether it trusts this machine, as it is under this domain. */
18
+ async function askIfTrusted(config) {
19
+ let { baseURL, domain } = config;
20
+ let packet = await (0, identity_1.signTrustPacket)(domain);
21
+ let url = `${baseURL.replace(/\/+$/, "")}${TRUST_PATH}`;
22
+ let response = await fetch(url, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(packet),
26
+ });
27
+ return await response.json();
28
+ }
29
+ exports.askIfTrusted = askIfTrusted;
30
+ async function main() {
31
+ let [baseURL, domain] = process.argv.slice(2);
32
+ if (!baseURL || !domain) {
33
+ throw new Error(USAGE);
34
+ }
35
+ let answer = await askIfTrusted({ baseURL, domain });
36
+ console.log(`${answer.trusted && "TRUSTED" || "NOT TRUSTED"} by ${baseURL}`);
37
+ console.log(` machine ${answer.machineId || "(not established)"}`);
38
+ console.log(` seen coming from ${answer.ip || "(unknown)"}`);
39
+ if (answer.reason) {
40
+ console.log(` ${answer.reason}`);
41
+ }
42
+ if (!answer.trusted) {
43
+ process.exitCode = 1;
44
+ }
45
+ }
46
+ exports.main = main;
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJ1c3RDbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0cnVzdENsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5Q0FBNkM7QUFFN0MsZ0dBQWdHO0FBQ2hHLDhGQUE4RjtBQUM5RixxQkFBcUI7QUFDckIsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDO0FBQzlCLE1BQU0sS0FBSyxHQUFHOzs7Ozs7O2lDQU9tQixDQUFDO0FBU2xDLGtGQUFrRjtBQUMzRSxLQUFLLFVBQVUsWUFBWSxDQUFDLE1BQTJDO0lBQzFFLElBQUksRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxDQUFDO0lBQ2pDLElBQUksTUFBTSxHQUFHLE1BQU0sSUFBQSwwQkFBZSxFQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzNDLElBQUksR0FBRyxHQUFHLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLEdBQUcsVUFBVSxFQUFFLENBQUM7SUFDeEQsSUFBSSxRQUFRLEdBQUcsTUFBTSxLQUFLLENBQUMsR0FBRyxFQUFFO1FBQzVCLE1BQU0sRUFBRSxNQUFNO1FBQ2QsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFO1FBQy9DLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztLQUMvQixDQUFDLENBQUM7SUFDSCxPQUFPLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBaUIsQ0FBQztBQUNoRCxDQUFDO0FBVkQsb0NBVUM7QUFFTSxLQUFLLFVBQVUsSUFBSTtJQUN0QixJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzlDLElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN0QixNQUFNLElBQUksS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNCLENBQUM7SUFDRCxJQUFJLE1BQU0sR0FBRyxNQUFNLFlBQVksQ0FBQyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ3JELE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxNQUFNLENBQUMsT0FBTyxJQUFJLFNBQVMsSUFBSSxhQUFhLE9BQU8sT0FBTyxFQUFFLENBQUMsQ0FBQztJQUM3RSxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsTUFBTSxDQUFDLFNBQVMsSUFBSSxtQkFBbUIsRUFBRSxDQUFDLENBQUM7SUFDcEUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsTUFBTSxDQUFDLEVBQUUsSUFBSSxXQUFXLEVBQUUsQ0FBQyxDQUFDO0lBQzlELElBQUksTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ2hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNsQixPQUFPLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQztJQUN6QixDQUFDO0FBQ0wsQ0FBQztBQWZELG9CQWVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc2lnblRydXN0UGFja2V0IH0gZnJvbSBcIi4vaWRlbnRpdHlcIjtcblxuLy8gVGhlIG90aGVyIGhhbGYgb2YgdGhlIGRlbW9uc3RyYXRpb246IGFza3MgYSBzZXJ2ZXIgd2hldGhlciBpdCB0cnVzdHMgdGhpcyBtYWNoaW5lLiBUaGUgYW5zd2VyXG4vLyBkZXBlbmRzIG9uIHRoZSBhZGRyZXNzIHRoaXMgbWFjaGluZSByZWFjaGVzIHRoZSBzZXJ2ZXIgZnJvbSwgd2hpY2ggaXMgd2h5IHRoZSBjbGllbnQgY2Fubm90XG4vLyB3b3JrIGl0IG91dCBhbG9uZS5cbmNvbnN0IFRSVVNUX1BBVEggPSBcIi90cnVzdGVkXCI7XG5jb25zdCBVU0FHRSA9IGBVc2FnZTogeWFybiBtYWNoaW5lY2xpZW50IDx1cmw+IDxkb21haW4+XG5cbkFza3MgdGhhdCBzZXJ2ZXIgd2hldGhlciB0aGlzIG1hY2hpbmUgaXMgdHJ1c3RlZCwgcHJvdmluZyB3aGljaCBtYWNoaW5lIGl0IGlzIHdpdGggYSBzaWduZWQgcGFja2V0LlxuVGhlIHNlcnZlciBkZWNpZGVzIHVzaW5nIHRoZSBhZGRyZXNzIHdlIHJlYWNoIGl0IGZyb20sIHNvIHRoZSBhbnN3ZXIgaXMgYWJvdXQgaG93IHdlIGFyZSB0YWxraW5nIHRvXG5pdCwgbm90IG9ubHkgYWJvdXQgd2hvIHdlIGFyZS5cblxuVGhlIGRvbWFpbiBpcyByZXF1aXJlZDogdGhpcyBtYWNoaW5lIGhhcyBhIGRpZmZlcmVudCBpZGVudGl0eSB1bmRlciBlYWNoIG9uZSwgYW5kIHRoZSBhbnN3ZXIgaXNcbmFib3V0IHRoZSBpZGVudGl0eSB3ZSBzaWduIHdpdGguYDtcblxuZXhwb3J0IHR5cGUgVHJ1c3RBbnN3ZXIgPSB7XG4gICAgdHJ1c3RlZDogYm9vbGVhbjtcbiAgICBtYWNoaW5lSWQ/OiBzdHJpbmc7XG4gICAgaXA/OiBzdHJpbmc7XG4gICAgcmVhc29uPzogc3RyaW5nO1xufTtcblxuLyoqIEFza3Mgb25lIHNlcnZlciB3aGV0aGVyIGl0IHRydXN0cyB0aGlzIG1hY2hpbmUsIGFzIGl0IGlzIHVuZGVyIHRoaXMgZG9tYWluLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGFza0lmVHJ1c3RlZChjb25maWc6IHsgYmFzZVVSTDogc3RyaW5nOyBkb21haW46IHN0cmluZyB9KTogUHJvbWlzZTxUcnVzdEFuc3dlcj4ge1xuICAgIGxldCB7IGJhc2VVUkwsIGRvbWFpbiB9ID0gY29uZmlnO1xuICAgIGxldCBwYWNrZXQgPSBhd2FpdCBzaWduVHJ1c3RQYWNrZXQoZG9tYWluKTtcbiAgICBsZXQgdXJsID0gYCR7YmFzZVVSTC5yZXBsYWNlKC9cXC8rJC8sIFwiXCIpfSR7VFJVU1RfUEFUSH1gO1xuICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCwge1xuICAgICAgICBtZXRob2Q6IFwiUE9TVFwiLFxuICAgICAgICBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHBhY2tldCksXG4gICAgfSk7XG4gICAgcmV0dXJuIGF3YWl0IHJlc3BvbnNlLmpzb24oKSBhcyBUcnVzdEFuc3dlcjtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7XG4gICAgbGV0IFtiYXNlVVJMLCBkb21haW5dID0gcHJvY2Vzcy5hcmd2LnNsaWNlKDIpO1xuICAgIGlmICghYmFzZVVSTCB8fCAhZG9tYWluKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihVU0FHRSk7XG4gICAgfVxuICAgIGxldCBhbnN3ZXIgPSBhd2FpdCBhc2tJZlRydXN0ZWQoeyBiYXNlVVJMLCBkb21haW4gfSk7XG4gICAgY29uc29sZS5sb2coYCR7YW5zd2VyLnRydXN0ZWQgJiYgXCJUUlVTVEVEXCIgfHwgXCJOT1QgVFJVU1RFRFwifSBieSAke2Jhc2VVUkx9YCk7XG4gICAgY29uc29sZS5sb2coYCAgbWFjaGluZSAke2Fuc3dlci5tYWNoaW5lSWQgfHwgXCIobm90IGVzdGFibGlzaGVkKVwifWApO1xuICAgIGNvbnNvbGUubG9nKGAgIHNlZW4gY29taW5nIGZyb20gJHthbnN3ZXIuaXAgfHwgXCIodW5rbm93bilcIn1gKTtcbiAgICBpZiAoYW5zd2VyLnJlYXNvbikge1xuICAgICAgICBjb25zb2xlLmxvZyhgICAke2Fuc3dlci5yZWFzb259YCk7XG4gICAgfVxuICAgIGlmICghYW5zd2VyLnRydXN0ZWQpIHtcbiAgICAgICAgcHJvY2Vzcy5leGl0Q29kZSA9IDE7XG4gICAgfVxufVxuIl19
48
+ /* _JS_SOURCE_HASH = "51f52913bf4c7fbbeb7e5b3c70a7bc032fcc718b1eba1d8ed233fe7fe01617d2"; */
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
6
+ //exports.unrevokeInEffect = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const paths_1 = require("../authorizedKeys/daemon/paths");
10
+ const sources_1 = require("../authorizedKeys/sources");
11
+ const discord_1 = require("../notifications/discord");
12
+ // When this machine first saw each unrevoke, by unrevoke id.
13
+ //
14
+ // The same rule the ssh keys get, for the same reason: an unrevoke waits an hour before it counts,
15
+ // so a signing key that was itself stolen cannot instantly undo the revocation that shut it out.
16
+ // Counted from when we first saw it, never from a time the file states - whoever wrote the file
17
+ // chooses that, and would choose an hour ago.
18
+ //
19
+ // On disk, because a check runs in whatever process happens to be asking. Measuring from process
20
+ // start would restart the hour on every request and the unrevoke would never arrive. Kept beside
21
+ // the repo keys rather than in the daemon's state, because this has to work on a machine with no
22
+ // daemon, which is most of them.
23
+ const FIRST_SEEN_FILE = "machine-unrevokes.json";
24
+ function firstSeenPath() {
25
+ return path_1.default.join((0, sources_1.keysDir)(), FIRST_SEEN_FILE);
26
+ }
27
+ async function readFirstSeen() {
28
+ try {
29
+ return JSON.parse(await promises_1.default.readFile(firstSeenPath(), "utf8"));
30
+ }
31
+ catch (e) {
32
+ // Never written, or unreadable. Either way nothing has been seen before now.
33
+ return {};
34
+ }
35
+ }
36
+ /** Whether an unrevoke has waited out its hour, recording the first sighting if this is one.
37
+
38
+ Returns false the first time, which is the point: seeing an unrevoke is not the same as
39
+ honouring it. */
40
+ async function unrevokeInEffect(unrevokeId) {
41
+ let firstSeen = await readFirstSeen();
42
+ let seenAt = firstSeen[unrevokeId];
43
+ if (!seenAt) {
44
+ seenAt = Date.now();
45
+ firstSeen[unrevokeId] = seenAt;
46
+ await promises_1.default.mkdir(path_1.default.dirname(firstSeenPath()), { recursive: true, mode: 0o700 });
47
+ await promises_1.default.writeFile(firstSeenPath(), JSON.stringify(firstSeen, undefined, 4) + "\n");
48
+ }
49
+ if (Date.now() - seenAt < paths_1.UNREVOKE_DELAY) {
50
+ console.log(`Holding the unrevoke ${unrevokeId} until ${(0, discord_1.messageTimestamp)(new Date(seenAt + paths_1.UNREVOKE_DELAY))}`);
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ exports.unrevokeInEffect = unrevokeInEffect;
56
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJ1c3RTdGF0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRydXN0U3RhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBQzdCLGdEQUF3QjtBQUN4QiwwREFBZ0U7QUFDaEUsdURBQW9EO0FBQ3BELHNEQUE0RDtBQUU1RCw2REFBNkQ7QUFDN0QsRUFBRTtBQUNGLG1HQUFtRztBQUNuRyxpR0FBaUc7QUFDakcsZ0dBQWdHO0FBQ2hHLDhDQUE4QztBQUM5QyxFQUFFO0FBQ0YsaUdBQWlHO0FBQ2pHLGlHQUFpRztBQUNqRyxpR0FBaUc7QUFDakcsaUNBQWlDO0FBQ2pDLE1BQU0sZUFBZSxHQUFHLHdCQUF3QixDQUFDO0FBRWpELFNBQVMsYUFBYTtJQUNsQixPQUFPLGNBQUksQ0FBQyxJQUFJLENBQUMsSUFBQSxpQkFBTyxHQUFFLEVBQUUsZUFBZSxDQUFDLENBQUM7QUFDakQsQ0FBQztBQUVELEtBQUssVUFBVSxhQUFhO0lBQ3hCLElBQUksQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLGtCQUFFLENBQUMsUUFBUSxDQUFDLGFBQWEsRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDVCw2RUFBNkU7UUFDN0UsT0FBTyxFQUFFLENBQUM7SUFDZCxDQUFDO0FBQ0wsQ0FBQztBQUVEOzs7b0JBR29CO0FBQ2IsS0FBSyxVQUFVLGdCQUFnQixDQUFDLFVBQWtCO0lBQ3JELElBQUksU0FBUyxHQUFHLE1BQU0sYUFBYSxFQUFFLENBQUM7SUFDdEMsSUFBSSxNQUFNLEdBQUcsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ25DLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNWLE1BQU0sR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDcEIsU0FBUyxDQUFDLFVBQVUsQ0FBQyxHQUFHLE1BQU0sQ0FBQztRQUMvQixNQUFNLGtCQUFFLENBQUMsS0FBSyxDQUFDLGNBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxFQUFFLENBQUMsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7UUFDaEYsTUFBTSxrQkFBRSxDQUFDLFNBQVMsQ0FBQyxhQUFhLEVBQUUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDeEYsQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLE1BQU0sR0FBRyxzQkFBYyxFQUFFLENBQUM7UUFDdkMsT0FBTyxDQUFDLEdBQUcsQ0FDUCx3QkFBd0IsVUFBVSxVQUFVLElBQUEsMEJBQWdCLEVBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLHNCQUFjLENBQUMsQ0FBQyxFQUFFLENBQ3BHLENBQUM7UUFDRixPQUFPLEtBQUssQ0FBQztJQUNqQixDQUFDO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDaEIsQ0FBQztBQWhCRCw0Q0FnQkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZnMgZnJvbSBcImZzL3Byb21pc2VzXCI7XG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuaW1wb3J0IHsgVU5SRVZPS0VfREVMQVkgfSBmcm9tIFwiLi4vYXV0aG9yaXplZEtleXMvZGFlbW9uL3BhdGhzXCI7XG5pbXBvcnQgeyBrZXlzRGlyIH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL3NvdXJjZXNcIjtcbmltcG9ydCB7IG1lc3NhZ2VUaW1lc3RhbXAgfSBmcm9tIFwiLi4vbm90aWZpY2F0aW9ucy9kaXNjb3JkXCI7XG5cbi8vIFdoZW4gdGhpcyBtYWNoaW5lIGZpcnN0IHNhdyBlYWNoIHVucmV2b2tlLCBieSB1bnJldm9rZSBpZC5cbi8vXG4vLyBUaGUgc2FtZSBydWxlIHRoZSBzc2gga2V5cyBnZXQsIGZvciB0aGUgc2FtZSByZWFzb246IGFuIHVucmV2b2tlIHdhaXRzIGFuIGhvdXIgYmVmb3JlIGl0IGNvdW50cyxcbi8vIHNvIGEgc2lnbmluZyBrZXkgdGhhdCB3YXMgaXRzZWxmIHN0b2xlbiBjYW5ub3QgaW5zdGFudGx5IHVuZG8gdGhlIHJldm9jYXRpb24gdGhhdCBzaHV0IGl0IG91dC5cbi8vIENvdW50ZWQgZnJvbSB3aGVuIHdlIGZpcnN0IHNhdyBpdCwgbmV2ZXIgZnJvbSBhIHRpbWUgdGhlIGZpbGUgc3RhdGVzIC0gd2hvZXZlciB3cm90ZSB0aGUgZmlsZVxuLy8gY2hvb3NlcyB0aGF0LCBhbmQgd291bGQgY2hvb3NlIGFuIGhvdXIgYWdvLlxuLy9cbi8vIE9uIGRpc2ssIGJlY2F1c2UgYSBjaGVjayBydW5zIGluIHdoYXRldmVyIHByb2Nlc3MgaGFwcGVucyB0byBiZSBhc2tpbmcuIE1lYXN1cmluZyBmcm9tIHByb2Nlc3Ncbi8vIHN0YXJ0IHdvdWxkIHJlc3RhcnQgdGhlIGhvdXIgb24gZXZlcnkgcmVxdWVzdCBhbmQgdGhlIHVucmV2b2tlIHdvdWxkIG5ldmVyIGFycml2ZS4gS2VwdCBiZXNpZGVcbi8vIHRoZSByZXBvIGtleXMgcmF0aGVyIHRoYW4gaW4gdGhlIGRhZW1vbidzIHN0YXRlLCBiZWNhdXNlIHRoaXMgaGFzIHRvIHdvcmsgb24gYSBtYWNoaW5lIHdpdGggbm9cbi8vIGRhZW1vbiwgd2hpY2ggaXMgbW9zdCBvZiB0aGVtLlxuY29uc3QgRklSU1RfU0VFTl9GSUxFID0gXCJtYWNoaW5lLXVucmV2b2tlcy5qc29uXCI7XG5cbmZ1bmN0aW9uIGZpcnN0U2VlblBhdGgoKSB7XG4gICAgcmV0dXJuIHBhdGguam9pbihrZXlzRGlyKCksIEZJUlNUX1NFRU5fRklMRSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJlYWRGaXJzdFNlZW4oKTogUHJvbWlzZTx7IFt1bnJldm9rZUlkOiBzdHJpbmddOiBudW1iZXIgfT4ge1xuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBKU09OLnBhcnNlKGF3YWl0IGZzLnJlYWRGaWxlKGZpcnN0U2VlblBhdGgoKSwgXCJ1dGY4XCIpKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8vIE5ldmVyIHdyaXR0ZW4sIG9yIHVucmVhZGFibGUuIEVpdGhlciB3YXkgbm90aGluZyBoYXMgYmVlbiBzZWVuIGJlZm9yZSBub3cuXG4gICAgICAgIHJldHVybiB7fTtcbiAgICB9XG59XG5cbi8qKiBXaGV0aGVyIGFuIHVucmV2b2tlIGhhcyB3YWl0ZWQgb3V0IGl0cyBob3VyLCByZWNvcmRpbmcgdGhlIGZpcnN0IHNpZ2h0aW5nIGlmIHRoaXMgaXMgb25lLlxuXG4gICAgUmV0dXJucyBmYWxzZSB0aGUgZmlyc3QgdGltZSwgd2hpY2ggaXMgdGhlIHBvaW50OiBzZWVpbmcgYW4gdW5yZXZva2UgaXMgbm90IHRoZSBzYW1lIGFzXG4gICAgaG9ub3VyaW5nIGl0LiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHVucmV2b2tlSW5FZmZlY3QodW5yZXZva2VJZDogc3RyaW5nKSB7XG4gICAgbGV0IGZpcnN0U2VlbiA9IGF3YWl0IHJlYWRGaXJzdFNlZW4oKTtcbiAgICBsZXQgc2VlbkF0ID0gZmlyc3RTZWVuW3VucmV2b2tlSWRdO1xuICAgIGlmICghc2VlbkF0KSB7XG4gICAgICAgIHNlZW5BdCA9IERhdGUubm93KCk7XG4gICAgICAgIGZpcnN0U2Vlblt1bnJldm9rZUlkXSA9IHNlZW5BdDtcbiAgICAgICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKGZpcnN0U2VlblBhdGgoKSksIHsgcmVjdXJzaXZlOiB0cnVlLCBtb2RlOiAwbzcwMCB9KTtcbiAgICAgICAgYXdhaXQgZnMud3JpdGVGaWxlKGZpcnN0U2VlblBhdGgoKSwgSlNPTi5zdHJpbmdpZnkoZmlyc3RTZWVuLCB1bmRlZmluZWQsIDQpICsgXCJcXG5cIik7XG4gICAgfVxuICAgIGlmIChEYXRlLm5vdygpIC0gc2VlbkF0IDwgVU5SRVZPS0VfREVMQVkpIHtcbiAgICAgICAgY29uc29sZS5sb2coXG4gICAgICAgICAgICBgSG9sZGluZyB0aGUgdW5yZXZva2UgJHt1bnJldm9rZUlkfSB1bnRpbCAke21lc3NhZ2VUaW1lc3RhbXAobmV3IERhdGUoc2VlbkF0ICsgVU5SRVZPS0VfREVMQVkpKX1gXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG4iXX0=
57
+ /* _JS_SOURCE_HASH = "eaf8b0fd92f88a8682308f89a16f004ca1c28ff8bfcd90a56c734f63669c2aeb"; */
@@ -0,0 +1,199 @@
1
+ import crypto from "crypto";
2
+ import fs from "fs/promises";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { generateTestCA, getMachineId, identityStorageKey, IdentityStorageType, verifyMachineIdForPublicKey } from "../../misc/https/certs";
6
+ import { runOverSSH, writeRemoteFile } from "../helpers/remoteSSH";
7
+
8
+ // A machine's identity is the CA it generated for itself, kept in one file in the home directory
9
+ // of whoever runs it. The name carries the domain it belongs to, so a machine can hold one per
10
+ // domain, and the id itself is a hash of that CA's public key - which is why a machine cannot
11
+ // claim to be another one without holding its key.
12
+ const IDENTITY_PREFIX = "keystore_";
13
+ const IDENTITY_SUFFIX = `_${identityStorageKey}.json`;
14
+ // How stale a signed packet may be. Long enough for clock skew between two machines, short enough
15
+ // that a captured packet is not worth replaying.
16
+ export const PACKET_LIFETIME = 2 * 60 * 1000;
17
+
18
+ export type Identity = {
19
+ machineId: string;
20
+ domain: string;
21
+ fullDomain: string;
22
+ fileName: string;
23
+ };
24
+
25
+ function identityFileName(domain: string) {
26
+ return `${IDENTITY_PREFIX}${domain}${IDENTITY_SUFFIX}`;
27
+ }
28
+
29
+ /** The domain out of an identity file's name, or "" if it is not one. */
30
+ function domainOfFileName(fileName: string) {
31
+ if (!fileName.startsWith(IDENTITY_PREFIX) || !fileName.endsWith(IDENTITY_SUFFIX)) {
32
+ return "";
33
+ }
34
+ return fileName.slice(IDENTITY_PREFIX.length, -IDENTITY_SUFFIX.length);
35
+ }
36
+
37
+ function describeIdentity(config: { domain: string; stored: IdentityStorageType }) {
38
+ let { domain, stored } = config;
39
+ return {
40
+ machineId: getMachineId(stored.domain, domain),
41
+ domain,
42
+ fullDomain: stored.domain,
43
+ fileName: identityFileName(domain),
44
+ };
45
+ }
46
+
47
+ /** Every domain this machine holds an identity under. A machine has one identity per domain, and
48
+ they are different machine ids, so which one is meant is a real question rather than a detail. */
49
+ export async function localDomains() {
50
+ let domains: string[] = [];
51
+ for (let name of (await fs.readdir(os.homedir()).catch(() => [] as string[])).sort()) {
52
+ let domain = domainOfFileName(name);
53
+ if (domain) {
54
+ domains.push(domain);
55
+ }
56
+ }
57
+ return domains;
58
+ }
59
+
60
+ /** This machine's identity under one domain. Undefined when it has none under that one.
61
+
62
+ The domain is required everywhere it appears. A machine has a different identity, and so a
63
+ different machine id, under every domain it runs under, so "this machine" on its own does not
64
+ name anything: answering under the wrong one means answering as a machine we are not. */
65
+ export async function localIdentity(domain: string): Promise<Identity | undefined> {
66
+ if (!domain) {
67
+ throw new Error(`Expected a domain, was ${JSON.stringify(domain)}`);
68
+ }
69
+ let contents = await fs.readFile(path.join(os.homedir(), identityFileName(domain)), "utf8").catch(() => "");
70
+ if (!contents) {
71
+ return undefined;
72
+ }
73
+ return describeIdentity({ domain, stored: JSON.parse(contents) });
74
+ }
75
+
76
+ /** This machine's identity and its keys, for signing. */
77
+ export async function localIdentityKeys(domain: string) {
78
+ let identity = await localIdentity(domain);
79
+ if (!identity) {
80
+ throw new Error(
81
+ `Expected an identity for ${domain} at ${path.join(os.homedir(), identityFileName(domain))},`
82
+ + ` there is none.\n`
83
+ + `This machine has: ${(await localDomains()).join(", ") || "no identities at all"}`
84
+ );
85
+ }
86
+ let stored: IdentityStorageType = JSON.parse(
87
+ await fs.readFile(path.join(os.homedir(), identity.fileName), "utf8")
88
+ );
89
+ return { identity, stored };
90
+ }
91
+
92
+ /** The identity of another machine under one domain, over ssh. Undefined when it has none under
93
+ that domain, whatever else it may have under others. */
94
+ export async function remoteIdentity(host: string, domain: string): Promise<Identity | undefined> {
95
+ if (!domain) {
96
+ throw new Error(`Expected a domain, was ${JSON.stringify(domain)}`);
97
+ }
98
+ let home = (await runOverSSH({ host, script: `echo $HOME` })).stdout.trim();
99
+ if (!home) {
100
+ throw new Error(`Expected ${host} to report a home directory, it reported nothing`);
101
+ }
102
+ let contents = await runOverSSH({
103
+ host,
104
+ script: `cat ${home}/${identityFileName(domain)} 2>/dev/null || true`,
105
+ });
106
+ if (!contents.stdout.trim()) {
107
+ return undefined;
108
+ }
109
+ return describeIdentity({ domain, stored: JSON.parse(contents.stdout) });
110
+ }
111
+
112
+ /** Gives a machine an identity it does not have yet, by generating one here and writing it there.
113
+
114
+ The CA is self signed and never leaves that machine afterwards, so generating it here is only a
115
+ convenience: what matters is that the private key ends up in one place and the id is the hash
116
+ of its public half. */
117
+ export async function createRemoteIdentity(config: { host: string; domain: string }) {
118
+ let { host, domain } = config;
119
+ let generated = generateTestCA(domain);
120
+ let stored: IdentityStorageType = {
121
+ domain: generated.domain,
122
+ certB64: generated.cert.toString("base64"),
123
+ keyB64: generated.key.toString("base64"),
124
+ };
125
+ let home = (await runOverSSH({ host, script: `echo $HOME` })).stdout.trim();
126
+ await writeRemoteFile({
127
+ host,
128
+ filePath: `${home}/${identityFileName(domain)}`,
129
+ contents: JSON.stringify(stored),
130
+ fileMode: "600",
131
+ // The home directory is already there, this is only what it is created with if it is not.
132
+ directoryMode: "700",
133
+ });
134
+ return describeIdentity({ domain, stored });
135
+ }
136
+
137
+ /** The raw ed25519 public key out of a certificate, which is what the machine id is a hash of. */
138
+ function publicKeyBytes(certPEM: string) {
139
+ let spki = new crypto.X509Certificate(certPEM).publicKey.export({ type: "spki", format: "der" });
140
+ // An ed25519 SPKI is a fixed 12 byte header followed by the 32 byte key.
141
+ return spki.subarray(spki.length - 32);
142
+ }
143
+
144
+ export type TrustPacket = {
145
+ machineId: string;
146
+ certB64: string;
147
+ signedAt: number;
148
+ signatureB64: string;
149
+ };
150
+
151
+ /** Proves we hold the private key behind our machine id, right now. The signature covers the id
152
+ and the time, so the packet cannot be replayed later or reused for another id. */
153
+ export async function signTrustPacket(domain: string): Promise<TrustPacket> {
154
+ let { identity, stored } = await localIdentityKeys(domain);
155
+ let signedAt = Date.now();
156
+ let signature = crypto.sign(
157
+ null,
158
+ Buffer.from(`${identity.machineId} ${signedAt}`),
159
+ crypto.createPrivateKey(Buffer.from(stored.keyB64, "base64").toString())
160
+ );
161
+ return {
162
+ machineId: identity.machineId,
163
+ certB64: stored.certB64,
164
+ signedAt,
165
+ signatureB64: signature.toString("base64"),
166
+ };
167
+ }
168
+
169
+ /** Whether a packet really came from the machine it names. Says why when it did not, because
170
+ "this is not that machine" and "this arrived too late" are different problems. */
171
+ export function verifyTrustPacket(packet: TrustPacket) {
172
+ if (!packet || !packet.machineId || !packet.certB64 || !packet.signatureB64) {
173
+ return { valid: false, problem: "the packet is missing fields" };
174
+ }
175
+ if (Math.abs(Date.now() - packet.signedAt) > PACKET_LIFETIME) {
176
+ return { valid: false, problem: `it was signed ${Math.round((Date.now() - packet.signedAt) / 1000)}s ago` };
177
+ }
178
+ let certPEM = Buffer.from(packet.certB64, "base64").toString();
179
+ let publicKey: crypto.KeyObject;
180
+ try {
181
+ publicKey = new crypto.X509Certificate(certPEM).publicKey;
182
+ } catch (e) {
183
+ return { valid: false, problem: `its certificate could not be read, ${e}` };
184
+ }
185
+ // The id is a hash of the public key, so this is what stops a machine claiming another's id.
186
+ if (!verifyMachineIdForPublicKey({ machineId: packet.machineId, publicKey: publicKeyBytes(certPEM) })) {
187
+ return { valid: false, problem: "the machine id does not belong to that certificate" };
188
+ }
189
+ let signed = crypto.verify(
190
+ null,
191
+ Buffer.from(`${packet.machineId} ${packet.signedAt}`),
192
+ publicKey,
193
+ Buffer.from(packet.signatureB64, "base64")
194
+ );
195
+ if (!signed) {
196
+ return { valid: false, problem: "the signature does not check out" };
197
+ }
198
+ return { valid: true, problem: "" };
199
+ }