sliftutils 1.7.126 → 1.7.127

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.
@@ -1,8 +1,10 @@
1
1
  import fs from "fs/promises";
2
2
  import path from "path";
3
- import { keyFingerprint, keyRestriction, summarizeKey } from "../authorizedKeys";
3
+ import { keyFingerprint, keyRestriction, keyRestrictionList, NO_RESTRICTION, summarizeKey } from "../authorizedKeys";
4
4
  import { KEYS_HISTORY_PATH, ROOT_AUTHORIZED_KEYS } from "./paths";
5
5
  import { notify } from "./notify";
6
+ import { getState, saveState } from "./state";
7
+ import { takeChangeReasons } from "./changes";
6
8
 
7
9
  const KEY_FILE_HEADER = "# Managed by portsecure. Manual changes are reverted and reported.";
8
10
 
@@ -22,6 +24,35 @@ function keyIdentity(keyLine: string) {
22
24
  return keyFingerprint(keyLine) || keyLine;
23
25
  }
24
26
 
27
+ /** Which addresses a key gained or lost, rather than two long lists to compare by eye. Returns ""
28
+ when the addresses did not change at all. Losing the from= altogether is the loudest case there
29
+ is, so it is spelled out rather than shown as a list of removals. */
30
+ function describeAddressChange(config: { previousLine: string; currentLine: string }) {
31
+ let { previousLine, currentLine } = config;
32
+ let previous = keyRestrictionList(previousLine);
33
+ let current = keyRestrictionList(currentLine);
34
+ if (!previous && !current) {
35
+ return "";
36
+ }
37
+ if (previous && !current) {
38
+ return ` was ${previous.join(",")}\n now ${NO_RESTRICTION}`;
39
+ }
40
+ if (!previous && current) {
41
+ return ` was ${NO_RESTRICTION}\n now restricted to ${current.join(",")}`;
42
+ }
43
+ let removed = (previous || []).filter(address => !(current || []).includes(address));
44
+ let added = (current || []).filter(address => !(previous || []).includes(address));
45
+ if (!removed.length && !added.length) {
46
+ return "";
47
+ }
48
+ let lines = [
49
+ ...removed.map(address => ` no longer allowed from ${address}`),
50
+ ...added.map(address => ` now also allowed from ${address}`),
51
+ ];
52
+ lines.push(` still allowed from ${(current || []).filter(address => !added.includes(address)).join(",") || "nothing"}`);
53
+ return lines.join("\n");
54
+ }
55
+
25
56
  export function describeKeyDifference(config: { before: string[]; after: string[] }) {
26
57
  let { before, after } = config;
27
58
  let beforeByKey = new Map(before.map(key => [keyIdentity(key), key]));
@@ -43,17 +74,13 @@ export function describeKeyDifference(config: { before: string[]; after: string[
43
74
  if (!currentLine || currentLine === previousLine) {
44
75
  continue;
45
76
  }
46
- let previousFrom = keyRestriction(previousLine);
47
- let currentFrom = keyRestriction(currentLine);
48
- if (previousFrom !== currentFrom) {
49
- lines.push(
50
- `~ changed ${summarizeKey(currentLine)}\n from ${previousFrom}\n`
51
- + ` to ${currentFrom}`
52
- );
77
+ let addressChange = describeAddressChange({ previousLine, currentLine });
78
+ if (addressChange) {
79
+ lines.push(`~ changed ${summarizeKey(currentLine)}\n${addressChange}`);
53
80
  continue;
54
81
  }
55
82
  lines.push(
56
- `~ changed ${summarizeKey(currentLine)}\n from ${currentFrom}\n`
83
+ `~ changed ${summarizeKey(currentLine)}\n from ${keyRestriction(currentLine)}\n`
57
84
  + ` its options or comment changed`
58
85
  );
59
86
  }
@@ -106,30 +133,66 @@ export async function archiveAuthorizedKeys(config: { filePath: string; reason:
106
133
  return archivePath;
107
134
  }
108
135
 
109
- /** Puts the allowed keys back in place if anything else changed them. */
110
- export async function enforceRootKeys(config: { keys: string[]; reason: string }) {
111
- let { keys, reason } = config;
136
+ function sameKeys(one: string[], two: string[]) {
137
+ return one.length === two.length && one.every((key, index) => key === two[index]);
138
+ }
139
+
140
+ /** Puts the allowed keys in place, and says which of the two things happened.
141
+
142
+ Whether this was our own doing is decided by comparing what we want against what we last wrote,
143
+ not by guessing from whether a repo happened to change. A revocation changes what we want
144
+ without any repo changing, and reading that as somebody having edited the file was both wrong
145
+ and alarming. */
146
+ export async function enforceRootKeys(keys: string[]) {
147
+ // An empty set is written out like any other. If every key is revoked then nobody should be
148
+ // getting in, and the way back is to put a key in the repo, which is already being watched.
112
149
  if (!keys.length) {
113
- // No sources, or none of them readable. Writing an empty file would lock everyone out, so
114
- // whatever access is already in place stays exactly as it is.
115
- console.log(`No keys came from any source, leaving ${ROOT_AUTHORIZED_KEYS} as it is`);
116
- return;
150
+ console.log(`No keys are allowed, so ${ROOT_AUTHORIZED_KEYS} is being emptied`);
117
151
  }
152
+ let state = getState();
118
153
  let currentKeys = await readAuthorizedKeysFile(ROOT_AUTHORIZED_KEYS);
119
- let matches = currentKeys.length === keys.length && currentKeys.every((key, index) => key === keys[index]);
120
- if (matches) {
154
+ if (!state.appliedKeys.length) {
155
+ // Nothing recorded yet, on a first start or an upgrade. Whatever is in the file is taken as
156
+ // ours, so the first check reports what actually changes rather than reintroducing every
157
+ // key that was already there.
158
+ state.appliedKeys = currentKeys;
159
+ await saveState();
160
+ }
161
+
162
+ if (sameKeys(currentKeys, keys)) {
163
+ // Nothing came of whatever was decided this pass, so none of it is reported.
164
+ takeChangeReasons();
165
+ if (!sameKeys(state.appliedKeys, keys)) {
166
+ state.appliedKeys = keys;
167
+ await saveState();
168
+ }
121
169
  return;
122
170
  }
123
- let archivePath = await archiveAuthorizedKeys({ filePath: ROOT_AUTHORIZED_KEYS, reason });
171
+
172
+ let weChangedIt = !sameKeys(state.appliedKeys, keys);
173
+ let previouslyApplied = state.appliedKeys;
174
+ // Still archived, it is just not worth a line in the message. Whoever needs the old file knows
175
+ // where the history is.
176
+ await archiveAuthorizedKeys({
177
+ filePath: ROOT_AUTHORIZED_KEYS,
178
+ reason: weChangedIt && "update" || "reverted",
179
+ });
124
180
  await writeAuthorizedKeysFile({ filePath: ROOT_AUTHORIZED_KEYS, keys });
125
- let difference = describeKeyDifference({ before: currentKeys, after: keys });
126
- let archiveNote = archivePath && `\nThe previous file is kept at \`${archivePath}\`.` || "";
127
- if (reason === "repo") {
128
- await notify(`root's authorized_keys was updated from the keys repo.\n\`\`\`\n${difference}\n\`\`\`${archiveNote}`);
181
+ state.appliedKeys = keys;
182
+ await saveState();
183
+
184
+ let reasons = takeChangeReasons();
185
+ let why = reasons.length && `\n\n${reasons.join("\n\n")}` || "";
186
+ if (weChangedIt) {
187
+ await notify(
188
+ `applied authorized key changes:`
189
+ + `\n\`\`\`\n${describeKeyDifference({ before: previouslyApplied, after: keys })}\n\`\`\`${why}`
190
+ );
129
191
  return;
130
192
  }
131
193
  await notify(
132
- `root's authorized_keys was changed outside portsecure and has been reverted to the keys repo.`
133
- + `\n\`\`\`\n${difference}\n\`\`\`${archiveNote}`
194
+ `root's authorized_keys was edited by something other than portsecure. The edit below has`
195
+ + ` been undone, and the keys from the repos are back in place.`
196
+ + `\n\`\`\`\n${describeKeyDifference({ before: keys, after: currentKeys })}\n\`\`\`${why}`
134
197
  );
135
198
  }
@@ -0,0 +1,139 @@
1
+ import fs from "fs/promises";
2
+ import { spawnPromise } from "../../helpers/spawn";
3
+ import { AUTH_LOG_PATH } from "./paths";
4
+
5
+ // Taking a key out of authorized_keys only stops the next login. Whoever is already connected with
6
+ // it stays connected for as long as they like, which would make revoking a key that is being
7
+ // actively misused close to pointless.
8
+ //
9
+ // sshd names the key it accepted and the process handling that connection on the same line, so the
10
+ // sessions belonging to one key can be ended without touching anyone else's.
11
+ const ACCEPTED = /(?:sshd|sshd-session)\[(\d+)\]:\s+Accepted publickey for (\S+) from (\S+) port (\d+) ssh2: \S+ (SHA256:[A-Za-z0-9+/=]+)/;
12
+
13
+ export type KeySession = { processId: number; user: string; ip: string; port: string };
14
+
15
+ export function parseAcceptedSessions(contents: string, fingerprint: string) {
16
+ let sessions: KeySession[] = [];
17
+ for (let line of contents.split("\n")) {
18
+ let match = line.match(ACCEPTED);
19
+ if (!match || match[5] !== fingerprint) {
20
+ continue;
21
+ }
22
+ sessions.push({ processId: Number(match[1]), user: match[2], ip: match[3], port: match[4] });
23
+ }
24
+ return sessions;
25
+ }
26
+
27
+ /** Process ids are reused, so one is only killed when it is still an ssh session. Killing whatever
28
+ happens to hold that number now would be far worse than missing a session. */
29
+ async function isSSHSession(processId: number) {
30
+ try {
31
+ let name = await fs.readFile(`/proc/${processId}/comm`, "utf8");
32
+ return name.trim().startsWith("sshd");
33
+ } catch (e) {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /** The whole log, not only what is new, because the session being ended may have been established
39
+ long before the key was misused. */
40
+ async function readWholeAuthLog() {
41
+ try {
42
+ return await fs.readFile(AUTH_LOG_PATH, "utf8");
43
+ } catch (e) {
44
+ let result = await spawnPromise({
45
+ command: "journalctl",
46
+ args: ["-u", "ssh", "-u", "sshd", "--no-pager", "--since", "-30days"],
47
+ });
48
+ return result.status === 0 && result.stdout || "";
49
+ }
50
+ }
51
+
52
+ /** Ends every live session that authenticated with this key. Returns what was ended, so the
53
+ revocation can say so rather than leaving it to be discovered. */
54
+ export async function endSessionsUsingKey(fingerprint: string) {
55
+ let ended: KeySession[] = [];
56
+ for (let session of parseAcceptedSessions(await readWholeAuthLog(), fingerprint)) {
57
+ if (session.processId <= 1 || session.processId === process.pid) {
58
+ continue;
59
+ }
60
+ if (!await isSSHSession(session.processId)) {
61
+ continue;
62
+ }
63
+ try {
64
+ process.kill(session.processId, "SIGKILL");
65
+ ended.push(session);
66
+ console.log(`Ended ssh session ${session.processId} (${session.user} from ${session.ip}), it used ${fingerprint}`);
67
+ } catch (e) {
68
+ // Gone between looking and killing, which is the outcome we wanted anyway.
69
+ }
70
+ }
71
+ return ended;
72
+ }
73
+
74
+ /** Whether this process is one connection. The listener is not a connection - it is the process
75
+ that accepts them - so it is not a candidate here at all.
76
+
77
+ Every openssh since 9.8 runs each connection as its own sshd-session. On older ones connections
78
+ are named sshd too, and the listener is the one systemd started directly with -D. */
79
+ async function isConnectionProcess(processId: number) {
80
+ let name: string;
81
+ try {
82
+ name = (await fs.readFile(`/proc/${processId}/comm`, "utf8")).trim();
83
+ } catch (e) {
84
+ return false;
85
+ }
86
+ if (name === "sshd-session") {
87
+ return true;
88
+ }
89
+ if (name !== "sshd") {
90
+ return false;
91
+ }
92
+ let commandLine = await fs.readFile(`/proc/${processId}/cmdline`, "utf8").catch(() => "");
93
+ let parentId = Number((await fs.readFile(`/proc/${processId}/stat`, "utf8").catch(() => "")).split(") ")[1]?.split(" ")[1] || 0);
94
+ return !commandLine.includes("-D") && parentId !== 1;
95
+ }
96
+
97
+ /** Disconnects every ssh session on the machine.
98
+
99
+ Not only the ones using the key that was just taken away: a session that predates the key being
100
+ removed is exactly as dangerous, and working out which sessions are still entitled to be here
101
+ is guesswork. Anyone who still has access can reconnect in a second, and anyone who does not
102
+ should not be here. */
103
+ export async function endAllSSHSessions() {
104
+ let ended: number[] = [];
105
+ for (let entry of await fs.readdir("/proc")) {
106
+ let processId = Number(entry);
107
+ if (!processId || processId <= 1 || processId === process.pid) {
108
+ continue;
109
+ }
110
+ if (!await isConnectionProcess(processId)) {
111
+ continue;
112
+ }
113
+ try {
114
+ process.kill(processId, "SIGKILL");
115
+ ended.push(processId);
116
+ } catch (e) {
117
+ // Gone between looking and killing, which is the outcome we wanted anyway.
118
+ }
119
+ }
120
+ if (ended.length) {
121
+ console.log(`Disconnected ${ended.length} ssh session(s): ${ended.join(", ")}`);
122
+ }
123
+ return ended;
124
+ }
125
+
126
+ export function describeAllEnded(ended: number[]) {
127
+ if (!ended.length) {
128
+ return " Nothing was connected.";
129
+ }
130
+ return ` Every ssh session on this machine was disconnected, ${ended.length} of them.`;
131
+ }
132
+
133
+ export function describeEndedSessions(ended: KeySession[]) {
134
+ if (!ended.length) {
135
+ return " Nothing was connected with it.";
136
+ }
137
+ return ` ${ended.length} live session(s) using it were killed: `
138
+ + ended.map(session => `${session.user}@${session.ip}:${session.port}`).join(", ") + ".";
139
+ }
@@ -45,8 +45,16 @@ export type PendingRevocation = {
45
45
  export type DaemonState = {
46
46
  sources: { [repoURL: string]: SourceState };
47
47
  userKeyHashes: { [userName: string]: string };
48
+ // Held in memory only, never written to disk. Deleting a revocation from the revoke repo does
49
+ // not bring a key back, because this machine still remembers it - the key that writes
50
+ // revocations is on every server, so whoever stole one could otherwise erase the record that
51
+ // shut them out. Restarting the daemon does clear it, which is the way back from a revocation
52
+ // that should not have happened.
48
53
  revocations: { [fingerprint: string]: RevocationState };
49
54
  pendingRevocations: PendingRevocation[];
55
+ // The keys we last wrote out. What tells a change we made apart from one somebody else made:
56
+ // if this still matches what we want, and the file does not, the file was edited behind us.
57
+ appliedKeys: string[];
50
58
  // Where we had read up to in the auth log, so a restart does not re-report old attempts.
51
59
  authLogOffset: number;
52
60
  authLogSignature: string;
@@ -57,6 +65,7 @@ let state: DaemonState = {
57
65
  userKeyHashes: {},
58
66
  revocations: {},
59
67
  pendingRevocations: [],
68
+ appliedKeys: [],
60
69
  authLogOffset: 0,
61
70
  authLogSignature: "",
62
71
  };
@@ -95,7 +104,11 @@ export async function loadState() {
95
104
  return;
96
105
  }
97
106
  try {
98
- state = Object.assign(state, JSON.parse(contents));
107
+ let loaded = JSON.parse(contents);
108
+ // Revocations are held in memory only, so whatever an older version left on disk is not
109
+ // read back in. Starting the daemon is what forgets them.
110
+ delete loaded.revocations;
111
+ state = Object.assign(state, loaded);
99
112
  } catch (e) {
100
113
  // Corrupt state only costs us one duplicate notification, so it is not worth failing over.
101
114
  console.log(`Ignoring unreadable state file ${STATE_PATH}. ${e}`);
@@ -103,6 +116,7 @@ export async function loadState() {
103
116
  }
104
117
 
105
118
  export async function saveState() {
119
+ let { revocations, ...persisted } = state;
106
120
  await fs.mkdir(path.dirname(STATE_PATH), { recursive: true });
107
- await fs.writeFile(STATE_PATH, JSON.stringify(state, undefined, 4), { mode: 0o600 });
121
+ await fs.writeFile(STATE_PATH, JSON.stringify(persisted, undefined, 4), { mode: 0o600 });
108
122
  }
@@ -3,14 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
6
- //exports.readRepoKeys = exports.summarizeKey = exports.keyRestriction = exports.keyFingerprint = exports.normalizeKeys = void 0;
6
+ //exports.readRepoKeys = exports.findKeyProblems = exports.summarizeKey = exports.keyRestriction = exports.keyRestrictionList = exports.NO_RESTRICTION = exports.keyFingerprint = exports.normalizeKeys = void 0;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = __importDefault(require("path"));
10
- // PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js contains a plain JS port of normalizeKeys,
11
- // summarizeKey and readRepoKeys, so it can resolve the same keys with no dependencies. The two
12
- // must agree on which keys a repo produces - if you change one, make the matching change in the
13
- // other.
14
10
  function normalizeKeys(contents) {
15
11
  return contents.split("\n").map(line => line.trim()).filter(line => line && !line.startsWith("#"));
16
12
  }
@@ -27,14 +23,25 @@ function keyFingerprint(keyLine) {
27
23
  return "SHA256:" + crypto_1.default.createHash("sha256").update(Buffer.from(blob, "base64")).digest("base64").replace(/=+$/, "");
28
24
  }
29
25
  exports.keyFingerprint = keyFingerprint;
26
+ exports.NO_RESTRICTION = "ANY ADDRESS (no from= restriction)";
27
+ /** The addresses a key may be used from, one by one, or undefined when the key carries no from=
28
+ at all. Undefined and an empty list are very different things, so they stay distinguishable. */
29
+ function keyRestrictionList(keyLine) {
30
+ let match = keyLine.match(/from="([^"]*)"/);
31
+ if (!match) {
32
+ return undefined;
33
+ }
34
+ return match[1].split(",").map(entry => entry.trim()).filter(entry => entry);
35
+ }
36
+ exports.keyRestrictionList = keyRestrictionList;
30
37
  /** The addresses a key may be used from, which is the part of an authorized_keys line that
31
38
  decides how much a stolen key is worth. A key with no restriction says so loudly. */
32
39
  function keyRestriction(keyLine) {
33
- let match = keyLine.match(/from="([^"]*)"/);
34
- if (!match) {
35
- return "ANY ADDRESS (no from= restriction)";
40
+ let list = keyRestrictionList(keyLine);
41
+ if (!list) {
42
+ return exports.NO_RESTRICTION;
36
43
  }
37
- return match[1];
44
+ return list.join(",");
38
45
  }
39
46
  exports.keyRestriction = keyRestriction;
40
47
  /** Enough to recognise whose key this is without printing the whole blob. */
@@ -50,6 +57,37 @@ function summarizeKey(keyLine) {
50
57
  return `${type} ...${blob.slice(-12)}${comment && ` ${comment}` || ""}`;
51
58
  }
52
59
  exports.summarizeKey = summarizeKey;
60
+ /** What a set of keys has to satisfy before it is worth signing, as a list of complaints.
61
+
62
+ Every key needs a from=, because an unrestricted key can never be caught being used from the
63
+ wrong place, and being caught is the only thing that triggers a revocation.
64
+
65
+ No two keys may allow the same addresses, because that is one person holding two keys: revoking
66
+ one of them leaves the other working, so the revocation achieves nothing. */
67
+ function findKeyProblems(keys) {
68
+ let problems = [];
69
+ let byRestriction = new Map();
70
+ for (let key of keys) {
71
+ let restriction = keyRestrictionList(key);
72
+ if (!restriction) {
73
+ problems.push(`no from= restriction, so it can be used from anywhere:\n ${summarizeKey(key)}`);
74
+ continue;
75
+ }
76
+ // Sorted, so the same addresses written in a different order still count as the same.
77
+ let identity = [...restriction].sort().join(",");
78
+ byRestriction.set(identity, [...(byRestriction.get(identity) || []), key]);
79
+ }
80
+ for (let [restriction, sharing] of byRestriction) {
81
+ if (sharing.length < 2) {
82
+ continue;
83
+ }
84
+ problems.push(`${sharing.length} keys allow exactly the same addresses (${restriction}), which is one`
85
+ + ` person holding more than one key:\n`
86
+ + sharing.map(key => ` ${summarizeKey(key)}`).join("\n"));
87
+ }
88
+ return problems;
89
+ }
90
+ exports.findKeyProblems = findKeyProblems;
53
91
  /** Reads the authorized keys a repo checkout wants applied. Prefers a top level authorized_keys
54
92
  file and otherwise concatenates every .pub at the top level. */
55
93
  async function readRepoKeys(repoPath) {
@@ -69,5 +107,5 @@ async function readRepoKeys(repoPath) {
69
107
  return keys;
70
108
  }
71
109
  exports.readRepoKeys = readRepoKeys;
72
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXplZEtleXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJhdXRob3JpemVkS2V5cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSxvREFBNEI7QUFDNUIsMkRBQTZCO0FBQzdCLGdEQUF3QjtBQUV4Qiw2R0FBNkc7QUFDN0csK0ZBQStGO0FBQy9GLGdHQUFnRztBQUNoRyxTQUFTO0FBRVQsU0FBZ0IsYUFBYSxDQUFDLFFBQWdCO0lBQzFDLE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDdkcsQ0FBQztBQUZELHNDQUVDO0FBRUQ7OEVBQzhFO0FBQzlFLFNBQWdCLGNBQWMsQ0FBQyxPQUFlO0lBQzFDLElBQUksS0FBSyxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDeEMsSUFBSSxTQUFTLEdBQUcsS0FBSyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLG9CQUFvQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ3pFLElBQUksSUFBSSxHQUFHLFNBQVMsSUFBSSxDQUFDLElBQUksS0FBSyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDeEQsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ1IsT0FBTyxFQUFFLENBQUM7SUFDZCxDQUFDO0lBQ0QsT0FBTyxTQUFTLEdBQUcsZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFF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cHRvIGZyb20gXCJjcnlwdG9cIjtcbmltcG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbi8vIFBPUlRFRCBDT0RFOiBzZWN1cml0eS9hdXRob3JpemVkS2V5cy9kYWVtb24vcG9ydHNlY3VyZURhZW1vbi5qcyBjb250YWlucyBhIHBsYWluIEpTIHBvcnQgb2Ygbm9ybWFsaXplS2V5cyxcbi8vIHN1bW1hcml6ZUtleSBhbmQgcmVhZFJlcG9LZXlzLCBzbyBpdCBjYW4gcmVzb2x2ZSB0aGUgc2FtZSBrZXlzIHdpdGggbm8gZGVwZW5kZW5jaWVzLiBUaGUgdHdvXG4vLyBtdXN0IGFncmVlIG9uIHdoaWNoIGtleXMgYSByZXBvIHByb2R1Y2VzIC0gaWYgeW91IGNoYW5nZSBvbmUsIG1ha2UgdGhlIG1hdGNoaW5nIGNoYW5nZSBpbiB0aGVcbi8vIG90aGVyLlxuXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplS2V5cyhjb250ZW50czogc3RyaW5nKSB7XG4gICAgcmV0dXJuIGNvbnRlbnRzLnNwbGl0KFwiXFxuXCIpLm1hcChsaW5lID0+IGxpbmUudHJpbSgpKS5maWx0ZXIobGluZSA9PiBsaW5lICYmICFsaW5lLnN0YXJ0c1dpdGgoXCIjXCIpKTtcbn1cblxuLyoqIFRoZSBmaW5nZXJwcmludCBzc2ggaXRzZWxmIHJlcG9ydHMgZm9yIGEga2V5LCB3aGljaCBpcyB3aGF0IHRoZSBzc2hkIGxvZyBuYW1lcyBhbmQgdGhlcmVmb3JlXG4gICAgd2hhdCBhIHJldm9jYXRpb24gaXMga2V5ZWQgYnkuIFJldHVybnMgXCJcIiBmb3IgYSBsaW5lIHRoYXQgaG9sZHMgbm8ga2V5LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGtleUZpbmdlcnByaW50KGtleUxpbmU6IHN0cmluZykge1xuICAgIGxldCBwYXJ0cyA9IGtleUxpbmUudHJpbSgpLnNwbGl0KC9cXHMrLyk7XG4gICAgbGV0IHR5cGVJbmRleCA9IHBhcnRzLmZpbmRJbmRleChwYXJ0ID0+IC9eKHNzaC18ZWNkc2EtfHNrLSkvLnRlc3QocGFydCkpO1xuICAgIGxldCBibG9iID0gdHlwZUluZGV4ID49IDAgJiYgcGFydHNbdHlwZUluZGV4ICsgMV0gfHwgXCJcIjtcbiAgICBpZiAoIWJsb2IpIHtcbiAgICAgICAgcmV0dXJuIFwiXCI7XG4gICAgfVxuICAgIHJldHVybiBcIlNIQTI1NjpcIiArIGNyeXB0by5jcmVhdGVIYXNoKFwic2hhMjU2XCIpLnVwZGF0ZShCdWZmZXIuZnJvbShibG9iLCBcImJhc2U2NFwiKSkuZGlnZXN0KFwiYmFzZTY0XCIpLnJlcGxhY2UoLz0rJC8sIFwiXCIpO1xufVxuXG4vKiogVGhlIGFkZHJlc3NlcyBhIGtleSBtYXkgYmUgdXNlZCBmcm9tLCB3aGljaCBpcyB0aGUgcGFydCBvZiBhbiBhdXRob3JpemVkX2tleXMgbGluZSB0aGF0XG4gICAgZGVjaWRlcyBob3cgbXVjaCBhIHN0b2xlbiBrZXkgaXMgd29ydGguIEEga2V5IHdpdGggbm8gcmVzdHJpY3Rpb24gc2F5cyBzbyBsb3VkbHkuICovXG5leHBvcnQgZnVuY3Rpb24ga2V5UmVzdHJpY3Rpb24oa2V5TGluZTogc3RyaW5nKSB7XG4gICAgbGV0IG1hdGNoID0ga2V5TGluZS5tYXRjaCgvZnJvbT1cIihbXlwiXSopXCIvKTtcbiAgICBpZiAoIW1hdGNoKSB7XG4gICAgICAgIHJldHVybiBcIkFOWSBBRERSRVNTIChubyBmcm9tPSByZXN0cmljdGlvbilcIjtcbiAgICB9XG4gICAgcmV0dXJuIG1hdGNoWzFdO1xufVxuXG4vKiogRW5vdWdoIHRvIHJlY29nbmlzZSB3aG9zZSBrZXkgdGhpcyBpcyB3aXRob3V0IHByaW50aW5nIHRoZSB3aG9sZSBibG9iLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcml6ZUtleShrZXlMaW5lOiBzdHJpbmcpIHtcbiAgICBsZXQgcGFydHMgPSBrZXlMaW5lLnRyaW0oKS5zcGxpdCgvXFxzKy8pO1xuICAgIGxldCB0eXBlSW5kZXggPSBwYXJ0cy5maW5kSW5kZXgocGFydCA9PiAvXihzc2gtfGVjZHNhLXxzay0pLy50ZXN0KHBhcnQpKTtcbiAgICBpZiAodHlwZUluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4ga2V5TGluZS5zbGljZSgwLCA2MCk7XG4gICAgfVxuICAgIGxldCB0eXBlID0gcGFydHNbdHlwZUluZGV4XTtcbiAgICBsZXQgYmxvYiA9IHBhcnRzW3R5cGVJbmRleCArIDFdIHx8IFwiXCI7XG4gICAgbGV0IGNvbW1lbnQgPSBwYXJ0cy5zbGljZSh0eXBlSW5kZXggKyAyKS5qb2luKFwiIFwiKTtcbiAgICByZXR1cm4gYCR7dHlwZX0gLi4uJHtibG9iLnNsaWNlKC0xMil9JHtjb21tZW50ICYmIGAgJHtjb21tZW50fWAgfHwgXCJcIn1gO1xufVxuXG4vKiogUmVhZHMgdGhlIGF1dGhvcml6ZWQga2V5cyBhIHJlcG8gY2hlY2tvdXQgd2FudHMgYXBwbGllZC4gUHJlZmVycyBhIHRvcCBsZXZlbCBhdXRob3JpemVkX2tleXNcbiAgICBmaWxlIGFuZCBvdGhlcndpc2UgY29uY2F0ZW5hdGVzIGV2ZXJ5IC5wdWIgYXQgdGhlIHRvcCBsZXZlbC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkUmVwb0tleXMocmVwb1BhdGg6IHN0cmluZykge1xuICAgIGxldCBjb21iaW5lZFBhdGggPSBwYXRoLmpvaW4ocmVwb1BhdGgsIFwiYXV0aG9yaXplZF9rZXlzXCIpO1xuICAgIGxldCBlbnRyaWVzID0gYXdhaXQgZnMucmVhZGRpcihyZXBvUGF0aCk7XG4gICAgaWYgKGVudHJpZXMuaW5jbHVkZXMoXCJhdXRob3JpemVkX2tleXNcIikpIHtcbiAgICAgICAgcmV0dXJuIG5vcm1hbGl6ZUtleXMoYXdhaXQgZnMucmVhZEZpbGUoY29tYmluZWRQYXRoLCBcInV0ZjhcIikpO1xuICAgIH1cbiAgICBsZXQgcHViRmlsZXMgPSBlbnRyaWVzLmZpbHRlcihuYW1lID0+IG5hbWUuZW5kc1dpdGgoXCIucHViXCIpKS5zb3J0KCk7XG4gICAgaWYgKCFwdWJGaWxlcy5sZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhdXRob3JpemVkX2tleXMgb3IgYXQgbGVhc3Qgb25lIC5wdWIgZmlsZSBpbiAke3JlcG9QYXRofSwgZm91bmQgbmVpdGhlcmApO1xuICAgIH1cbiAgICBsZXQga2V5czogc3RyaW5nW10gPSBbXTtcbiAgICBmb3IgKGxldCBuYW1lIG9mIHB1YkZpbGVzKSB7XG4gICAgICAgIGtleXMucHVzaCguLi5ub3JtYWxpemVLZXlzKGF3YWl0IGZzLnJlYWRGaWxlKHBhdGguam9pbihyZXBvUGF0aCwgbmFtZSksIFwidXRmOFwiKSkpO1xuICAgIH1cbiAgICByZXR1cm4ga2V5cztcbn1cbiJdfQ==
73
- /* _JS_SOURCE_HASH = "acd843c08a7ede6419089a90a105d6fbe56220931b66deb6b231678bdebe43cf"; */
110
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXplZEtleXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJhdXRob3JpemVkS2V5cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSxvREFBNEI7QUFDNUIsMkRBQTZCO0FBQzdCLGdEQUF3QjtBQUV4QixTQUFnQixhQUFhLENBQUMsUUFBZ0I7SUFDMUMsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztBQUN2RyxDQUFDO0FBRkQsc0NBRUM7QUFFRDs4RUFDOEU7QUFDOUUsU0FBZ0IsY0FBYyxDQUFDLE9BQWU7SUFDMUMsSUFBSSxLQUFLLEdBQUcsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN4QyxJQUFJLFNBQVMsR0FBRyxLQUFLLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDekUsSUFBSSxJQUFJLEdBQUcsU0FBUyxJQUFJLENBQUMsSUFBSSxLQUFLLENBQUMsU0FBUyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN4RCxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDUixPQUFPLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFDRCxPQUFPLFNBQVMsR0FBRyxnQkFBTSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsT0FB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QUFDO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELFFBQVEsaUJBQWlCLENBQUMsQ0FBQztJQUN4RyxDQUFDO0lBQ0QsSUFBSSxJQUFJLEdBQWEsRUFBRSxDQUFDO0lBQ3hCLEtBQUssSUFBSSxJQUFJLElBQUksUUFBUSxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLGFBQWEsQ0FBQyxNQUFNLGtCQUFFLENBQUMsUUFBUSxDQUFDLGNBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0RixDQUFDO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDaEIsQ0FBQztBQWZELG9DQWVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNyeXB0byBmcm9tIFwiY3J5cHRvXCI7XG5pbXBvcnQgZnMgZnJvbSBcImZzL3Byb21pc2VzXCI7XG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplS2V5cyhjb250ZW50czogc3RyaW5nKSB7XG4gICAgcmV0dXJuIGNvbnRlbnRzLnNwbGl0KFwiXFxuXCIpLm1hcChsaW5lID0+IGxpbmUudHJpbSgpKS5maWx0ZXIobGluZSA9PiBsaW5lICYmICFsaW5lLnN0YXJ0c1dpdGgoXCIjXCIpKTtcbn1cblxuLyoqIFRoZSBmaW5nZXJwcmludCBzc2ggaXRzZWxmIHJlcG9ydHMgZm9yIGEga2V5LCB3aGljaCBpcyB3aGF0IHRoZSBzc2hkIGxvZyBuYW1lcyBhbmQgdGhlcmVmb3JlXG4gICAgd2hhdCBhIHJldm9jYXRpb24gaXMga2V5ZWQgYnkuIFJldHVybnMgXCJcIiBmb3IgYSBsaW5lIHRoYXQgaG9sZHMgbm8ga2V5LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGtleUZpbmdlcnByaW50KGtleUxpbmU6IHN0cmluZykge1xuICAgIGxldCBwYXJ0cyA9IGtleUxpbmUudHJpbSgpLnNwbGl0KC9cXHMrLyk7XG4gICAgbGV0IHR5cGVJbmRleCA9IHBhcnRzLmZpbmRJbmRleChwYXJ0ID0+IC9eKHNzaC18ZWNkc2EtfHNrLSkvLnRlc3QocGFydCkpO1xuICAgIGxldCBibG9iID0gdHlwZUluZGV4ID49IDAgJiYgcGFydHNbdHlwZUluZGV4ICsgMV0gfHwgXCJcIjtcbiAgICBpZiAoIWJsb2IpIHtcbiAgICAgICAgcmV0dXJuIFwiXCI7XG4gICAgfVxuICAgIHJldHVybiBcIlNIQTI1NjpcIiArIGNyeXB0by5jcmVhdGVIYXNoKFwic2hhMjU2XCIpLnVwZGF0ZShCdWZmZXIuZnJvbShibG9iLCBcImJhc2U2NFwiKSkuZGlnZXN0KFwiYmFzZTY0XCIpLnJlcGxhY2UoLz0rJC8sIFwiXCIpO1xufVxuXG5leHBvcnQgY29uc3QgTk9fUkVTVFJJQ1RJT04gPSBcIkFOWSBBRERSRVNTIChubyBmcm9tPSByZXN0cmljdGlvbilcIjtcblxuLyoqIFRoZSBhZGRyZXNzZXMgYSBrZXkgbWF5IGJlIHVzZWQgZnJvbSwgb25lIGJ5IG9uZSwgb3IgdW5kZWZpbmVkIHdoZW4gdGhlIGtleSBjYXJyaWVzIG5vIGZyb209XG4gICAgYXQgYWxsLiBVbmRlZmluZWQgYW5kIGFuIGVtcHR5IGxpc3QgYXJlIHZlcnkgZGlmZmVyZW50IHRoaW5ncywgc28gdGhleSBzdGF5IGRpc3Rpbmd1aXNoYWJsZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBrZXlSZXN0cmljdGlvbkxpc3Qoa2V5TGluZTogc3RyaW5nKSB7XG4gICAgbGV0IG1hdGNoID0ga2V5TGluZS5tYXRjaCgvZnJvbT1cIihbXlwiXSopXCIvKTtcbiAgICBpZiAoIW1hdGNoKSB7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuICAgIHJldHVybiBtYXRjaFsxXS5zcGxpdChcIixcIikubWFwKGVudHJ5ID0+IGVudHJ5LnRyaW0oKSkuZmlsdGVyKGVudHJ5ID0+IGVudHJ5KTtcbn1cblxuLyoqIFRoZSBhZGRyZXNzZXMgYSBrZXkgbWF5IGJlIHVzZWQgZnJvbSwgd2hpY2ggaXMgdGhlIHBhcnQgb2YgYW4gYXV0aG9yaXplZF9rZXlzIGxpbmUgdGhhdFxuICAgIGRlY2lkZXMgaG93IG11Y2ggYSBzdG9sZW4ga2V5IGlzIHdvcnRoLiBBIGtleSB3aXRoIG5vIHJlc3RyaWN0aW9uIHNheXMgc28gbG91ZGx5LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGtleVJlc3RyaWN0aW9uKGtleUxpbmU6IHN0cmluZykge1xuICAgIGxldCBsaXN0ID0ga2V5UmVzdHJpY3Rpb25MaXN0KGtleUxpbmUpO1xuICAgIGlmICghbGlzdCkge1xuICAgICAgICByZXR1cm4gTk9fUkVTVFJJQ1RJT047XG4gICAgfVxuICAgIHJldHVybiBsaXN0LmpvaW4oXCIsXCIpO1xufVxuXG4vKiogRW5vdWdoIHRvIHJlY29nbmlzZSB3aG9zZSBrZXkgdGhpcyBpcyB3aXRob3V0IHByaW50aW5nIHRoZSB3aG9sZSBibG9iLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN1bW1hcml6ZUtleShrZXlMaW5lOiBzdHJpbmcpIHtcbiAgICBsZXQgcGFydHMgPSBrZXlMaW5lLnRyaW0oKS5zcGxpdCgvXFxzKy8pO1xuICAgIGxldCB0eXBlSW5kZXggPSBwYXJ0cy5maW5kSW5kZXgocGFydCA9PiAvXihzc2gtfGVjZHNhLXxzay0pLy50ZXN0KHBhcnQpKTtcbiAgICBpZiAodHlwZUluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4ga2V5TGluZS5zbGljZSgwLCA2MCk7XG4gICAgfVxuICAgIGxldCB0eXBlID0gcGFydHNbdHlwZUluZGV4XTtcbiAgICBsZXQgYmxvYiA9IHBhcnRzW3R5cGVJbmRleCArIDFdIHx8IFwiXCI7XG4gICAgbGV0IGNvbW1lbnQgPSBwYXJ0cy5zbGljZSh0eXBlSW5kZXggKyAyKS5qb2luKFwiIFwiKTtcbiAgICByZXR1cm4gYCR7dHlwZX0gLi4uJHtibG9iLnNsaWNlKC0xMil9JHtjb21tZW50ICYmIGAgJHtjb21tZW50fWAgfHwgXCJcIn1gO1xufVxuXG4vKiogV2hhdCBhIHNldCBvZiBrZXlzIGhhcyB0byBzYXRpc2Z5IGJlZm9yZSBpdCBpcyB3b3J0aCBzaWduaW5nLCBhcyBhIGxpc3Qgb2YgY29tcGxhaW50cy5cblxuICAgIEV2ZXJ5IGtleSBuZWVkcyBhIGZyb209LCBiZWNhdXNlIGFuIHVucmVzdHJpY3RlZCBrZXkgY2FuIG5ldmVyIGJlIGNhdWdodCBiZWluZyB1c2VkIGZyb20gdGhlXG4gICAgd3JvbmcgcGxhY2UsIGFuZCBiZWluZyBjYXVnaHQgaXMgdGhlIG9ubHkgdGhpbmcgdGhhdCB0cmlnZ2VycyBhIHJldm9jYXRpb24uXG5cbiAgICBObyB0d28ga2V5cyBtYXkgYWxsb3cgdGhlIHNhbWUgYWRkcmVzc2VzLCBiZWNhdXNlIHRoYXQgaXMgb25lIHBlcnNvbiBob2xkaW5nIHR3byBrZXlzOiByZXZva2luZ1xuICAgIG9uZSBvZiB0aGVtIGxlYXZlcyB0aGUgb3RoZXIgd29ya2luZywgc28gdGhlIHJldm9jYXRpb24gYWNoaWV2ZXMgbm90aGluZy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmaW5kS2V5UHJvYmxlbXMoa2V5czogc3RyaW5nW10pIHtcbiAgICBsZXQgcHJvYmxlbXM6IHN0cmluZ1tdID0gW107XG4gICAgbGV0IGJ5UmVzdHJpY3Rpb24gPSBuZXcgTWFwPHN0cmluZywgc3RyaW5nW10+KCk7XG4gICAgZm9yIChsZXQga2V5IG9mIGtleXMpIHtcbiAgICAgICAgbGV0IHJlc3RyaWN0aW9uID0ga2V5UmVzdHJpY3Rpb25MaXN0KGtleSk7XG4gICAgICAgIGlmICghcmVzdHJpY3Rpb24pIHtcbiAgICAgICAgICAgIHByb2JsZW1zLnB1c2goYG5vIGZyb209IHJlc3RyaWN0aW9uLCBzbyBpdCBjYW4gYmUgdXNlZCBmcm9tIGFueXdoZXJlOlxcbiAgICAgICR7c3VtbWFyaXplS2V5KGtleSl9YCk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICAvLyBTb3J0ZWQsIHNvIHRoZSBzYW1lIGFkZHJlc3NlcyB3cml0dGVuIGluIGEgZGlmZmVyZW50IG9yZGVyIHN0aWxsIGNvdW50IGFzIHRoZSBzYW1lLlxuICAgICAgICBsZXQgaWRlbnRpdHkgPSBbLi4ucmVzdHJpY3Rpb25dLnNvcnQoKS5qb2luKFwiLFwiKTtcbiAgICAgICAgYnlSZXN0cmljdGlvbi5zZXQoaWRlbnRpdHksIFsuLi4oYnlSZXN0cmljdGlvbi5nZXQoaWRlbnRpdHkpIHx8IFtdKSwga2V5XSk7XG4gICAgfVxuICAgIGZvciAobGV0IFtyZXN0cmljdGlvbiwgc2hhcmluZ10gb2YgYnlSZXN0cmljdGlvbikge1xuICAgICAgICBpZiAoc2hhcmluZy5sZW5ndGggPCAyKSB7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBwcm9ibGVtcy5wdXNoKFxuICAgICAgICAgICAgYCR7c2hhcmluZy5sZW5ndGh9IGtleXMgYWxsb3cgZXhhY3RseSB0aGUgc2FtZSBhZGRyZXNzZXMgKCR7cmVzdHJpY3Rpb259KSwgd2hpY2ggaXMgb25lYFxuICAgICAgICAgICAgKyBgIHBlcnNvbiBob2xkaW5nIG1vcmUgdGhhbiBvbmUga2V5OlxcbmBcbiAgICAgICAgICAgICsgc2hhcmluZy5tYXAoa2V5ID0+IGAgICAgICAke3N1bW1hcml6ZUtleShrZXkpfWApLmpvaW4oXCJcXG5cIilcbiAgICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHByb2JsZW1zO1xufVxuXG4vKiogUmVhZHMgdGhlIGF1dGhvcml6ZWQga2V5cyBhIHJlcG8gY2hlY2tvdXQgd2FudHMgYXBwbGllZC4gUHJlZmVycyBhIHRvcCBsZXZlbCBhdXRob3JpemVkX2tleXNcbiAgICBmaWxlIGFuZCBvdGhlcndpc2UgY29uY2F0ZW5hdGVzIGV2ZXJ5IC5wdWIgYXQgdGhlIHRvcCBsZXZlbC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkUmVwb0tleXMocmVwb1BhdGg6IHN0cmluZykge1xuICAgIGxldCBjb21iaW5lZFBhdGggPSBwYXRoLmpvaW4ocmVwb1BhdGgsIFwiYXV0aG9yaXplZF9rZXlzXCIpO1xuICAgIGxldCBlbnRyaWVzID0gYXdhaXQgZnMucmVhZGRpcihyZXBvUGF0aCk7XG4gICAgaWYgKGVudHJpZXMuaW5jbHVkZXMoXCJhdXRob3JpemVkX2tleXNcIikpIHtcbiAgICAgICAgcmV0dXJuIG5vcm1hbGl6ZUtleXMoYXdhaXQgZnMucmVhZEZpbGUoY29tYmluZWRQYXRoLCBcInV0ZjhcIikpO1xuICAgIH1cbiAgICBsZXQgcHViRmlsZXMgPSBlbnRyaWVzLmZpbHRlcihuYW1lID0+IG5hbWUuZW5kc1dpdGgoXCIucHViXCIpKS5zb3J0KCk7XG4gICAgaWYgKCFwdWJGaWxlcy5sZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhdXRob3JpemVkX2tleXMgb3IgYXQgbGVhc3Qgb25lIC5wdWIgZmlsZSBpbiAke3JlcG9QYXRofSwgZm91bmQgbmVpdGhlcmApO1xuICAgIH1cbiAgICBsZXQga2V5czogc3RyaW5nW10gPSBbXTtcbiAgICBmb3IgKGxldCBuYW1lIG9mIHB1YkZpbGVzKSB7XG4gICAgICAgIGtleXMucHVzaCguLi5ub3JtYWxpemVLZXlzKGF3YWl0IGZzLnJlYWRGaWxlKHBhdGguam9pbihyZXBvUGF0aCwgbmFtZSksIFwidXRmOFwiKSkpO1xuICAgIH1cbiAgICByZXR1cm4ga2V5cztcbn1cbiJdfQ==
111
+ /* _JS_SOURCE_HASH = "a5147a2eb954468a9ddfcac36189a744f5b17f944d7e837ce0a525c780093a26"; */