sliftutils 1.7.125 → 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.
- package/bin/derivekey.js +10 -0
- package/bin/portsecuredaemon.js +13 -0
- package/bin/testnotify.js +9 -0
- package/bin/unrevoke.js +9 -0
- package/package.json +10 -3
- package/security/README.md +66 -2
- package/security/authorizedKeys/authorizedKeys.ts +68 -5
- package/security/authorizedKeys/daemon/authLog.ts +186 -0
- package/security/authorizedKeys/daemon/changes.ts +26 -0
- package/security/authorizedKeys/daemon/daemon.ts +335 -0
- package/security/authorizedKeys/daemon/git.ts +119 -0
- package/security/authorizedKeys/daemon/notify.ts +25 -0
- package/security/authorizedKeys/daemon/paths.ts +26 -0
- package/security/authorizedKeys/daemon/portsecure.service +3 -2
- package/security/authorizedKeys/daemon/revocation.ts +329 -0
- package/security/authorizedKeys/daemon/rootKeys.ts +198 -0
- package/security/authorizedKeys/daemon/sessions.ts +139 -0
- package/security/authorizedKeys/daemon/sshdConfig.ts +85 -0
- package/security/authorizedKeys/daemon/state.ts +122 -0
- package/security/authorizedKeys/daemon/trust.ts +291 -0
- package/security/authorizedKeys/daemon/userKeys.ts +76 -0
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +111 -0
- package/security/authorizedKeys/dist/revokeSource.ts.cache +44 -0
- package/security/authorizedKeys/dist/secureSSH.ts.cache +539 -0
- package/security/authorizedKeys/dist/sources.ts.cache +24 -0
- package/security/authorizedKeys/dist/unrevoke.ts.cache +167 -0
- package/security/authorizedKeys/revokeSource.ts +40 -0
- package/security/authorizedKeys/secureSSH.ts +240 -38
- package/security/authorizedKeys/unrevoke.ts +175 -0
- package/security/helpers/dist/paths.ts.cache +28 -0
- package/security/helpers/dist/remoteSSH.ts.cache +90 -0
- package/security/helpers/dist/spawn.ts.cache +34 -0
- package/security/helpers/remoteSSH.ts +18 -6
- package/security/helpers/spawn.ts +5 -1
- package/security/keys/deriveKey.ts +72 -0
- package/security/keys/dist/deriveKey.ts.cache +72 -0
- package/security/keys/dist/sshKeyFile.ts.cache +153 -0
- package/security/keys/sshKeyFile.ts +156 -0
- package/security/notifications/discord.ts +18 -1
- package/security/notifications/dist/discord.ts.cache +197 -0
- package/security/notifications/setupNotify.ts +17 -8
- package/security/notifications/testNotify.ts +48 -0
- package/security/signedFiles/dist/manifest.ts.cache +85 -0
- package/security/signedFiles/dist/signFiles.ts.cache +181 -0
- package/security/signedFiles/manifest.ts +38 -8
- package/security/signedFiles/signFiles.ts +123 -49
- package/security/authorizedKeys/daemon/portsecureDaemon.js +0 -1032
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
|
+
//exports.remoteCommandExists = exports.writeRemoteFile = exports.readRemoteFile = exports.runOverSSH = exports.SUDO_PREAMBLE = void 0;
|
|
4
|
+
const spawn_1 = require("./spawn");
|
|
5
|
+
const SSH_CONNECT_TIMEOUT = 10;
|
|
6
|
+
const EXISTS_MARKER = "__PORTSECURE_EXISTS__";
|
|
7
|
+
const MISSING_MARKER = "__PORTSECURE_MISSING__";
|
|
8
|
+
const MAX_ERROR_BODY_LENGTH = 500;
|
|
9
|
+
// /etc is root owned, so fall back to sudo whenever the SSH user is not root.
|
|
10
|
+
exports.SUDO_PREAMBLE = `SUDO=""; if [ "$(id -u)" -ne 0 ]; then SUDO="sudo -n"; fi`;
|
|
11
|
+
/** The host string is handed to ssh untouched. Users, keys and ports belong in the caller's ssh
|
|
12
|
+
config, so BatchMode makes a missing setup fail immediately instead of prompting. */
|
|
13
|
+
async function runOverSSH(config) {
|
|
14
|
+
let { host, script, input, allowFailure } = config;
|
|
15
|
+
let result = await (0, spawn_1.spawnPromise)({
|
|
16
|
+
command: "ssh",
|
|
17
|
+
args: [
|
|
18
|
+
"-o", "BatchMode=yes",
|
|
19
|
+
"-o", `ConnectTimeout=${SSH_CONNECT_TIMEOUT}`,
|
|
20
|
+
host,
|
|
21
|
+
script,
|
|
22
|
+
],
|
|
23
|
+
input,
|
|
24
|
+
inheritStderr: !allowFailure,
|
|
25
|
+
});
|
|
26
|
+
if (result.error) {
|
|
27
|
+
throw new Error(`Expected ssh to run against ${host}, failed with ${result.error.message}`);
|
|
28
|
+
}
|
|
29
|
+
if (result.status !== 0 && !allowFailure) {
|
|
30
|
+
throw new Error(`Expected ssh to ${host} to exit 0, was ${result.status}.`
|
|
31
|
+
+ ` Non-interactive ssh access to ${host} has to work on its own - fix it in your ssh config.\n`
|
|
32
|
+
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
33
|
+
}
|
|
34
|
+
return { stdout: result.stdout || "", stderr: result.stderr || "", status: result.status };
|
|
35
|
+
}
|
|
36
|
+
exports.runOverSSH = runOverSSH;
|
|
37
|
+
/** Returns undefined when the file does not exist, so a missing file reads differently from an
|
|
38
|
+
empty one. */
|
|
39
|
+
async function readRemoteFile(config) {
|
|
40
|
+
let { host, filePath } = config;
|
|
41
|
+
let output = (await runOverSSH({
|
|
42
|
+
host,
|
|
43
|
+
script: `${exports.SUDO_PREAMBLE}
|
|
44
|
+
if $SUDO test -f "${filePath}"; then
|
|
45
|
+
echo "${EXISTS_MARKER}"
|
|
46
|
+
$SUDO cat "${filePath}"
|
|
47
|
+
else
|
|
48
|
+
echo "${MISSING_MARKER}"
|
|
49
|
+
fi`,
|
|
50
|
+
})).stdout;
|
|
51
|
+
let newlineIndex = output.indexOf("\n");
|
|
52
|
+
let marker = output.slice(0, newlineIndex).trim();
|
|
53
|
+
if (marker === MISSING_MARKER) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (marker !== EXISTS_MARKER) {
|
|
57
|
+
throw new Error(`Expected ${EXISTS_MARKER} or ${MISSING_MARKER} from ${host}, was ${output.slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
58
|
+
}
|
|
59
|
+
return output.slice(newlineIndex + 1);
|
|
60
|
+
}
|
|
61
|
+
exports.readRemoteFile = readRemoteFile;
|
|
62
|
+
async function writeRemoteFile(config) {
|
|
63
|
+
let { host, filePath, contents, fileMode, directoryMode } = config;
|
|
64
|
+
let directory = filePath.slice(0, filePath.lastIndexOf("/"));
|
|
65
|
+
await runOverSSH({
|
|
66
|
+
host,
|
|
67
|
+
// Deploying from Windows must not carry CRLF onto the target, where it breaks systemd
|
|
68
|
+
// units and makes private keys unreadable to ssh.
|
|
69
|
+
input: contents.replace(/\r\n/g, "\n"),
|
|
70
|
+
script: `${exports.SUDO_PREAMBLE}
|
|
71
|
+
set -e
|
|
72
|
+
$SUDO mkdir -p "${directory}"
|
|
73
|
+
$SUDO chmod ${directoryMode} "${directory}"
|
|
74
|
+
$SUDO tee "${filePath}" > /dev/null
|
|
75
|
+
$SUDO chmod ${fileMode} "${filePath}"`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
exports.writeRemoteFile = writeRemoteFile;
|
|
79
|
+
async function remoteCommandExists(config) {
|
|
80
|
+
let { host, command } = config;
|
|
81
|
+
let result = await runOverSSH({
|
|
82
|
+
host,
|
|
83
|
+
script: `command -v "${command}" > /dev/null 2>&1 && echo yes || echo no`,
|
|
84
|
+
allowFailure: true,
|
|
85
|
+
});
|
|
86
|
+
return result.stdout.trim() === "yes";
|
|
87
|
+
}
|
|
88
|
+
exports.remoteCommandExists = remoteCommandExists;
|
|
89
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtb3RlU1NILmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVtb3RlU1NILnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUF1QztBQUV2QyxNQUFNLG1CQUFtQixHQUFHLEVBQUUsQ0FBQztBQUMvQixNQUFNLGFBQWEsR0FBRyx1QkFBdUIsQ0FBQztBQUM5QyxNQUFNLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUNoRCxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUVsQyw4RUFBOEU7QUFDakUsUUFBQSxhQUFhLEdBQUcsMkRBQTJELENBQUM7QUFFekY7d0ZBQ3dGO0FBQ2pGLEtBQUssVUFBVSxVQUFVLENBQUMsTUFBZ0Y7SUFDN0csSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLFlBQVksRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUNuRCxJQUFJLE1BQU0sR0FBRyxNQUFNLElBQUEsb0JBQVksRUFBQztRQUM1QixPQUFPLEVBQUUsS0FBSztRQUNkLElBQUksRUFBRTtZQUNGLElBQUksRUFBRSxlQUFlO1lBQ3JCLElBQUksRUFBRSxrQkFBa0IsbUJBQW1CLEVBQUU7WUFDN0MsSUFBSTtZQUNKLE1BQU07U0FDVDtRQUNELEtBQUs7UUFDTCxhQUFhLEVBQUUsQ0FBQyxZQUFZO0tBQy9CLENBQUMsQ0FBQztJQUNILElBQUksTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxJQUFJLEtBQUssQ0FBQywrQkFBK0IsSUFBSSxpQkFBaUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO0lBQ2hHLENBQUM7SUFDRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDdkMsTUFBTSxJQUFJLEtBQUssQ0FDWCxtQkFBbUIsSUFBSSxtQkFBbUIsTUFBTSxDQUFDLE1BQU0sR0FBRztjQUN4RCxrQ0FBa0MsSUFBSSx3REFBd0Q7Y0FDOUYsR0FBRyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUscUJBQXFCLENBQUMsRUFBRSxDQUNoRixDQUFDO0lBQ04sQ0FBQztJQUNELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU0sSUFBSSxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNLElBQUksRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7QUFDL0YsQ0FBQztBQXhCRCxnQ0F3QkM7QUFFRDtpQkFDaUI7QUFDVixLQUFLLFVBQVUsY0FBYyxDQUFDLE1BQTBDO0lBQzNFLElBQUksRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxDQUFDO0lBQ2hDLElBQUksTUFBTSxHQUFHLENBQUMsTUFBTSxVQUFVLENBQUM7UUFDM0IsSUFBSTtRQUNKLE1BQU0sRUFBRSxHQUFHLHFCQUFhO29CQUNaLFFBQVE7WUFDaEIsYUFBYTtpQkFDUixRQUFROztZQUViLGNBQWM7R0FDdkI7S0FDRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDWCxJQUFJLFlBQVksR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hDLElBQUksTUFBTSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFlBQVksQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2xELElBQUksTUFBTSxLQUFLLGNBQWMsRUFBRSxDQUFDO1FBQzVCLE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFDRCxJQUFJLE1BQU0sS0FBSyxhQUFhLEVBQUUsQ0FBQztRQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLFlBQVksYUFBYSxPQUFPLGNBQWMsU0FBUyxJQUFJLFNBQVMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDbEksQ0FBQztJQUNELE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDMUMsQ0FBQztBQXJCRCx3Q0FxQkM7QUFFTSxLQUFLLFVBQVUsZUFBZSxDQUFDLE1BTXJDO0lBQ0csSUFBSSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsR0FBRyxNQUFNLENBQUM7SUFDbkUsSUFBSSxTQUFTLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsUUFBUSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQzdELE1BQU0sVUFBVSxDQUFDO1FBQ2IsSUFBSTtRQUNKLHNGQUFzRjtRQUN0RixrREFBa0Q7UUFDbEQsS0FBSyxFQUFFLFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQztRQUN0QyxNQUFNLEVBQUUsR0FBRyxxQkFBYTs7a0JBRWQsU0FBUztjQUNiLGFBQWEsS0FBSyxTQUFTO2FBQzVCLFFBQVE7Y0FDUCxRQUFRLEtBQUssUUFBUSxHQUFHO0tBQ2pDLENBQUMsQ0FBQztBQUNQLENBQUM7QUFyQkQsMENBcUJDO0FBRU0sS0FBSyxVQUFVLG1CQUFtQixDQUFDLE1BQXlDO0lBQy9FLElBQUksRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxDQUFDO0lBQy9CLElBQUksTUFBTSxHQUFHLE1BQU0sVUFBVSxDQUFDO1FBQzFCLElBQUk7UUFDSixNQUFNLEVBQUUsZUFBZSxPQUFPLDJDQUEyQztRQUN6RSxZQUFZLEVBQUUsSUFBSTtLQUNyQixDQUFDLENBQUM7SUFDSCxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLEtBQUssS0FBSyxDQUFDO0FBQzFDLENBQUM7QUFSRCxrREFRQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHNwYXduUHJvbWlzZSB9IGZyb20gXCIuL3NwYXduXCI7XG5cbmNvbnN0IFNTSF9DT05ORUNUX1RJTUVPVVQgPSAxMDtcbmNvbnN0IEVYSVNUU19NQVJLRVIgPSBcIl9fUE9SVFNFQ1VSRV9FWElTVFNfX1wiO1xuY29uc3QgTUlTU0lOR19NQVJLRVIgPSBcIl9fUE9SVFNFQ1VSRV9NSVNTSU5HX19cIjtcbmNvbnN0IE1BWF9FUlJPUl9CT0RZX0xFTkdUSCA9IDUwMDtcblxuLy8gL2V0YyBpcyByb290IG93bmVkLCBzbyBmYWxsIGJhY2sgdG8gc3VkbyB3aGVuZXZlciB0aGUgU1NIIHVzZXIgaXMgbm90IHJvb3QuXG5leHBvcnQgY29uc3QgU1VET19QUkVBTUJMRSA9IGBTVURPPVwiXCI7IGlmIFsgXCIkKGlkIC11KVwiIC1uZSAwIF07IHRoZW4gU1VETz1cInN1ZG8gLW5cIjsgZmlgO1xuXG4vKiogVGhlIGhvc3Qgc3RyaW5nIGlzIGhhbmRlZCB0byBzc2ggdW50b3VjaGVkLiBVc2Vycywga2V5cyBhbmQgcG9ydHMgYmVsb25nIGluIHRoZSBjYWxsZXIncyBzc2hcbiAgICBjb25maWcsIHNvIEJhdGNoTW9kZSBtYWtlcyBhIG1pc3Npbmcgc2V0dXAgZmFpbCBpbW1lZGlhdGVseSBpbnN0ZWFkIG9mIHByb21wdGluZy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBydW5PdmVyU1NIKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IHNjcmlwdDogc3RyaW5nOyBpbnB1dD86IHN0cmluZzsgYWxsb3dGYWlsdXJlPzogYm9vbGVhbiB9KSB7XG4gICAgbGV0IHsgaG9zdCwgc2NyaXB0LCBpbnB1dCwgYWxsb3dGYWlsdXJlIH0gPSBjb25maWc7XG4gICAgbGV0IHJlc3VsdCA9IGF3YWl0IHNwYXduUHJvbWlzZSh7XG4gICAgICAgIGNvbW1hbmQ6IFwic3NoXCIsXG4gICAgICAgIGFyZ3M6IFtcbiAgICAgICAgICAgIFwiLW9cIiwgXCJCYXRjaE1vZGU9eWVzXCIsXG4gICAgICAgICAgICBcIi1vXCIsIGBDb25uZWN0VGltZW91dD0ke1NTSF9DT05ORUNUX1RJTUVPVVR9YCxcbiAgICAgICAgICAgIGhvc3QsXG4gICAgICAgICAgICBzY3JpcHQsXG4gICAgICAgIF0sXG4gICAgICAgIGlucHV0LFxuICAgICAgICBpbmhlcml0U3RkZXJyOiAhYWxsb3dGYWlsdXJlLFxuICAgIH0pO1xuICAgIGlmIChyZXN1bHQuZXJyb3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBzc2ggdG8gcnVuIGFnYWluc3QgJHtob3N0fSwgZmFpbGVkIHdpdGggJHtyZXN1bHQuZXJyb3IubWVzc2FnZX1gKTtcbiAgICB9XG4gICAgaWYgKHJlc3VsdC5zdGF0dXMgIT09IDAgJiYgIWFsbG93RmFpbHVyZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgc3NoIHRvICR7aG9zdH0gdG8gZXhpdCAwLCB3YXMgJHtyZXN1bHQuc3RhdHVzfS5gXG4gICAgICAgICAgICArIGAgTm9uLWludGVyYWN0aXZlIHNzaCBhY2Nlc3MgdG8gJHtob3N0fSBoYXMgdG8gd29yayBvbiBpdHMgb3duIC0gZml4IGl0IGluIHlvdXIgc3NoIGNvbmZpZy5cXG5gXG4gICAgICAgICAgICArIGAkeyhyZXN1bHQuc3Rkb3V0ICsgcmVzdWx0LnN0ZGVycikudHJpbSgpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YFxuICAgICAgICApO1xuICAgIH1cbiAgICByZXR1cm4geyBzdGRvdXQ6IHJlc3VsdC5zdGRvdXQgfHwgXCJcIiwgc3RkZXJyOiByZXN1bHQuc3RkZXJyIHx8IFwiXCIsIHN0YXR1czogcmVzdWx0LnN0YXR1cyB9O1xufVxuXG4vKiogUmV0dXJucyB1bmRlZmluZWQgd2hlbiB0aGUgZmlsZSBkb2VzIG5vdCBleGlzdCwgc28gYSBtaXNzaW5nIGZpbGUgcmVhZHMgZGlmZmVyZW50bHkgZnJvbSBhblxuICAgIGVtcHR5IG9uZS4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkUmVtb3RlRmlsZShjb25maWc6IHsgaG9zdDogc3RyaW5nOyBmaWxlUGF0aDogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBob3N0LCBmaWxlUGF0aCB9ID0gY29uZmlnO1xuICAgIGxldCBvdXRwdXQgPSAoYXdhaXQgcnVuT3ZlclNTSCh7XG4gICAgICAgIGhvc3QsXG4gICAgICAgIHNjcmlwdDogYCR7U1VET19QUkVBTUJMRX1cbmlmICRTVURPIHRlc3QgLWYgXCIke2ZpbGVQYXRofVwiOyB0aGVuXG4gICAgZWNobyBcIiR7RVhJU1RTX01BUktFUn1cIlxuICAgICRTVURPIGNhdCBcIiR7ZmlsZVBhdGh9XCJcbmVsc2VcbiAgICBlY2hvIFwiJHtNSVNTSU5HX01BUktFUn1cIlxuZmlgLFxuICAgIH0pKS5zdGRvdXQ7XG4gICAgbGV0IG5ld2xpbmVJbmRleCA9IG91dHB1dC5pbmRleE9mKFwiXFxuXCIpO1xuICAgIGxldCBtYXJrZXIgPSBvdXRwdXQuc2xpY2UoMCwgbmV3bGluZUluZGV4KS50cmltKCk7XG4gICAgaWYgKG1hcmtlciA9PT0gTUlTU0lOR19NQVJLRVIpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgaWYgKG1hcmtlciAhPT0gRVhJU1RTX01BUktFUikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkICR7RVhJU1RTX01BUktFUn0gb3IgJHtNSVNTSU5HX01BUktFUn0gZnJvbSAke2hvc3R9LCB3YXMgJHtvdXRwdXQuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIG91dHB1dC5zbGljZShuZXdsaW5lSW5kZXggKyAxKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHdyaXRlUmVtb3RlRmlsZShjb25maWc6IHtcbiAgICBob3N0OiBzdHJpbmc7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICBjb250ZW50czogc3RyaW5nO1xuICAgIGZpbGVNb2RlOiBzdHJpbmc7XG4gICAgZGlyZWN0b3J5TW9kZTogc3RyaW5nO1xufSkge1xuICAgIGxldCB7IGhvc3QsIGZpbGVQYXRoLCBjb250ZW50cywgZmlsZU1vZGUsIGRpcmVjdG9yeU1vZGUgfSA9IGNvbmZpZztcbiAgICBsZXQgZGlyZWN0b3J5ID0gZmlsZVBhdGguc2xpY2UoMCwgZmlsZVBhdGgubGFzdEluZGV4T2YoXCIvXCIpKTtcbiAgICBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgLy8gRGVwbG95aW5nIGZyb20gV2luZG93cyBtdXN0IG5vdCBjYXJyeSBDUkxGIG9udG8gdGhlIHRhcmdldCwgd2hlcmUgaXQgYnJlYWtzIHN5c3RlbWRcbiAgICAgICAgLy8gdW5pdHMgYW5kIG1ha2VzIHByaXZhdGUga2V5cyB1bnJlYWRhYmxlIHRvIHNzaC5cbiAgICAgICAgaW5wdXQ6IGNvbnRlbnRzLnJlcGxhY2UoL1xcclxcbi9nLCBcIlxcblwiKSxcbiAgICAgICAgc2NyaXB0OiBgJHtTVURPX1BSRUFNQkxFfVxuc2V0IC1lXG4kU1VETyBta2RpciAtcCBcIiR7ZGlyZWN0b3J5fVwiXG4kU1VETyBjaG1vZCAke2RpcmVjdG9yeU1vZGV9IFwiJHtkaXJlY3Rvcnl9XCJcbiRTVURPIHRlZSBcIiR7ZmlsZVBhdGh9XCIgPiAvZGV2L251bGxcbiRTVURPIGNobW9kICR7ZmlsZU1vZGV9IFwiJHtmaWxlUGF0aH1cImAsXG4gICAgfSk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdGVDb21tYW5kRXhpc3RzKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IGNvbW1hbmQ6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgaG9zdCwgY29tbWFuZCB9ID0gY29uZmlnO1xuICAgIGxldCByZXN1bHQgPSBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgc2NyaXB0OiBgY29tbWFuZCAtdiBcIiR7Y29tbWFuZH1cIiA+IC9kZXYvbnVsbCAyPiYxICYmIGVjaG8geWVzIHx8IGVjaG8gbm9gLFxuICAgICAgICBhbGxvd0ZhaWx1cmU6IHRydWUsXG4gICAgfSk7XG4gICAgcmV0dXJuIHJlc3VsdC5zdGRvdXQudHJpbSgpID09PSBcInllc1wiO1xufVxuIl19
|
|
90
|
+
/* _JS_SOURCE_HASH = "b9707fe65376e19327371edb2ebdda68daa9b9b21220d7ac91ec21ef22520290"; */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
|
+
//exports.spawnPromise = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
// PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js contains a plain JS port of spawnPromise, so it can run
|
|
6
|
+
// with no dependencies. If you change one, make the matching change in the other.
|
|
7
|
+
/** An argument list rather than a shell string, so hostnames and paths can never be parsed as
|
|
8
|
+
shell syntax. Resolves with the exit code instead of throwing, callers decide what a failure
|
|
9
|
+
means. `inheritStderr` lets a child's own errors reach the terminal as they happen. */
|
|
10
|
+
function spawnPromise(config) {
|
|
11
|
+
let { command, args, cwd, input, inheritStderr, timeoutTime } = config;
|
|
12
|
+
return new Promise(resolve => {
|
|
13
|
+
var _a, _b, _c, _d;
|
|
14
|
+
let child = (0, child_process_1.spawn)(command, args, {
|
|
15
|
+
cwd,
|
|
16
|
+
stdio: ["pipe", "pipe", inheritStderr && "inherit" || "pipe"],
|
|
17
|
+
});
|
|
18
|
+
let stdout = "";
|
|
19
|
+
let stderr = "";
|
|
20
|
+
// A command that never returns would otherwise hold up everything behind it.
|
|
21
|
+
let timer = timeoutTime && setTimeout(() => child.kill("SIGKILL"), timeoutTime) || undefined;
|
|
22
|
+
child.on("close", () => timer && clearTimeout(timer));
|
|
23
|
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", chunk => stdout += chunk);
|
|
24
|
+
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", chunk => stderr += chunk);
|
|
25
|
+
child.on("error", error => resolve({ stdout, stderr, status: undefined, error }));
|
|
26
|
+
child.on("close", status => resolve({ stdout, stderr, status: status !== null && status !== void 0 ? status : undefined, error: undefined }));
|
|
27
|
+
// A child that exits before reading everything would otherwise raise EPIPE.
|
|
28
|
+
(_c = child.stdin) === null || _c === void 0 ? void 0 : _c.on("error", () => undefined);
|
|
29
|
+
(_d = child.stdin) === null || _d === void 0 ? void 0 : _d.end(input || "");
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
exports.spawnPromise = spawnPromise;
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Bhd24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzcGF3bi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxpREFBc0M7QUFFdEMsMEhBQTBIO0FBQzFILGtGQUFrRjtBQUVsRjs7MEZBRTBGO0FBQzFGLFNBQWdCLFlBQVksQ0FBQyxNQU81QjtJQUNHLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN2RSxPQUFPLElBQUksT0FBTyxDQUEyRixPQUFPLENBQUMsRUFBRTs7UUFDbkgsSUFBSSxLQUFLLEdBQUcsSUFBQSxxQkFBSyxFQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUU7WUFDN0IsR0FBRztZQUNILEtBQUssRUFBRSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsYUFBYSxJQUFJLFNBQVMsSUFBSSxNQUFNLENBQUM7U0FDaEUsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxNQUFNLEdBQUcsRUFBRSxDQUFDO1FBQ2hCLElBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztRQUNoQiw2RUFBNkU7UUFDN0UsSUFBSSxLQUFLLEdBQUcsV0FBVyxJQUFJLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLFdBQVcsQ0FBQyxJQUFJLFNBQVMsQ0FBQztRQUM3RixLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxLQUFLLElBQUksWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDdEQsTUFBQSxLQUFLLENBQUMsTUFBTSwwQ0FBRSxFQUFFLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLENBQUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxDQUFDO1FBQ25ELE1BQUEsS0FBSyxDQUFDLE1BQU0sMENBQUUsRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxDQUFDLE1BQU0sSUFBSSxLQUFLLENBQUMsQ0FBQztRQUNuRCxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDbEYsS0FBSyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQU4sTUFBTSxjQUFOLE1BQU0sR0FBSSxTQUFTLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUN4Ryw0RUFBNEU7UUFDNUUsTUFBQSxLQUFLLENBQUMsS0FBSywwQ0FBRSxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzFDLE1BQUEsS0FBSyxDQUFDLEtBQUssMENBQUUsR0FBRyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNsQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUM7QUEzQkQsb0NBMkJDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3Bhd24gfSBmcm9tIFwiY2hpbGRfcHJvY2Vzc1wiO1xuXG4vLyBQT1JURUQgQ09ERTogc2VjdXJpdHkvYXV0aG9yaXplZEtleXMvZGFlbW9uL3BvcnRzZWN1cmVEYWVtb24uanMgY29udGFpbnMgYSBwbGFpbiBKUyBwb3J0IG9mIHNwYXduUHJvbWlzZSwgc28gaXQgY2FuIHJ1blxuLy8gd2l0aCBubyBkZXBlbmRlbmNpZXMuIElmIHlvdSBjaGFuZ2Ugb25lLCBtYWtlIHRoZSBtYXRjaGluZyBjaGFuZ2UgaW4gdGhlIG90aGVyLlxuXG4vKiogQW4gYXJndW1lbnQgbGlzdCByYXRoZXIgdGhhbiBhIHNoZWxsIHN0cmluZywgc28gaG9zdG5hbWVzIGFuZCBwYXRocyBjYW4gbmV2ZXIgYmUgcGFyc2VkIGFzXG4gICAgc2hlbGwgc3ludGF4LiBSZXNvbHZlcyB3aXRoIHRoZSBleGl0IGNvZGUgaW5zdGVhZCBvZiB0aHJvd2luZywgY2FsbGVycyBkZWNpZGUgd2hhdCBhIGZhaWx1cmVcbiAgICBtZWFucy4gYGluaGVyaXRTdGRlcnJgIGxldHMgYSBjaGlsZCdzIG93biBlcnJvcnMgcmVhY2ggdGhlIHRlcm1pbmFsIGFzIHRoZXkgaGFwcGVuLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNwYXduUHJvbWlzZShjb25maWc6IHtcbiAgICBjb21tYW5kOiBzdHJpbmc7XG4gICAgYXJnczogc3RyaW5nW107XG4gICAgY3dkPzogc3RyaW5nO1xuICAgIGlucHV0Pzogc3RyaW5nO1xuICAgIGluaGVyaXRTdGRlcnI/OiBib29sZWFuO1xuICAgIHRpbWVvdXRUaW1lPzogbnVtYmVyO1xufSkge1xuICAgIGxldCB7IGNvbW1hbmQsIGFyZ3MsIGN3ZCwgaW5wdXQsIGluaGVyaXRTdGRlcnIsIHRpbWVvdXRUaW1lIH0gPSBjb25maWc7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPHsgc3Rkb3V0OiBzdHJpbmc7IHN0ZGVycjogc3RyaW5nOyBzdGF0dXM6IG51bWJlciB8IHVuZGVmaW5lZDsgZXJyb3I6IEVycm9yIHwgdW5kZWZpbmVkIH0+KHJlc29sdmUgPT4ge1xuICAgICAgICBsZXQgY2hpbGQgPSBzcGF3bihjb21tYW5kLCBhcmdzLCB7XG4gICAgICAgICAgICBjd2QsXG4gICAgICAgICAgICBzdGRpbzogW1wicGlwZVwiLCBcInBpcGVcIiwgaW5oZXJpdFN0ZGVyciAmJiBcImluaGVyaXRcIiB8fCBcInBpcGVcIl0sXG4gICAgICAgIH0pO1xuICAgICAgICBsZXQgc3Rkb3V0ID0gXCJcIjtcbiAgICAgICAgbGV0IHN0ZGVyciA9IFwiXCI7XG4gICAgICAgIC8vIEEgY29tbWFuZCB0aGF0IG5ldmVyIHJldHVybnMgd291bGQgb3RoZXJ3aXNlIGhvbGQgdXAgZXZlcnl0aGluZyBiZWhpbmQgaXQuXG4gICAgICAgIGxldCB0aW1lciA9IHRpbWVvdXRUaW1lICYmIHNldFRpbWVvdXQoKCkgPT4gY2hpbGQua2lsbChcIlNJR0tJTExcIiksIHRpbWVvdXRUaW1lKSB8fCB1bmRlZmluZWQ7XG4gICAgICAgIGNoaWxkLm9uKFwiY2xvc2VcIiwgKCkgPT4gdGltZXIgJiYgY2xlYXJUaW1lb3V0KHRpbWVyKSk7XG4gICAgICAgIGNoaWxkLnN0ZG91dD8ub24oXCJkYXRhXCIsIGNodW5rID0+IHN0ZG91dCArPSBjaHVuayk7XG4gICAgICAgIGNoaWxkLnN0ZGVycj8ub24oXCJkYXRhXCIsIGNodW5rID0+IHN0ZGVyciArPSBjaHVuayk7XG4gICAgICAgIGNoaWxkLm9uKFwiZXJyb3JcIiwgZXJyb3IgPT4gcmVzb2x2ZSh7IHN0ZG91dCwgc3RkZXJyLCBzdGF0dXM6IHVuZGVmaW5lZCwgZXJyb3IgfSkpO1xuICAgICAgICBjaGlsZC5vbihcImNsb3NlXCIsIHN0YXR1cyA9PiByZXNvbHZlKHsgc3Rkb3V0LCBzdGRlcnIsIHN0YXR1czogc3RhdHVzID8/IHVuZGVmaW5lZCwgZXJyb3I6IHVuZGVmaW5lZCB9KSk7XG4gICAgICAgIC8vIEEgY2hpbGQgdGhhdCBleGl0cyBiZWZvcmUgcmVhZGluZyBldmVyeXRoaW5nIHdvdWxkIG90aGVyd2lzZSByYWlzZSBFUElQRS5cbiAgICAgICAgY2hpbGQuc3RkaW4/Lm9uKFwiZXJyb3JcIiwgKCkgPT4gdW5kZWZpbmVkKTtcbiAgICAgICAgY2hpbGQuc3RkaW4/LmVuZChpbnB1dCB8fCBcIlwiKTtcbiAgICB9KTtcbn1cbiJdfQ==
|
|
34
|
+
/* _JS_SOURCE_HASH = "148887f3afb42a330df49eb955396403fab2e558c30741383b1a17b19a134288"; */
|
|
@@ -8,28 +8,40 @@ const MAX_ERROR_BODY_LENGTH = 500;
|
|
|
8
8
|
// /etc is root owned, so fall back to sudo whenever the SSH user is not root.
|
|
9
9
|
export const SUDO_PREAMBLE = `SUDO=""; if [ "$(id -u)" -ne 0 ]; then SUDO="sudo -n"; fi`;
|
|
10
10
|
|
|
11
|
+
/** No host means this machine, so the very same script runs with nothing in front of it. Local and
|
|
12
|
+
remote then do exactly the same thing, rather than being two pieces of code that have to be kept
|
|
13
|
+
saying the same thing. */
|
|
14
|
+
export const THIS_MACHINE = "";
|
|
15
|
+
|
|
16
|
+
export function describeHost(host: string) {
|
|
17
|
+
return host || "this machine";
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
/** The host string is handed to ssh untouched. Users, keys and ports belong in the caller's ssh
|
|
12
21
|
config, so BatchMode makes a missing setup fail immediately instead of prompting. */
|
|
13
22
|
export async function runOverSSH(config: { host: string; script: string; input?: string; allowFailure?: boolean }) {
|
|
14
23
|
let { host, script, input, allowFailure } = config;
|
|
15
24
|
let result = await spawnPromise({
|
|
16
|
-
command: "ssh",
|
|
17
|
-
args: [
|
|
25
|
+
command: host && "ssh" || "sh",
|
|
26
|
+
args: host && [
|
|
18
27
|
"-o", "BatchMode=yes",
|
|
19
28
|
"-o", `ConnectTimeout=${SSH_CONNECT_TIMEOUT}`,
|
|
20
29
|
host,
|
|
21
30
|
script,
|
|
22
|
-
],
|
|
31
|
+
] || ["-c", script],
|
|
23
32
|
input,
|
|
24
33
|
inheritStderr: !allowFailure,
|
|
25
34
|
});
|
|
26
35
|
if (result.error) {
|
|
27
|
-
throw new Error(`Expected
|
|
36
|
+
throw new Error(`Expected to run against ${describeHost(host)}, failed with ${result.error.message}`);
|
|
28
37
|
}
|
|
29
38
|
if (result.status !== 0 && !allowFailure) {
|
|
39
|
+
let advice = host
|
|
40
|
+
&& ` Non-interactive ssh access to ${host} has to work on its own - fix it in your ssh config.`
|
|
41
|
+
|| "";
|
|
30
42
|
throw new Error(
|
|
31
|
-
`Expected
|
|
32
|
-
+
|
|
43
|
+
`Expected the script on ${describeHost(host)} to exit 0, was ${result.status}.${advice}\n`
|
|
44
|
+
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`
|
|
33
45
|
);
|
|
34
46
|
}
|
|
35
47
|
return { stdout: result.stdout || "", stderr: result.stderr || "", status: result.status };
|
|
@@ -12,8 +12,9 @@ export function spawnPromise(config: {
|
|
|
12
12
|
cwd?: string;
|
|
13
13
|
input?: string;
|
|
14
14
|
inheritStderr?: boolean;
|
|
15
|
+
timeoutTime?: number;
|
|
15
16
|
}) {
|
|
16
|
-
let { command, args, cwd, input, inheritStderr } = config;
|
|
17
|
+
let { command, args, cwd, input, inheritStderr, timeoutTime } = config;
|
|
17
18
|
return new Promise<{ stdout: string; stderr: string; status: number | undefined; error: Error | undefined }>(resolve => {
|
|
18
19
|
let child = spawn(command, args, {
|
|
19
20
|
cwd,
|
|
@@ -21,6 +22,9 @@ export function spawnPromise(config: {
|
|
|
21
22
|
});
|
|
22
23
|
let stdout = "";
|
|
23
24
|
let stderr = "";
|
|
25
|
+
// A command that never returns would otherwise hold up everything behind it.
|
|
26
|
+
let timer = timeoutTime && setTimeout(() => child.kill("SIGKILL"), timeoutTime) || undefined;
|
|
27
|
+
child.on("close", () => timer && clearTimeout(timer));
|
|
24
28
|
child.stdout?.on("data", chunk => stdout += chunk);
|
|
25
29
|
child.stderr?.on("data", chunk => stderr += chunk);
|
|
26
30
|
child.on("error", error => resolve({ stdout, stderr, status: undefined, error }));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { expandHome } from "../helpers/paths";
|
|
4
|
+
import { formatOpenSSHPrivateKey, formatPublicKeyLine, parseOpenSSHPrivateKey, publicKeyFromSeed } from "./sshKeyFile";
|
|
5
|
+
|
|
6
|
+
const USAGE = `Usage: yarn derivekey <label> <source-key> <derived-key>
|
|
7
|
+
|
|
8
|
+
Derives a second ed25519 key from an existing one. The same label and source key always give the
|
|
9
|
+
same derived key, so it can be worked out again rather than backed up.
|
|
10
|
+
|
|
11
|
+
yarn derivekey revokegithubkey ~/authorized_keys_access/id_ed25519 ~/authorized_keys_access/id_ed25519_revoke`;
|
|
12
|
+
|
|
13
|
+
/** A label mixed into a key's secret to get another key, so one key can stand behind several
|
|
14
|
+
identities without any of them being stored.
|
|
15
|
+
|
|
16
|
+
HMAC rather than hashing the two joined together: with a plain hash, anyone who obtained one
|
|
17
|
+
derived key could extend it into further labels without ever knowing the source key. The label
|
|
18
|
+
is the message and the source secret is the key, which is what HMAC is for. */
|
|
19
|
+
export function deriveEd25519Key(config: { seed: Buffer; label: string }) {
|
|
20
|
+
let { seed, label } = config;
|
|
21
|
+
if (!label) {
|
|
22
|
+
throw new Error(`Expected a label to derive with, was ${JSON.stringify(label)}`);
|
|
23
|
+
}
|
|
24
|
+
let derivedSeed = crypto.createHmac("sha256", seed).update(label, "utf8").digest();
|
|
25
|
+
return { seed: derivedSeed, publicKey: publicKeyFromSeed(derivedSeed) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function pathExists(filePath: string) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(filePath);
|
|
31
|
+
return true;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv: string[]) {
|
|
38
|
+
if (argv.length !== 3) {
|
|
39
|
+
throw new Error(`Expected a label, a source key and a derived key, was ${argv.length} argument(s)\n${USAGE}`);
|
|
40
|
+
}
|
|
41
|
+
let [label, sourcePath, derivedPath] = argv;
|
|
42
|
+
return { label, sourcePath: expandHome(sourcePath), derivedPath: expandHome(derivedPath) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function main() {
|
|
46
|
+
let { label, sourcePath, derivedPath } = parseArgs(process.argv.slice(2));
|
|
47
|
+
|
|
48
|
+
if (!await pathExists(sourcePath)) {
|
|
49
|
+
throw new Error(`Expected a source key at ${sourcePath}, no such file exists`);
|
|
50
|
+
}
|
|
51
|
+
// Writing over a key would strand whatever it already gives access to, and the derived key can
|
|
52
|
+
// always be worked out again, so there is never a reason to overwrite.
|
|
53
|
+
if (await pathExists(derivedPath)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Expected ${derivedPath} to not exist, it does. Deriving the same label again gives the`
|
|
56
|
+
+ ` same key, so delete it first if you want it rewritten.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let source = parseOpenSSHPrivateKey(await fs.readFile(sourcePath, "utf8"));
|
|
61
|
+
let derived = deriveEd25519Key({ seed: source.seed, label });
|
|
62
|
+
let comment = `${source.comment || sourcePath} ${label}`.trim();
|
|
63
|
+
|
|
64
|
+
await fs.writeFile(derivedPath, formatOpenSSHPrivateKey({ seed: derived.seed, comment }), { mode: 0o600 });
|
|
65
|
+
let publicLine = formatPublicKeyLine({ publicKey: derived.publicKey, comment });
|
|
66
|
+
await fs.writeFile(`${derivedPath}.pub`, publicLine, { mode: 0o644 });
|
|
67
|
+
|
|
68
|
+
console.log(`Derived "${label}" from ${sourcePath}`);
|
|
69
|
+
console.log(` private ${derivedPath}`);
|
|
70
|
+
console.log(` public ${derivedPath}.pub`);
|
|
71
|
+
console.log(publicLine.trim());
|
|
72
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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.main = exports.deriveEd25519Key = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const paths_1 = require("../helpers/paths");
|
|
10
|
+
const sshKeyFile_1 = require("./sshKeyFile");
|
|
11
|
+
const USAGE = `Usage: yarn derivekey <label> <source-key> <derived-key>
|
|
12
|
+
|
|
13
|
+
Derives a second ed25519 key from an existing one. The same label and source key always give the
|
|
14
|
+
same derived key, so it can be worked out again rather than backed up.
|
|
15
|
+
|
|
16
|
+
yarn derivekey revokegithubkey ~/authorized_keys_access/id_ed25519 ~/authorized_keys_access/id_ed25519_revoke`;
|
|
17
|
+
/** A label mixed into a key's secret to get another key, so one key can stand behind several
|
|
18
|
+
identities without any of them being stored.
|
|
19
|
+
|
|
20
|
+
HMAC rather than hashing the two joined together: with a plain hash, anyone who obtained one
|
|
21
|
+
derived key could extend it into further labels without ever knowing the source key. The label
|
|
22
|
+
is the message and the source secret is the key, which is what HMAC is for. */
|
|
23
|
+
function deriveEd25519Key(config) {
|
|
24
|
+
let { seed, label } = config;
|
|
25
|
+
if (!label) {
|
|
26
|
+
throw new Error(`Expected a label to derive with, was ${JSON.stringify(label)}`);
|
|
27
|
+
}
|
|
28
|
+
let derivedSeed = crypto_1.default.createHmac("sha256", seed).update(label, "utf8").digest();
|
|
29
|
+
return { seed: derivedSeed, publicKey: (0, sshKeyFile_1.publicKeyFromSeed)(derivedSeed) };
|
|
30
|
+
}
|
|
31
|
+
exports.deriveEd25519Key = deriveEd25519Key;
|
|
32
|
+
async function pathExists(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
await promises_1.default.access(filePath);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parseArgs(argv) {
|
|
42
|
+
if (argv.length !== 3) {
|
|
43
|
+
throw new Error(`Expected a label, a source key and a derived key, was ${argv.length} argument(s)\n${USAGE}`);
|
|
44
|
+
}
|
|
45
|
+
let [label, sourcePath, derivedPath] = argv;
|
|
46
|
+
return { label, sourcePath: (0, paths_1.expandHome)(sourcePath), derivedPath: (0, paths_1.expandHome)(derivedPath) };
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
let { label, sourcePath, derivedPath } = parseArgs(process.argv.slice(2));
|
|
50
|
+
if (!await pathExists(sourcePath)) {
|
|
51
|
+
throw new Error(`Expected a source key at ${sourcePath}, no such file exists`);
|
|
52
|
+
}
|
|
53
|
+
// Writing over a key would strand whatever it already gives access to, and the derived key can
|
|
54
|
+
// always be worked out again, so there is never a reason to overwrite.
|
|
55
|
+
if (await pathExists(derivedPath)) {
|
|
56
|
+
throw new Error(`Expected ${derivedPath} to not exist, it does. Deriving the same label again gives the`
|
|
57
|
+
+ ` same key, so delete it first if you want it rewritten.`);
|
|
58
|
+
}
|
|
59
|
+
let source = (0, sshKeyFile_1.parseOpenSSHPrivateKey)(await promises_1.default.readFile(sourcePath, "utf8"));
|
|
60
|
+
let derived = deriveEd25519Key({ seed: source.seed, label });
|
|
61
|
+
let comment = `${source.comment || sourcePath} ${label}`.trim();
|
|
62
|
+
await promises_1.default.writeFile(derivedPath, (0, sshKeyFile_1.formatOpenSSHPrivateKey)({ seed: derived.seed, comment }), { mode: 0o600 });
|
|
63
|
+
let publicLine = (0, sshKeyFile_1.formatPublicKeyLine)({ publicKey: derived.publicKey, comment });
|
|
64
|
+
await promises_1.default.writeFile(`${derivedPath}.pub`, publicLine, { mode: 0o644 });
|
|
65
|
+
console.log(`Derived "${label}" from ${sourcePath}`);
|
|
66
|
+
console.log(` private ${derivedPath}`);
|
|
67
|
+
console.log(` public ${derivedPath}.pub`);
|
|
68
|
+
console.log(publicLine.trim());
|
|
69
|
+
}
|
|
70
|
+
exports.main = main;
|
|
71
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVyaXZlS2V5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVyaXZlS2V5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLG9EQUE0QjtBQUM1QiwyREFBNkI7QUFDN0IsNENBQThDO0FBQzlDLDZDQUF1SDtBQUV2SCxNQUFNLEtBQUssR0FBRzs7Ozs7Z0hBS2tHLENBQUM7QUFFakg7Ozs7O2tGQUtrRjtBQUNsRixTQUFnQixnQkFBZ0IsQ0FBQyxNQUF1QztJQUNwRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUM3QixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDVCxNQUFNLElBQUksS0FBSyxDQUFDLHdDQUF3QyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNyRixDQUFDO0lBQ0QsSUFBSSxXQUFXLEdBQUcsZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDbkYsT0FBTyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLElBQUEsOEJBQWlCLEVBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztBQUM1RSxDQUFDO0FBUEQsNENBT0M7QUFFRCxLQUFLLFVBQVUsVUFBVSxDQUFDLFFBQWdCO0lBQ3RDLElBQUksQ0FBQztRQUNELE1BQU0sa0JBQUUsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDMUIsT0FBTyxJQUFJLENBQUM7SUFDaEIsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDVCxPQUFPLEtBQUssQ0FBQztJQUNqQixDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLElBQWM7SUFDN0IsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELElBQUksQ0FBQyxNQUFNLGlCQUFpQixLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ2xILENBQUM7SUFDRCxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDNUMsT0FBTyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsSUFBQSxrQkFBVSxFQUFDLFVBQVUsQ0FBQyxFQUFFLFdBQVcsRUFBRSxJQUFBLGtCQUFVLEVBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztBQUMvRixDQUFDO0FBRU0sS0FBSyxVQUFVLElBQUk7SUFDdEIsSUFBSSxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFMUUsSUFBSSxDQUFDLE1BQU0sVUFBVSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsVUFBVSx1QkFBdUIsQ0FBQyxDQUFDO0lBQ25GLENBQUM7SUFDRCwrRkFBK0Y7SUFDL0YsdUVBQXVFO0lBQ3ZFLElBQUksTUFBTSxVQUFVLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztRQUNoQyxNQUFNLElBQUksS0FBSyxDQUNYLFlBQVksV0FBVyxpRUFBaUU7Y0FDdEYseURBQXlELENBQzlELENBQUM7SUFDTixDQUFDO0lBRUQsSUFBSSxNQUFNLEdBQUcsSUFBQSxtQ0FBc0IsRUFBQyxNQUFNLGtCQUFFLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzNFLElBQUksT0FBTyxHQUFHLGdCQUFnQixDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztJQUM3RCxJQUFJLE9BQU8sR0FBRyxHQUFHLE1BQU0sQ0FBQyxPQUFPLElBQUksVUFBVSxJQUFJLEtBQUssRUFBRSxDQUFDLElBQUksRUFBRSxDQUFDO0lBRWhFLE1BQU0sa0JBQUUsQ0FBQyxTQUFTLENBQUMsV0FBVyxFQUFFLElBQUEsb0NBQXVCLEVBQUMsRUFBRSxJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7SUFDM0csSUFBSSxVQUFVLEdBQUcsSUFBQSxnQ0FBbUIsRUFBQyxFQUFFLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDaEYsTUFBTSxrQkFBRSxDQUFDLFNBQVMsQ0FBQyxHQUFHLFdBQVcsTUFBTSxFQUFFLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBRXRFLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxLQUFLLFVBQVUsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNyRCxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsV0FBVyxFQUFFLENBQUMsQ0FBQztJQUN4QyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsV0FBVyxNQUFNLENBQUMsQ0FBQztJQUM1QyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQ25DLENBQUM7QUEzQkQsb0JBMkJDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNyeXB0byBmcm9tIFwiY3J5cHRvXCI7XG5pbXBvcnQgZnMgZnJvbSBcImZzL3Byb21pc2VzXCI7XG5pbXBvcnQgeyBleHBhbmRIb21lIH0gZnJvbSBcIi4uL2hlbHBlcnMvcGF0aHNcIjtcbmltcG9ydCB7IGZvcm1hdE9wZW5TU0hQcml2YXRlS2V5LCBmb3JtYXRQdWJsaWNLZXlMaW5lLCBwYXJzZU9wZW5TU0hQcml2YXRlS2V5LCBwdWJsaWNLZXlGcm9tU2VlZCB9IGZyb20gXCIuL3NzaEtleUZpbGVcIjtcblxuY29uc3QgVVNBR0UgPSBgVXNhZ2U6IHlhcm4gZGVyaXZla2V5IDxsYWJlbD4gPHNvdXJjZS1rZXk+IDxkZXJpdmVkLWtleT5cblxuRGVyaXZlcyBhIHNlY29uZCBlZDI1NTE5IGtleSBmcm9tIGFuIGV4aXN0aW5nIG9uZS4gVGhlIHNhbWUgbGFiZWwgYW5kIHNvdXJjZSBrZXkgYWx3YXlzIGdpdmUgdGhlXG5zYW1lIGRlcml2ZWQga2V5LCBzbyBpdCBjYW4gYmUgd29ya2VkIG91dCBhZ2FpbiByYXRoZXIgdGhhbiBiYWNrZWQgdXAuXG5cbiAgeWFybiBkZXJpdmVrZXkgcmV2b2tlZ2l0aHVia2V5IH4vYXV0aG9yaXplZF9rZXlzX2FjY2Vzcy9pZF9lZDI1NTE5IH4vYXV0aG9yaXplZF9rZXlzX2FjY2Vzcy9pZF9lZDI1NTE5X3Jldm9rZWA7XG5cbi8qKiBBIGxhYmVsIG1peGVkIGludG8gYSBrZXkncyBzZWNyZXQgdG8gZ2V0IGFub3RoZXIga2V5LCBzbyBvbmUga2V5IGNhbiBzdGFuZCBiZWhpbmQgc2V2ZXJhbFxuICAgIGlkZW50aXRpZXMgd2l0aG91dCBhbnkgb2YgdGhlbSBiZWluZyBzdG9yZWQuXG5cbiAgICBITUFDIHJhdGhlciB0aGFuIGhhc2hpbmcgdGhlIHR3byBqb2luZWQgdG9nZXRoZXI6IHdpdGggYSBwbGFpbiBoYXNoLCBhbnlvbmUgd2hvIG9idGFpbmVkIG9uZVxuICAgIGRlcml2ZWQga2V5IGNvdWxkIGV4dGVuZCBpdCBpbnRvIGZ1cnRoZXIgbGFiZWxzIHdpdGhvdXQgZXZlciBrbm93aW5nIHRoZSBzb3VyY2Uga2V5LiBUaGUgbGFiZWxcbiAgICBpcyB0aGUgbWVzc2FnZSBhbmQgdGhlIHNvdXJjZSBzZWNyZXQgaXMgdGhlIGtleSwgd2hpY2ggaXMgd2hhdCBITUFDIGlzIGZvci4gKi9cbmV4cG9ydCBmdW5jdGlvbiBkZXJpdmVFZDI1NTE5S2V5KGNvbmZpZzogeyBzZWVkOiBCdWZmZXI7IGxhYmVsOiBzdHJpbmcgfSkge1xuICAgIGxldCB7IHNlZWQsIGxhYmVsIH0gPSBjb25maWc7XG4gICAgaWYgKCFsYWJlbCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgbGFiZWwgdG8gZGVyaXZlIHdpdGgsIHdhcyAke0pTT04uc3RyaW5naWZ5KGxhYmVsKX1gKTtcbiAgICB9XG4gICAgbGV0IGRlcml2ZWRTZWVkID0gY3J5cHRvLmNyZWF0ZUhtYWMoXCJzaGEyNTZcIiwgc2VlZCkudXBkYXRlKGxhYmVsLCBcInV0ZjhcIikuZGlnZXN0KCk7XG4gICAgcmV0dXJuIHsgc2VlZDogZGVyaXZlZFNlZWQsIHB1YmxpY0tleTogcHVibGljS2V5RnJvbVNlZWQoZGVyaXZlZFNlZWQpIH07XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHBhdGhFeGlzdHMoZmlsZVBhdGg6IHN0cmluZykge1xuICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGZzLmFjY2VzcyhmaWxlUGF0aCk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gcGFyc2VBcmdzKGFyZ3Y6IHN0cmluZ1tdKSB7XG4gICAgaWYgKGFyZ3YubGVuZ3RoICE9PSAzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBsYWJlbCwgYSBzb3VyY2Uga2V5IGFuZCBhIGRlcml2ZWQga2V5LCB3YXMgJHthcmd2Lmxlbmd0aH0gYXJndW1lbnQocylcXG4ke1VTQUdFfWApO1xuICAgIH1cbiAgICBsZXQgW2xhYmVsLCBzb3VyY2VQYXRoLCBkZXJpdmVkUGF0aF0gPSBhcmd2O1xuICAgIHJldHVybiB7IGxhYmVsLCBzb3VyY2VQYXRoOiBleHBhbmRIb21lKHNvdXJjZVBhdGgpLCBkZXJpdmVkUGF0aDogZXhwYW5kSG9tZShkZXJpdmVkUGF0aCkgfTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7XG4gICAgbGV0IHsgbGFiZWwsIHNvdXJjZVBhdGgsIGRlcml2ZWRQYXRoIH0gPSBwYXJzZUFyZ3MocHJvY2Vzcy5hcmd2LnNsaWNlKDIpKTtcblxuICAgIGlmICghYXdhaXQgcGF0aEV4aXN0cyhzb3VyY2VQYXRoKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgc291cmNlIGtleSBhdCAke3NvdXJjZVBhdGh9LCBubyBzdWNoIGZpbGUgZXhpc3RzYCk7XG4gICAgfVxuICAgIC8vIFdyaXRpbmcgb3ZlciBhIGtleSB3b3VsZCBzdHJhbmQgd2hhdGV2ZXIgaXQgYWxyZWFkeSBnaXZlcyBhY2Nlc3MgdG8sIGFuZCB0aGUgZGVyaXZlZCBrZXkgY2FuXG4gICAgLy8gYWx3YXlzIGJlIHdvcmtlZCBvdXQgYWdhaW4sIHNvIHRoZXJlIGlzIG5ldmVyIGEgcmVhc29uIHRvIG92ZXJ3cml0ZS5cbiAgICBpZiAoYXdhaXQgcGF0aEV4aXN0cyhkZXJpdmVkUGF0aCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkICR7ZGVyaXZlZFBhdGh9IHRvIG5vdCBleGlzdCwgaXQgZG9lcy4gRGVyaXZpbmcgdGhlIHNhbWUgbGFiZWwgYWdhaW4gZ2l2ZXMgdGhlYFxuICAgICAgICAgICAgKyBgIHNhbWUga2V5LCBzbyBkZWxldGUgaXQgZmlyc3QgaWYgeW91IHdhbnQgaXQgcmV3cml0dGVuLmBcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBsZXQgc291cmNlID0gcGFyc2VPcGVuU1NIUHJpdmF0ZUtleShhd2FpdCBmcy5yZWFkRmlsZShzb3VyY2VQYXRoLCBcInV0ZjhcIikpO1xuICAgIGxldCBkZXJpdmVkID0gZGVyaXZlRWQyNTUxOUtleSh7IHNlZWQ6IHNvdXJjZS5zZWVkLCBsYWJlbCB9KTtcbiAgICBsZXQgY29tbWVudCA9IGAke3NvdXJjZS5jb21tZW50IHx8IHNvdXJjZVBhdGh9ICR7bGFiZWx9YC50cmltKCk7XG5cbiAgICBhd2FpdCBmcy53cml0ZUZpbGUoZGVyaXZlZFBhdGgsIGZvcm1hdE9wZW5TU0hQcml2YXRlS2V5KHsgc2VlZDogZGVyaXZlZC5zZWVkLCBjb21tZW50IH0pLCB7IG1vZGU6IDBvNjAwIH0pO1xuICAgIGxldCBwdWJsaWNMaW5lID0gZm9ybWF0UHVibGljS2V5TGluZSh7IHB1YmxpY0tleTogZGVyaXZlZC5wdWJsaWNLZXksIGNvbW1lbnQgfSk7XG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKGAke2Rlcml2ZWRQYXRofS5wdWJgLCBwdWJsaWNMaW5lLCB7IG1vZGU6IDBvNjQ0IH0pO1xuXG4gICAgY29uc29sZS5sb2coYERlcml2ZWQgXCIke2xhYmVsfVwiIGZyb20gJHtzb3VyY2VQYXRofWApO1xuICAgIGNvbnNvbGUubG9nKGAgIHByaXZhdGUgJHtkZXJpdmVkUGF0aH1gKTtcbiAgICBjb25zb2xlLmxvZyhgICBwdWJsaWMgICR7ZGVyaXZlZFBhdGh9LnB1YmApO1xuICAgIGNvbnNvbGUubG9nKHB1YmxpY0xpbmUudHJpbSgpKTtcbn1cbiJdfQ==
|
|
72
|
+
/* _JS_SOURCE_HASH = "7d3b190d59d20686acd4f001d6c457d0b90d75d2ae382d6aff4571bb3310be76"; */
|
|
@@ -0,0 +1,153 @@
|
|
|
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.formatPublicKeyLine = exports.formatOpenSSHPrivateKey = exports.parseOpenSSHPrivateKey = exports.publicKeyFromSeed = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
// OpenSSH keeps private keys in its own container rather than anything node can read, so the few
|
|
9
|
+
// bytes of it we need are handled here. Only unencrypted ed25519 keys are supported, which is what
|
|
10
|
+
// a key used by automation is.
|
|
11
|
+
const MAGIC = "openssh-key-v1\0";
|
|
12
|
+
const KEY_TYPE = "ssh-ed25519";
|
|
13
|
+
const NONE = "none";
|
|
14
|
+
const SEED_LENGTH = 32;
|
|
15
|
+
const PUBLIC_LENGTH = 32;
|
|
16
|
+
// The one block size that matters here, since nothing is encrypted.
|
|
17
|
+
const PADDING_BLOCK = 8;
|
|
18
|
+
const PEM_LINE_LENGTH = 70;
|
|
19
|
+
const PRIVATE_HEADER = "-----BEGIN OPENSSH PRIVATE KEY-----";
|
|
20
|
+
const PRIVATE_FOOTER = "-----END OPENSSH PRIVATE KEY-----";
|
|
21
|
+
// The prefix that turns a bare 32 byte seed into a pkcs8 ed25519 key node will accept.
|
|
22
|
+
const PKCS8_ED25519_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
23
|
+
class ByteReader {
|
|
24
|
+
constructor(buffer) {
|
|
25
|
+
this.buffer = buffer;
|
|
26
|
+
this.offset = 0;
|
|
27
|
+
}
|
|
28
|
+
readBytes(length) {
|
|
29
|
+
let value = this.buffer.subarray(this.offset, this.offset + length);
|
|
30
|
+
if (value.length !== length) {
|
|
31
|
+
throw new Error(`Expected ${length} bytes at offset ${this.offset}, was ${value.length}`);
|
|
32
|
+
}
|
|
33
|
+
this.offset += length;
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
readUInt32() {
|
|
37
|
+
let value = this.buffer.readUInt32BE(this.offset);
|
|
38
|
+
this.offset += 4;
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
readString() {
|
|
42
|
+
return this.readBytes(this.readUInt32());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function writeString(value) {
|
|
46
|
+
let contents = Buffer.isBuffer(value) && value || Buffer.from(value, "utf8");
|
|
47
|
+
let length = Buffer.alloc(4);
|
|
48
|
+
length.writeUInt32BE(contents.length);
|
|
49
|
+
return Buffer.concat([length, contents]);
|
|
50
|
+
}
|
|
51
|
+
function writeUInt32(value) {
|
|
52
|
+
let bytes = Buffer.alloc(4);
|
|
53
|
+
bytes.writeUInt32BE(value);
|
|
54
|
+
return bytes;
|
|
55
|
+
}
|
|
56
|
+
/** The public key of a seed, worked out by node rather than by reimplementing the curve. */
|
|
57
|
+
function publicKeyFromSeed(seed) {
|
|
58
|
+
if (seed.length !== SEED_LENGTH) {
|
|
59
|
+
throw new Error(`Expected a ${SEED_LENGTH} byte seed, was ${seed.length}`);
|
|
60
|
+
}
|
|
61
|
+
let privateKey = crypto_1.default.createPrivateKey({
|
|
62
|
+
key: Buffer.concat([PKCS8_ED25519_PREFIX, seed]),
|
|
63
|
+
format: "der",
|
|
64
|
+
type: "pkcs8",
|
|
65
|
+
});
|
|
66
|
+
// The spki wrapper is a fixed 12 byte header followed by the raw key.
|
|
67
|
+
return crypto_1.default.createPublicKey(privateKey).export({ format: "der", type: "spki" }).subarray(12);
|
|
68
|
+
}
|
|
69
|
+
exports.publicKeyFromSeed = publicKeyFromSeed;
|
|
70
|
+
function parseOpenSSHPrivateKey(contents) {
|
|
71
|
+
let body = contents.split("\n").filter(line => line.trim() && !line.startsWith("-----")).join("");
|
|
72
|
+
if (!contents.includes(PRIVATE_HEADER)) {
|
|
73
|
+
throw new Error(`Expected an OpenSSH private key starting with ${PRIVATE_HEADER}`);
|
|
74
|
+
}
|
|
75
|
+
let reader = new ByteReader(Buffer.from(body, "base64"));
|
|
76
|
+
if (reader.readBytes(MAGIC.length).toString() !== MAGIC) {
|
|
77
|
+
throw new Error(`Expected the key to start with ${JSON.stringify(MAGIC)}`);
|
|
78
|
+
}
|
|
79
|
+
let cipherName = reader.readString().toString();
|
|
80
|
+
let kdfName = reader.readString().toString();
|
|
81
|
+
reader.readString();
|
|
82
|
+
if (cipherName !== NONE || kdfName !== NONE) {
|
|
83
|
+
throw new Error(`Expected an unencrypted key, was encrypted with ${cipherName}/${kdfName}.`
|
|
84
|
+
+ ` Remove the passphrase with "ssh-keygen -p" first.`);
|
|
85
|
+
}
|
|
86
|
+
let keyCount = reader.readUInt32();
|
|
87
|
+
if (keyCount !== 1) {
|
|
88
|
+
throw new Error(`Expected the key file to hold one key, held ${keyCount}`);
|
|
89
|
+
}
|
|
90
|
+
reader.readString();
|
|
91
|
+
let privateSection = new ByteReader(reader.readString());
|
|
92
|
+
let checkOne = privateSection.readUInt32();
|
|
93
|
+
let checkTwo = privateSection.readUInt32();
|
|
94
|
+
if (checkOne !== checkTwo) {
|
|
95
|
+
throw new Error(`Expected the key's two check values to match, were ${checkOne} and ${checkTwo}`);
|
|
96
|
+
}
|
|
97
|
+
let keyType = privateSection.readString().toString();
|
|
98
|
+
if (keyType !== KEY_TYPE) {
|
|
99
|
+
throw new Error(`Expected an ${KEY_TYPE} key, was ${keyType}`);
|
|
100
|
+
}
|
|
101
|
+
privateSection.readString();
|
|
102
|
+
// OpenSSH stores the seed and the public key together in the private field.
|
|
103
|
+
let privateValue = privateSection.readString();
|
|
104
|
+
if (privateValue.length !== SEED_LENGTH + PUBLIC_LENGTH) {
|
|
105
|
+
throw new Error(`Expected a ${SEED_LENGTH + PUBLIC_LENGTH} byte private value, was ${privateValue.length}`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
seed: privateValue.subarray(0, SEED_LENGTH),
|
|
109
|
+
publicKey: privateValue.subarray(SEED_LENGTH),
|
|
110
|
+
comment: privateSection.readString().toString(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
exports.parseOpenSSHPrivateKey = parseOpenSSHPrivateKey;
|
|
114
|
+
function formatOpenSSHPrivateKey(config) {
|
|
115
|
+
let { seed, comment } = config;
|
|
116
|
+
let publicKey = publicKeyFromSeed(seed);
|
|
117
|
+
let publicBlob = Buffer.concat([writeString(KEY_TYPE), writeString(publicKey)]);
|
|
118
|
+
// The check value only proves a passphrase decrypted correctly, and nothing here is encrypted.
|
|
119
|
+
// Taking it from the seed keeps the same input producing the same file, so deriving a key twice
|
|
120
|
+
// is something you can compare rather than something that looks different every time.
|
|
121
|
+
let check = crypto_1.default.createHash("sha256").update(seed).digest().readUInt32BE(0);
|
|
122
|
+
let privateSection = Buffer.concat([
|
|
123
|
+
writeUInt32(check),
|
|
124
|
+
writeUInt32(check),
|
|
125
|
+
writeString(KEY_TYPE),
|
|
126
|
+
writeString(publicKey),
|
|
127
|
+
writeString(Buffer.concat([seed, publicKey])),
|
|
128
|
+
writeString(comment),
|
|
129
|
+
]);
|
|
130
|
+
let paddingLength = (PADDING_BLOCK - privateSection.length % PADDING_BLOCK) % PADDING_BLOCK;
|
|
131
|
+
let padding = Buffer.from(Array.from({ length: paddingLength }, (unused, index) => index + 1));
|
|
132
|
+
let contents = Buffer.concat([
|
|
133
|
+
Buffer.from(MAGIC),
|
|
134
|
+
writeString(NONE),
|
|
135
|
+
writeString(NONE),
|
|
136
|
+
writeString(""),
|
|
137
|
+
writeUInt32(1),
|
|
138
|
+
writeString(publicBlob),
|
|
139
|
+
writeString(Buffer.concat([privateSection, padding])),
|
|
140
|
+
]);
|
|
141
|
+
let encoded = contents.toString("base64").match(new RegExp(`.{1,${PEM_LINE_LENGTH}}`, "g")) || [];
|
|
142
|
+
return `${PRIVATE_HEADER}\n${encoded.join("\n")}\n${PRIVATE_FOOTER}\n`;
|
|
143
|
+
}
|
|
144
|
+
exports.formatOpenSSHPrivateKey = formatOpenSSHPrivateKey;
|
|
145
|
+
/** The single line form, as it appears in authorized_keys and in a .pub file. */
|
|
146
|
+
function formatPublicKeyLine(config) {
|
|
147
|
+
let { publicKey, comment } = config;
|
|
148
|
+
let blob = Buffer.concat([writeString(KEY_TYPE), writeString(publicKey)]);
|
|
149
|
+
return `${KEY_TYPE} ${blob.toString("base64")}${comment && ` ${comment}` || ""}\n`;
|
|
150
|
+
}
|
|
151
|
+
exports.formatPublicKeyLine = formatPublicKeyLine;
|
|
152
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NoS2V5RmlsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNzaEtleUZpbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsb0RBQTRCO0FBRTVCLGlHQUFpRztBQUNqRyxtR0FBbUc7QUFDbkcsK0JBQStCO0FBRS9CLE1BQU0sS0FBSyxHQUFHLGtCQUFrQixDQUFDO0FBQ2pDLE1BQU0sUUFBUSxHQUFHLGFBQWEsQ0FBQztBQUMvQixNQUFNLElBQUksR0FBRyxNQUFNLENBQUM7QUFDcEIsTUFBTSxXQUFXLEdBQUcsRUFBRSxDQUFDO0FBQ3ZCLE1BQU0sYUFBYSxHQUFHLEVBQUUsQ0FBQztBQUN6QixvRUFBb0U7QUFDcEUsTUFBTSxhQUFhLEdBQUcsQ0FBQyxDQUFDO0FBQ3hCLE1BQU0sZUFBZSxHQUFHLEVBQUUsQ0FBQztBQUMzQixNQUFNLGNBQWMsR0FBRyxxQ0FBcUMsQ0FBQztBQUM3RCxNQUFNLGNBQWMsR0FBRyxtQ0FBbUMsQ0FBQztBQUMzRCx1RkFBdUY7QUFDdkYsTUFBTSxvQkFBb0IsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLGtDQUFrQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRXBGLE1BQU0sVUFBVTtJQUVaLFlBQW9CLE1BQWM7UUFBZCxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBRDFCLFdBQU0sR0FBRyxDQUFDLENBQUM7SUFDbUIsQ0FBQztJQUV2QyxTQUFTLENBQUMsTUFBYztRQUNwQixJQUFJLEtBQUssR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLENBQUM7UUFDcEUsSUFBSSxLQUFLLENBQUMsTUFBTSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQzFCLE1BQU0sSUFBSSxLQUFLLENBQUMsWUFBWSxNQUFNLG9CQUFvQixJQUFJLENBQUMsTUFBTSxTQUFTLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQzlGLENBQUM7UUFDRCxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQztRQUN0QixPQUFPLEtBQUssQ0FBQztJQUNqQixDQUFDO0lBRUQsVUFBVTtRQUNOLElBQUksS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsRCxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQztRQUNqQixPQUFPLEtBQUssQ0FBQztJQUNqQixDQUFDO0lBRUQsVUFBVTtRQUNOLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUM3QyxDQUFDO0NBQ0o7QUFFRCxTQUFTLFdBQVcsQ0FBQyxLQUFzQjtJQUN2QyxJQUFJLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM3RSxJQUFJLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdCLE1BQU0sQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3RDLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO0FBQzdDLENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxLQUFhO0lBQzlCLElBQUksS0FBSyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDNUIsS0FBSyxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMzQixPQUFPLEtBQUssQ0FBQztBQUNqQixDQUFDO0FBRUQsNEZBQTRGO0FBQzVGLFNBQWdCLGlCQUFpQixDQUFDLElBQVk7SUFDMUMsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFdBQVcsRUFBRSxDQUFDO1FBQzlCLE1BQU0sSUFBSSxLQUFLLENBQUMsY0FBYyxXQUFXLG1CQUFtQixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUMvRSxDQUFDO0lBQ0QsSUFBSSxVQUFVLEdBQUcsZ0JBQU0sQ0FBQyxnQkFBZ0IsQ0FBQztRQUNyQyxHQUFHLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLG9CQUFvQixFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ2hELE1BQU0sRUFBRSxLQUFLO1FBQ2IsSUFBSSxFQUFFLE9BQU87S0FDaEIsQ0FBQyxDQUFDO0lBQ0gsc0VBQXNFO0lBQ3RFLE9BQU8sZ0JBQU0sQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDbkcsQ0FBQztBQVhELDhDQVdDO0FBRUQsU0FBZ0Isc0JBQXNCLENBQUMsUUFBZ0I7SUFDbkQsSUFBSSxJQUFJLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2xHLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7UUFDckMsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUN2RixDQUFDO0lBQ0QsSUFBSSxNQUFNLEdBQUcsSUFBSSxVQUFVLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztJQUN6RCxJQUFJLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLFFBQVEsRUFBRSxLQUFLLEtBQUssRUFBRSxDQUFDO1FBQ3RELE1BQU0sSUFBSSxLQUFLLENBQUMsa0NBQWtDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQy9FLENBQUM7SUFDRCxJQUFJLFVBQVUsR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDaEQsSUFBSSxPQUFPLEdBQUcsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzdDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUNwQixJQUFJLFVBQVUsS0FBSyxJQUFJLElBQUksT0FBTyxLQUFLLElBQUksRUFBRSxDQUFDO1FBQzFDLE1BQU0sSUFBSSxLQUFLLENBQ1gsbURBQW1ELFVBQVUsSUFBSSxPQUFPLEdBQUc7Y0FDekUsb0RBQW9ELENBQ3pELENBQUM7SUFDTixDQUFDO0lBQ0QsSUFBSSxRQUFRLEdBQUcsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ25DLElBQUksUUFBUSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDL0UsQ0FBQztJQUNELE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUVwQixJQUFJLGNBQWMsR0FBRyxJQUFJLFVBQVUsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUN6RCxJQUFJLFFBQVEsR0FBRyxjQUFjLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDM0MsSUFBSSxRQUFRLEdBQUcsY0FBYyxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzNDLElBQUksUUFBUSxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0RBQXNELFFBQVEsUUFBUSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ3RHLENBQUM7SUFDRCxJQUFJLE9BQU8sR0FBRyxjQUFjLENBQUMsVUFBVSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDckQsSUFBSSxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDdkIsTUFBTSxJQUFJLEtBQUssQ0FBQyxlQUFlLFFBQVEsYUFBYSxPQUFPLEVBQUUsQ0FBQyxDQUFDO0lBQ25FLENBQUM7SUFDRCxjQUFjLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDNUIsNEVBQTRFO0lBQzVFLElBQUksWUFBWSxHQUFHLGNBQWMsQ0FBQyxVQUFVLEVBQUUsQ0FBQztJQUMvQyxJQUFJLFlBQVksQ0FBQyxNQUFNLEtBQUssV0FBVyxHQUFHLGFBQWEsRUFBRSxDQUFDO1FBQ3RELE1BQU0sSUFBSSxLQUFLLENBQUMsY0FBYyxXQUFXLEdBQUcsYUFBYSw0QkFBNEIsWUFBWSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDaEgsQ0FBQztJQUNELE9BQU87UUFDSCxJQUFJLEVBQUUsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsV0FBVyxDQUFDO1FBQzNDLFNBQVMsRUFBRSxZQUFZLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQztRQUM3QyxPQUFPLEVBQUUsY0FBYyxDQUFDLFVBQVUsRUFBRSxDQUFDLFFBQVEsRUFBRTtLQUNsRCxDQUFDO0FBQ04sQ0FBQztBQTdDRCx3REE2Q0M7QUFFRCxTQUFnQix1QkFBdUIsQ0FBQyxNQUF5QztJQUM3RSxJQUFJLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUMvQixJQUFJLFNBQVMsR0FBRyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN4QyxJQUFJLFVBQVUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxFQUFFLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFaEYsK0ZBQStGO0lBQy9GLGdHQUFnRztJQUNoRyxzRkFBc0Y7SUFDdEYsSUFBSSxLQUFLLEdBQUcsZ0JBQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5RSxJQUFJLGNBQWMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQy9CLFdBQVcsQ0FBQyxLQUFLLENBQUM7UUFDbEIsV0FBVyxDQUFDLEtBQUssQ0FBQztRQUNsQixXQUFXLENBQUMsUUFBUSxDQUFDO1FBQ3JCLFdBQVcsQ0FBQyxTQUFTLENBQUM7UUFDdEIsV0FBVyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQztRQUM3QyxXQUFXLENBQUMsT0FBTyxDQUFDO0tBQ3ZCLENBQUMsQ0FBQztJQUNILElBQUksYUFBYSxHQUFHLENBQUMsYUFBYSxHQUFHLGNBQWMsQ0FBQyxNQUFNLEdBQUcsYUFBYSxDQUFDLEdBQUcsYUFBYSxDQUFDO0lBQzVGLElBQUksT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxhQUFhLEVBQUUsRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRS9GLElBQUksUUFBUSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDbEIsV0FBVyxDQUFDLElBQUksQ0FBQztRQUNqQixXQUFXLENBQUMsSUFBSSxDQUFDO1FBQ2pCLFdBQVcsQ0FBQyxFQUFFLENBQUM7UUFDZixXQUFXLENBQUMsQ0FBQyxDQUFDO1FBQ2QsV0FBVyxDQUFDLFVBQVUsQ0FBQztRQUN2QixXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLGNBQWMsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0tBQ3hELENBQUMsQ0FBQztJQUNILElBQUksT0FBTyxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sZUFBZSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDbEcsT0FBTyxHQUFHLGNBQWMsS0FBSyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLGNBQWMsSUFBSSxDQUFDO0FBQzNFLENBQUM7QUEvQkQsMERBK0JDO0FBRUQsaUZBQWlGO0FBQ2pGLFNBQWdCLG1CQUFtQixDQUFDLE1BQThDO0lBQzlFLElBQUksRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxDQUFDO0lBQ3BDLElBQUksSUFBSSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLEVBQUUsV0FBVyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxRSxPQUFPLEdBQUcsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsT0FBTyxJQUFJLElBQUksT0FBTyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7QUFDdkYsQ0FBQztBQUpELGtEQUlDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNyeXB0byBmcm9tIFwiY3J5cHRvXCI7XG5cbi8vIE9wZW5TU0gga2VlcHMgcHJpdmF0ZSBrZXlzIGluIGl0cyBvd24gY29udGFpbmVyIHJhdGhlciB0aGFuIGFueXRoaW5nIG5vZGUgY2FuIHJlYWQsIHNvIHRoZSBmZXdcbi8vIGJ5dGVzIG9mIGl0IHdlIG5lZWQgYXJlIGhhbmRsZWQgaGVyZS4gT25seSB1bmVuY3J5cHRlZCBlZDI1NTE5IGtleXMgYXJlIHN1cHBvcnRlZCwgd2hpY2ggaXMgd2hhdFxuLy8gYSBrZXkgdXNlZCBieSBhdXRvbWF0aW9uIGlzLlxuXG5jb25zdCBNQUdJQyA9IFwib3BlbnNzaC1rZXktdjFcXDBcIjtcbmNvbnN0IEtFWV9UWVBFID0gXCJzc2gtZWQyNTUxOVwiO1xuY29uc3QgTk9ORSA9IFwibm9uZVwiO1xuY29uc3QgU0VFRF9MRU5HVEggPSAzMjtcbmNvbnN0IFBVQkxJQ19MRU5HVEggPSAzMjtcbi8vIFRoZSBvbmUgYmxvY2sgc2l6ZSB0aGF0IG1hdHRlcnMgaGVyZSwgc2luY2Ugbm90aGluZyBpcyBlbmNyeXB0ZWQuXG5jb25zdCBQQURESU5HX0JMT0NLID0gODtcbmNvbnN0IFBFTV9MSU5FX0xFTkdUSCA9IDcwO1xuY29uc3QgUFJJVkFURV9IRUFERVIgPSBcIi0tLS0tQkVHSU4gT1BFTlNTSCBQUklWQVRFIEtFWS0tLS0tXCI7XG5jb25zdCBQUklWQVRFX0ZPT1RFUiA9IFwiLS0tLS1FTkQgT1BFTlNTSCBQUklWQVRFIEtFWS0tLS0tXCI7XG4vLyBUaGUgcHJlZml4IHRoYXQgdHVybnMgYSBiYXJlIDMyIGJ5dGUgc2VlZCBpbnRvIGEgcGtjczggZWQyNTUxOSBrZXkgbm9kZSB3aWxsIGFjY2VwdC5cbmNvbnN0IFBLQ1M4X0VEMjU1MTlfUFJFRklYID0gQnVmZmVyLmZyb20oXCIzMDJlMDIwMTAwMzAwNTA2MDMyYjY1NzAwNDIyMDQyMFwiLCBcImhleFwiKTtcblxuY2xhc3MgQnl0ZVJlYWRlciB7XG4gICAgcHJpdmF0ZSBvZmZzZXQgPSAwO1xuICAgIGNvbnN0cnVjdG9yKHByaXZhdGUgYnVmZmVyOiBCdWZmZXIpIHsgfVxuXG4gICAgcmVhZEJ5dGVzKGxlbmd0aDogbnVtYmVyKSB7XG4gICAgICAgIGxldCB2YWx1ZSA9IHRoaXMuYnVmZmVyLnN1YmFycmF5KHRoaXMub2Zmc2V0LCB0aGlzLm9mZnNldCArIGxlbmd0aCk7XG4gICAgICAgIGlmICh2YWx1ZS5sZW5ndGggIT09IGxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCAke2xlbmd0aH0gYnl0ZXMgYXQgb2Zmc2V0ICR7dGhpcy5vZmZzZXR9LCB3YXMgJHt2YWx1ZS5sZW5ndGh9YCk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5vZmZzZXQgKz0gbGVuZ3RoO1xuICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgfVxuXG4gICAgcmVhZFVJbnQzMigpIHtcbiAgICAgICAgbGV0IHZhbHVlID0gdGhpcy5idWZmZXIucmVhZFVJbnQzMkJFKHRoaXMub2Zmc2V0KTtcbiAgICAgICAgdGhpcy5vZmZzZXQgKz0gNDtcbiAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgIH1cblxuICAgIHJlYWRTdHJpbmcoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlYWRCeXRlcyh0aGlzLnJlYWRVSW50MzIoKSk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiB3cml0ZVN0cmluZyh2YWx1ZTogQnVmZmVyIHwgc3RyaW5nKSB7XG4gICAgbGV0IGNvbnRlbnRzID0gQnVmZmVyLmlzQnVmZmVyKHZhbHVlKSAmJiB2YWx1ZSB8fCBCdWZmZXIuZnJvbSh2YWx1ZSwgXCJ1dGY4XCIpO1xuICAgIGxldCBsZW5ndGggPSBCdWZmZXIuYWxsb2MoNCk7XG4gICAgbGVuZ3RoLndyaXRlVUludDMyQkUoY29udGVudHMubGVuZ3RoKTtcbiAgICByZXR1cm4gQnVmZmVyLmNvbmNhdChbbGVuZ3RoLCBjb250ZW50c10pO1xufVxuXG5mdW5jdGlvbiB3cml0ZVVJbnQzMih2YWx1ZTogbnVtYmVyKSB7XG4gICAgbGV0IGJ5dGVzID0gQnVmZmVyLmFsbG9jKDQpO1xuICAgIGJ5dGVzLndyaXRlVUludDMyQkUodmFsdWUpO1xuICAgIHJldHVybiBieXRlcztcbn1cblxuLyoqIFRoZSBwdWJsaWMga2V5IG9mIGEgc2VlZCwgd29ya2VkIG91dCBieSBub2RlIHJhdGhlciB0aGFuIGJ5IHJlaW1wbGVtZW50aW5nIHRoZSBjdXJ2ZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwdWJsaWNLZXlGcm9tU2VlZChzZWVkOiBCdWZmZXIpIHtcbiAgICBpZiAoc2VlZC5sZW5ndGggIT09IFNFRURfTEVOR1RIKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSAke1NFRURfTEVOR1RIfSBieXRlIHNlZWQsIHdhcyAke3NlZWQubGVuZ3RofWApO1xuICAgIH1cbiAgICBsZXQgcHJpdmF0ZUtleSA9IGNyeXB0by5jcmVhdGVQcml2YXRlS2V5KHtcbiAgICAgICAga2V5OiBCdWZmZXIuY29uY2F0KFtQS0NTOF9FRDI1NTE5X1BSRUZJWCwgc2VlZF0pLFxuICAgICAgICBmb3JtYXQ6IFwiZGVyXCIsXG4gICAgICAgIHR5cGU6IFwicGtjczhcIixcbiAgICB9KTtcbiAgICAvLyBUaGUgc3BraSB3cmFwcGVyIGlzIGEgZml4ZWQgMTIgYnl0ZSBoZWFkZXIgZm9sbG93ZWQgYnkgdGhlIHJhdyBrZXkuXG4gICAgcmV0dXJuIGNyeXB0by5jcmVhdGVQdWJsaWNLZXkocHJpdmF0ZUtleSkuZXhwb3J0KHsgZm9ybWF0OiBcImRlclwiLCB0eXBlOiBcInNwa2lcIiB9KS5zdWJhcnJheSgxMik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZU9wZW5TU0hQcml2YXRlS2V5KGNvbnRlbnRzOiBzdHJpbmcpIHtcbiAgICBsZXQgYm9keSA9IGNvbnRlbnRzLnNwbGl0KFwiXFxuXCIpLmZpbHRlcihsaW5lID0+IGxpbmUudHJpbSgpICYmICFsaW5lLnN0YXJ0c1dpdGgoXCItLS0tLVwiKSkuam9pbihcIlwiKTtcbiAgICBpZiAoIWNvbnRlbnRzLmluY2x1ZGVzKFBSSVZBVEVfSEVBREVSKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGFuIE9wZW5TU0ggcHJpdmF0ZSBrZXkgc3RhcnRpbmcgd2l0aCAke1BSSVZBVEVfSEVBREVSfWApO1xuICAgIH1cbiAgICBsZXQgcmVhZGVyID0gbmV3IEJ5dGVSZWFkZXIoQnVmZmVyLmZyb20oYm9keSwgXCJiYXNlNjRcIikpO1xuICAgIGlmIChyZWFkZXIucmVhZEJ5dGVzKE1BR0lDLmxlbmd0aCkudG9TdHJpbmcoKSAhPT0gTUFHSUMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCB0aGUga2V5IHRvIHN0YXJ0IHdpdGggJHtKU09OLnN0cmluZ2lmeShNQUdJQyl9YCk7XG4gICAgfVxuICAgIGxldCBjaXBoZXJOYW1lID0gcmVhZGVyLnJlYWRTdHJpbmcoKS50b1N0cmluZygpO1xuICAgIGxldCBrZGZOYW1lID0gcmVhZGVyLnJlYWRTdHJpbmcoKS50b1N0cmluZygpO1xuICAgIHJlYWRlci5yZWFkU3RyaW5nKCk7XG4gICAgaWYgKGNpcGhlck5hbWUgIT09IE5PTkUgfHwga2RmTmFtZSAhPT0gTk9ORSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgYW4gdW5lbmNyeXB0ZWQga2V5LCB3YXMgZW5jcnlwdGVkIHdpdGggJHtjaXBoZXJOYW1lfS8ke2tkZk5hbWV9LmBcbiAgICAgICAgICAgICsgYCBSZW1vdmUgdGhlIHBhc3NwaHJhc2Ugd2l0aCBcInNzaC1rZXlnZW4gLXBcIiBmaXJzdC5gXG4gICAgICAgICk7XG4gICAgfVxuICAgIGxldCBrZXlDb3VudCA9IHJlYWRlci5yZWFkVUludDMyKCk7XG4gICAgaWYgKGtleUNvdW50ICE9PSAxKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgdGhlIGtleSBmaWxlIHRvIGhvbGQgb25lIGtleSwgaGVsZCAke2tleUNvdW50fWApO1xuICAgIH1cbiAgICByZWFkZXIucmVhZFN0cmluZygpO1xuXG4gICAgbGV0IHByaXZhdGVTZWN0aW9uID0gbmV3IEJ5dGVSZWFkZXIocmVhZGVyLnJlYWRTdHJpbmcoKSk7XG4gICAgbGV0IGNoZWNrT25lID0gcHJpdmF0ZVNlY3Rpb24ucmVhZFVJbnQzMigpO1xuICAgIGxldCBjaGVja1R3byA9IHByaXZhdGVTZWN0aW9uLnJlYWRVSW50MzIoKTtcbiAgICBpZiAoY2hlY2tPbmUgIT09IGNoZWNrVHdvKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgdGhlIGtleSdzIHR3byBjaGVjayB2YWx1ZXMgdG8gbWF0Y2gsIHdlcmUgJHtjaGVja09uZX0gYW5kICR7Y2hlY2tUd299YCk7XG4gICAgfVxuICAgIGxldCBrZXlUeXBlID0gcHJpdmF0ZVNlY3Rpb24ucmVhZFN0cmluZygpLnRvU3RyaW5nKCk7XG4gICAgaWYgKGtleVR5cGUgIT09IEtFWV9UWVBFKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYW4gJHtLRVlfVFlQRX0ga2V5LCB3YXMgJHtrZXlUeXBlfWApO1xuICAgIH1cbiAgICBwcml2YXRlU2VjdGlvbi5yZWFkU3RyaW5nKCk7XG4gICAgLy8gT3BlblNTSCBzdG9yZXMgdGhlIHNlZWQgYW5kIHRoZSBwdWJsaWMga2V5IHRvZ2V0aGVyIGluIHRoZSBwcml2YXRlIGZpZWxkLlxuICAgIGxldCBwcml2YXRlVmFsdWUgPSBwcml2YXRlU2VjdGlvbi5yZWFkU3RyaW5nKCk7XG4gICAgaWYgKHByaXZhdGVWYWx1ZS5sZW5ndGggIT09IFNFRURfTEVOR1RIICsgUFVCTElDX0xFTkdUSCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgJHtTRUVEX0xFTkdUSCArIFBVQkxJQ19MRU5HVEh9IGJ5dGUgcHJpdmF0ZSB2YWx1ZSwgd2FzICR7cHJpdmF0ZVZhbHVlLmxlbmd0aH1gKTtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgICAgc2VlZDogcHJpdmF0ZVZhbHVlLnN1YmFycmF5KDAsIFNFRURfTEVOR1RIKSxcbiAgICAgICAgcHVibGljS2V5OiBwcml2YXRlVmFsdWUuc3ViYXJyYXkoU0VFRF9MRU5HVEgpLFxuICAgICAgICBjb21tZW50OiBwcml2YXRlU2VjdGlvbi5yZWFkU3RyaW5nKCkudG9TdHJpbmcoKSxcbiAgICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0T3BlblNTSFByaXZhdGVLZXkoY29uZmlnOiB7IHNlZWQ6IEJ1ZmZlcjsgY29tbWVudDogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBzZWVkLCBjb21tZW50IH0gPSBjb25maWc7XG4gICAgbGV0IHB1YmxpY0tleSA9IHB1YmxpY0tleUZyb21TZWVkKHNlZWQpO1xuICAgIGxldCBwdWJsaWNCbG9iID0gQnVmZmVyLmNvbmNhdChbd3JpdGVTdHJpbmcoS0VZX1RZUEUpLCB3cml0ZVN0cmluZyhwdWJsaWNLZXkpXSk7XG5cbiAgICAvLyBUaGUgY2hlY2sgdmFsdWUgb25seSBwcm92ZXMgYSBwYXNzcGhyYXNlIGRlY3J5cHRlZCBjb3JyZWN0bHksIGFuZCBub3RoaW5nIGhlcmUgaXMgZW5jcnlwdGVkLlxuICAgIC8vIFRha2luZyBpdCBmcm9tIHRoZSBzZWVkIGtlZXBzIHRoZSBzYW1lIGlucHV0IHByb2R1Y2luZyB0aGUgc2FtZSBmaWxlLCBzbyBkZXJpdmluZyBhIGtleSB0d2ljZVxuICAgIC8vIGlzIHNvbWV0aGluZyB5b3UgY2FuIGNvbXBhcmUgcmF0aGVyIHRoYW4gc29tZXRoaW5nIHRoYXQgbG9va3MgZGlmZmVyZW50IGV2ZXJ5IHRpbWUuXG4gICAgbGV0IGNoZWNrID0gY3J5cHRvLmNyZWF0ZUhhc2goXCJzaGEyNTZcIikudXBkYXRlKHNlZWQpLmRpZ2VzdCgpLnJlYWRVSW50MzJCRSgwKTtcbiAgICBsZXQgcHJpdmF0ZVNlY3Rpb24gPSBCdWZmZXIuY29uY2F0KFtcbiAgICAgICAgd3JpdGVVSW50MzIoY2hlY2spLFxuICAgICAgICB3cml0ZVVJbnQzMihjaGVjayksXG4gICAgICAgIHdyaXRlU3RyaW5nKEtFWV9UWVBFKSxcbiAgICAgICAgd3JpdGVTdHJpbmcocHVibGljS2V5KSxcbiAgICAgICAgd3JpdGVTdHJpbmcoQnVmZmVyLmNvbmNhdChbc2VlZCwgcHVibGljS2V5XSkpLFxuICAgICAgICB3cml0ZVN0cmluZyhjb21tZW50KSxcbiAgICBdKTtcbiAgICBsZXQgcGFkZGluZ0xlbmd0aCA9IChQQURESU5HX0JMT0NLIC0gcHJpdmF0ZVNlY3Rpb24ubGVuZ3RoICUgUEFERElOR19CTE9DSykgJSBQQURESU5HX0JMT0NLO1xuICAgIGxldCBwYWRkaW5nID0gQnVmZmVyLmZyb20oQXJyYXkuZnJvbSh7IGxlbmd0aDogcGFkZGluZ0xlbmd0aCB9LCAodW51c2VkLCBpbmRleCkgPT4gaW5kZXggKyAxKSk7XG5cbiAgICBsZXQgY29udGVudHMgPSBCdWZmZXIuY29uY2F0KFtcbiAgICAgICAgQnVmZmVyLmZyb20oTUFHSUMpLFxuICAgICAgICB3cml0ZVN0cmluZyhOT05FKSxcbiAgICAgICAgd3JpdGVTdHJpbmcoTk9ORSksXG4gICAgICAgIHdyaXRlU3RyaW5nKFwiXCIpLFxuICAgICAgICB3cml0ZVVJbnQzMigxKSxcbiAgICAgICAgd3JpdGVTdHJpbmcocHVibGljQmxvYiksXG4gICAgICAgIHdyaXRlU3RyaW5nKEJ1ZmZlci5jb25jYXQoW3ByaXZhdGVTZWN0aW9uLCBwYWRkaW5nXSkpLFxuICAgIF0pO1xuICAgIGxldCBlbmNvZGVkID0gY29udGVudHMudG9TdHJpbmcoXCJiYXNlNjRcIikubWF0Y2gobmV3IFJlZ0V4cChgLnsxLCR7UEVNX0xJTkVfTEVOR1RIfX1gLCBcImdcIikpIHx8IFtdO1xuICAgIHJldHVybiBgJHtQUklWQVRFX0hFQURFUn1cXG4ke2VuY29kZWQuam9pbihcIlxcblwiKX1cXG4ke1BSSVZBVEVfRk9PVEVSfVxcbmA7XG59XG5cbi8qKiBUaGUgc2luZ2xlIGxpbmUgZm9ybSwgYXMgaXQgYXBwZWFycyBpbiBhdXRob3JpemVkX2tleXMgYW5kIGluIGEgLnB1YiBmaWxlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFB1YmxpY0tleUxpbmUoY29uZmlnOiB7IHB1YmxpY0tleTogQnVmZmVyOyBjb21tZW50OiBzdHJpbmcgfSkge1xuICAgIGxldCB7IHB1YmxpY0tleSwgY29tbWVudCB9ID0gY29uZmlnO1xuICAgIGxldCBibG9iID0gQnVmZmVyLmNvbmNhdChbd3JpdGVTdHJpbmcoS0VZX1RZUEUpLCB3cml0ZVN0cmluZyhwdWJsaWNLZXkpXSk7XG4gICAgcmV0dXJuIGAke0tFWV9UWVBFfSAke2Jsb2IudG9TdHJpbmcoXCJiYXNlNjRcIil9JHtjb21tZW50ICYmIGAgJHtjb21tZW50fWAgfHwgXCJcIn1cXG5gO1xufVxuIl19
|
|
153
|
+
/* _JS_SOURCE_HASH = "57e4d1fd38c49a01bb35cf57a53a29f28eb2053b81167b87b4c17bab13251fae"; */
|