UNPKG

35.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const fs_1 = require("fs");
5const child_process_1 = require("child_process");
6const http = require("http");
7const path = require("path");
8const getPort = require("get-port");
9const createDebug = require("debug");
10const command_exists_1 = require("command-exists");
11const glob = require("glob");
12const eol = require("eol");
13const constants_1 = require("./constants");
14const utils_1 = require("./utils");
15const debug = createDebug('devcert');
16// Install the once-per-machine trusted root CA. We'll use this CA to sign per-app certs, allowing
17// us to minimize the need for elevated permissions while still allowing for per-app certificates.
18function installCertificateAuthority(installCertutil) {
19 return tslib_1.__awaiter(this, void 0, void 0, function* () {
20 debug(`generating openssl configuration`);
21 generateOpenSSLConfFiles();
22 debug(`generating root certificate authority key`);
23 utils_1.generateKey(constants_1.rootKeyPath);
24 debug(`generating root certificate authority certificate`);
25 utils_1.openssl(`req -config ${constants_1.opensslConfPath} -key ${constants_1.rootKeyPath} -out ${constants_1.rootCertPath} -new -subj "/CN=devcert" -x509 -days 7000 -extensions v3_ca`);
26 debug(`adding root certificate authority to trust stores`);
27 if (constants_1.isMac) {
28 yield addToMacTrustStores(installCertutil);
29 }
30 else if (constants_1.isLinux) {
31 yield addToLinuxTrustStores(installCertutil);
32 }
33 else {
34 yield addToWindowsTrustStores();
35 }
36 });
37}
38exports.default = installCertificateAuthority;
39// Copy our openssl conf template to the local config folder, and update the paths to be OS
40// specific. Also initializes the files openssl needs to sign certificates as a certificate
41// authority
42function generateOpenSSLConfFiles() {
43 let confTemplate = fs_1.readFileSync(constants_1.opensslConfTemplate, 'utf-8');
44 confTemplate = confTemplate.replace(/DATABASE_PATH/, constants_1.configPath('index.txt').replace(/\\/g, '\\\\'));
45 confTemplate = confTemplate.replace(/SERIAL_PATH/, constants_1.configPath('serial').replace(/\\/g, '\\\\'));
46 confTemplate = eol.auto(confTemplate);
47 fs_1.writeFileSync(constants_1.opensslConfPath, confTemplate);
48 fs_1.writeFileSync(constants_1.configPath('index.txt'), '');
49 fs_1.writeFileSync(constants_1.configPath('serial'), '01');
50 // This version number lets us write code in the future that intelligently upgrades an existing
51 // devcert installation. This "ca-version" is independent of the devcert package version, and
52 // tracks changes to the root certificate setup only.
53 fs_1.writeFileSync(constants_1.configPath('devcert-ca-version'), '1');
54}
55// macOS is pretty simple - just add the certificate to the system keychain, and most applications
56// will delegate to that for determining trusted certificates. Firefox, of course, does it's own
57// thing. We can try to automatically install the cert with Firefox if we can use certutil via the
58// `nss` Homebrew package, otherwise we go manual with user-facing prompts.
59function addToMacTrustStores(installCertutil) {
60 return tslib_1.__awaiter(this, void 0, void 0, function* () {
61 // Chrome, Safari, system utils
62 debug('adding devcert root CA to macOS system keychain');
63 utils_1.run(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain -p ssl -p basic "${constants_1.rootCertPath}"`);
64 // Firefox
65 try {
66 // Try to use certutil to install the cert automatically
67 debug('adding devcert root CA to firefox');
68 yield addCertificateToNSSCertDB(path.join(process.env.HOME, 'Library/Application Support/Firefox/Profiles/*'), {
69 installCertutil,
70 checkForOpenFirefox: true
71 });
72 }
73 catch (e) {
74 // Otherwise, open the cert in Firefox to install it
75 yield openCertificateInFirefox('/Applications/Firefox.app/Contents/MacOS/firefox');
76 }
77 });
78}
79// Linux is surprisingly difficult. There seems to be multiple system-wide repositories for certs,
80// so we copy ours to each. However, Firefox does it's usual separate trust store. Plus Chrome
81// relies on the NSS tooling (like Firefox), but uses the user's NSS database, unlike Firefox which
82// uses a separate Mozilla one. And since Chrome doesn't prompt the user with a GUI flow when
83// opening certs, if we can't use certutil, we're out of luck.
84function addToLinuxTrustStores(installCertutil) {
85 return tslib_1.__awaiter(this, void 0, void 0, function* () {
86 // system utils
87 debug('adding devcert root CA to linux system-wide certificates');
88 utils_1.run(`sudo cp ${constants_1.rootCertPath} /etc/ssl/certs/devcert.pem`);
89 utils_1.run(`sudo cp ${constants_1.rootCertPath} /usr/local/share/ca-certificates/devcert.cer`);
90 utils_1.run(`sudo update-ca-certificates`);
91 // Firefox
92 try {
93 // Try to use certutil to install the cert automatically
94 debug('adding devcert root CA to firefox');
95 yield addCertificateToNSSCertDB(path.join(process.env.HOME, '.mozilla/firefox/*'), {
96 installCertutil,
97 checkForOpenFirefox: true
98 });
99 }
100 catch (e) {
101 // Otherwise, open the cert in Firefox to install it
102 yield openCertificateInFirefox('firefox');
103 }
104 // Chrome
105 try {
106 debug('adding devcert root CA to chrome');
107 yield addCertificateToNSSCertDB(path.join(process.env.HOME, '.pki/nssdb'), { installCertutil });
108 }
109 catch (e) {
110 console.warn(`
111WARNING: Because you did not pass in \`installCertutil: true\` to devcert, we
112are unable to update Chrome to automatically trust generated development
113certificates. The certificates will work, but Chrome will continue to warn you
114that they are untrusted.`);
115 }
116 });
117}
118// Windows is at least simple. Like macOS, most applications will delegate to the system trust
119// store, which is updated with the confusingly named `certutil` exe (not the same as the
120// NSS/Mozilla certutil). Firefox does it's own thing as usual, and getting a copy of NSS certutil
121// onto the Windows machine to try updating the Firefox store is basically a nightmare, so we don't
122// even try it - we just bail out to the GUI.
123function addToWindowsTrustStores() {
124 return tslib_1.__awaiter(this, void 0, void 0, function* () {
125 // IE, Chrome, system utils
126 debug('adding devcert root to Windows OS trust store');
127 utils_1.run(`certutil -addstore -user root ${constants_1.rootCertPath}`);
128 // Firefox (don't even try NSS certutil, no easy install for Windows)
129 yield openCertificateInFirefox('start firefox');
130 });
131}
132// Given a directory or glob pattern of directories, attempt to install the certificate to each
133// directory containing an NSS database.
134function addCertificateToNSSCertDB(nssDirGlob, options = {}) {
135 return tslib_1.__awaiter(this, void 0, void 0, function* () {
136 let certutilPath = lookupOrInstallCertutil(options.installCertutil);
137 if (!certutilPath) {
138 throw new Error('certutil not available, and `installCertutil` was false');
139 }
140 // Firefox appears to load the NSS database in-memory on startup, and overwrite on exit. So we
141 // have to ask the user to quite Firefox first so our changes don't get overwritten.
142 if (options.checkForOpenFirefox) {
143 let runningProcesses = utils_1.run('ps aux');
144 if (runningProcesses.indexOf('firefox') > -1) {
145 console.log('Please close Firefox before continuing (Press <Enter> when ready)');
146 yield utils_1.waitForUser();
147 }
148 }
149 debug(`trying to install certificate into NSS databases in ${nssDirGlob}`);
150 glob.sync(nssDirGlob).forEach((potentialNSSDBDir) => {
151 debug(`checking to see if ${potentialNSSDBDir} is a valid NSS database directory`);
152 if (fs_1.existsSync(path.join(potentialNSSDBDir, 'cert8.db'))) {
153 debug(`Found legacy NSS database in ${potentialNSSDBDir}, adding devcert ...`);
154 utils_1.run(`${certutilPath} -A -d "${potentialNSSDBDir}" -t 'C,,' -i ${constants_1.rootCertPath} -n devcert`);
155 }
156 else if (fs_1.existsSync(path.join(potentialNSSDBDir, 'cert9.db'))) {
157 debug(`Found modern NSS database in ${potentialNSSDBDir}, adding devcert ...`);
158 utils_1.run(`${certutilPath} -A -d "sql:${potentialNSSDBDir}" -t 'C,,' -i ${constants_1.rootCertPath} -n devcert`);
159 }
160 });
161 });
162}
163// When a Firefox tab is directed to a URL that returns a certificate, it will automatically prompt
164// the user if they want to add it to their trusted certificates. This is handy since Firefox is by
165// far the most troublesome to handle. If we can't automatically install the certificate (because
166// certutil is not available / installable), we instead start a quick web server and host our
167// certificate file. Then we open the hosted cert URL in Firefox to kick off the GUI flow.
168function openCertificateInFirefox(firefoxPath) {
169 return tslib_1.__awaiter(this, void 0, void 0, function* () {
170 debug('adding devert to firefox manually - launch webserver for certificate hosting');
171 let port = yield getPort();
172 let server = http.createServer((req, res) => {
173 res.writeHead(200, { 'Content-type': 'application/x-x509-ca-cert' });
174 res.write(fs_1.readFileSync(constants_1.rootCertPath));
175 res.end();
176 }).listen(port);
177 debug('certificate is hosted, starting firefox at hosted URL');
178 console.log(`Unable to automatically install SSL certificate - please follow the prompts at http://localhost:${port} in Firefox to trust the root certificate`);
179 console.log('See https://github.com/davewasmer/devcert#how-it-works for more details');
180 console.log('-- Press <Enter> once you finish the Firefox prompts --');
181 child_process_1.exec(`${firefoxPath} http://localhost:${port}`);
182 yield utils_1.waitForUser();
183 });
184}
185// Try to install certutil if it's not already available, and return the path to the executable
186function lookupOrInstallCertutil(installCertutil) {
187 debug('looking for nss tooling ...');
188 if (constants_1.isMac) {
189 debug('on mac, looking for homebrew (the only method to install nss that is currently supported by devcert');
190 if (command_exists_1.sync('brew')) {
191 let nssPath;
192 let certutilPath;
193 try {
194 certutilPath = path.join(utils_1.run('brew --prefix nss').toString().trim(), 'bin', 'certutil');
195 }
196 catch (e) {
197 debug('brew was found, but nss is not installed');
198 if (installCertutil) {
199 debug('attempting to install nss via brew');
200 utils_1.run('brew install nss');
201 certutilPath = path.join(utils_1.run('brew --prefix nss').toString().trim(), 'bin', 'certutil');
202 }
203 else {
204 return false;
205 }
206 }
207 debug(`Found nss installed at ${certutilPath}`);
208 return certutilPath;
209 }
210 }
211 else if (constants_1.isLinux) {
212 debug('on linux, checking is nss is already installed');
213 if (!command_exists_1.sync('certutil')) {
214 if (installCertutil) {
215 debug('not already installed, installing it ourselves');
216 utils_1.run('sudo apt install libnss3-tools');
217 }
218 else {
219 debug('not installed and do not want to install');
220 return false;
221 }
222 }
223 debug('looks like nss is installed');
224 return utils_1.run('which certutil').toString().trim();
225 }
226 // Windows? Ha!
227 return false;
228}
229//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm9vdC1hdXRob3JpdHkuanMiLCJzb3VyY2VSb290IjoiL1VzZXJzL2Rhdy9vc3MvZGV2Y2VydC8iLCJzb3VyY2VzIjpbInJvb3QtYXV0aG9yaXR5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJCQUE2RDtBQUM3RCxpREFBcUM7QUFDckMsNkJBQTZCO0FBQzdCLDZCQUE2QjtBQUM3QixvQ0FBb0M7QUFDcEMscUNBQXFDO0FBQ3JDLG1EQUF1RDtBQUN2RCw2QkFBNkI7QUFDN0IsMkJBQTJCO0FBRTNCLDJDQVNxQjtBQUNyQixtQ0FLaUI7QUFFakIsTUFBTSxLQUFLLEdBQUcsV0FBVyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRXJDLGtHQUFrRztBQUNsRyxrR0FBa0c7QUFDbEcscUNBQTBELGVBQXdCOztRQUNoRixLQUFLLENBQUMsa0NBQWtDLENBQUMsQ0FBQztRQUMxQyx3QkFBd0IsRUFBRSxDQUFDO1FBRTNCLEtBQUssQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO1FBQ25ELG1CQUFXLENBQUMsdUJBQVcsQ0FBQyxDQUFDO1FBRXpCLEtBQUssQ0FBQyxtREFBbUQsQ0FBQyxDQUFDO1FBQzNELGVBQU8sQ0FBQyxlQUFnQiwyQkFBZ0IsU0FBVSx1QkFBWSxTQUFVLHdCQUFhLDhEQUE4RCxDQUFDLENBQUM7UUFFckosS0FBSyxDQUFDLG1EQUFtRCxDQUFDLENBQUE7UUFDMUQsRUFBRSxDQUFDLENBQUMsaUJBQUssQ0FBQyxDQUFDLENBQUM7WUFDVixNQUFNLG1CQUFtQixDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQzdDLENBQUM7UUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsbUJBQU8sQ0FBQyxDQUFDLENBQUM7WUFDbkIsTUFBTSxxQkFBcUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUMvQyxDQUFDO1FBQUMsSUFBSSxDQUFDLENBQUM7WUFDTixNQUFNLHVCQUF1QixFQUFFLENBQUM7UUFDbEMsQ0FBQztJQUNILENBQUM7Q0FBQTtBQWxCRCw4Q0Fr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QUFJLEVBQUUsQ0FBQztJQUNqRCxDQUFDO0lBQ0QsZUFBZTtJQUNmLE1BQU0sQ0FBQyxLQUFLLENBQUM7QUFDZixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgcmVhZEZpbGVTeW5jLCB3cml0ZUZpbGVTeW5jLCBleGlzdHNTeW5jIH0gZnJvbSAnZnMnO1xuaW1wb3J0IHsgZXhlYyB9IGZyb20gJ2NoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0ICogYXMgaHR0cCBmcm9tICdodHRwJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgKiBhcyBnZXRQb3J0IGZyb20gJ2dldC1wb3J0JztcbmltcG9ydCAqIGFzIGNyZWF0ZURlYnVnIGZyb20gJ2RlYnVnJztcbmltcG9ydCB7IHN5bmMgYXMgY29tbWFuZEV4aXN0cyB9IGZyb20gJ2NvbW1hbmQtZXhpc3RzJztcbmltcG9ydCAqIGFzIGdsb2IgZnJvbSAnZ2xvYic7XG5pbXBvcnQgKiBhcyBlb2wgZnJvbSAnZW9sJztcblxuaW1wb3J0IHtcbiAgaXNNYWMsXG4gIGlzTGludXgsXG4gIGlzV2luZG93cyxcbiAgY29uZmlnUGF0aCxcbiAgcm9vdEtleVBhdGgsXG4gIHJvb3RDZXJ0UGF0aCxcbiAgb3BlbnNzbENvbmZQYXRoLFxuICBvcGVuc3NsQ29uZlRlbXBsYXRlXG59IGZyb20gJy4vY29uc3RhbnRzJztcbmltcG9ydCB7XG4gIG9wZW5zc2wsXG4gIGdlbmVyYXRlS2V5LFxuICBydW4sXG4gIHdhaXRGb3JVc2VyXG59IGZyb20gJy4vdXRpbHMnO1xuXG5jb25zdCBkZWJ1ZyA9IGNyZWF0ZURlYnVnKCdkZXZjZXJ0Jyk7XG5cbi8vIEluc3RhbGwgdGhlIG9uY2UtcGVyLW1hY2hpbmUgdHJ1c3RlZCByb290IENBLiBXZSdsbCB1c2UgdGhpcyBDQSB0byBzaWduIHBlci1hcHAgY2VydHMsIGFsbG93aW5nXG4vLyB1cyB0byBtaW5pbWl6ZSB0aGUgbmVlZCBmb3IgZWxldmF0ZWQgcGVybWlzc2lvbnMgd2hpbGUgc3RpbGwgYWxsb3dpbmcgZm9yIHBlci1hcHAgY2VydGlmaWNhdGVzLlxuZXhwb3J0IGRlZmF1bHQgYXN5bmMgZnVuY3Rpb24gaW5zdGFsbENlcnRpZmljYXRlQXV0aG9yaXR5KGluc3RhbGxDZXJ0dXRpbDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICBkZWJ1ZyhgZ2VuZXJhdGluZyBvcGVuc3NsIGNvbmZpZ3VyYXRpb25gKTtcbiAgZ2VuZXJhdGVPcGVuU1NMQ29uZkZpbGVzKCk7XG5cbiAgZGVidWcoYGdlbmVyYXRpbmcgcm9vdCBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkga2V5YCk7XG4gIGdlbmVyYXRlS2V5KHJvb3RLZXlQYXRoKTtcblxuICBkZWJ1ZyhgZ2VuZXJhdGluZyByb290IGNlcnRpZmljYXRlIGF1dGhvcml0eSBjZXJ0aWZpY2F0ZWApO1xuICBvcGVuc3NsKGByZXEgLWNvbmZpZyAkeyBvcGVuc3NsQ29uZlBhdGggfSAta2V5ICR7IHJvb3RLZXlQYXRoIH0gLW91dCAkeyByb290Q2VydFBhdGggfSAtbmV3IC1zdWJqIFwiL0NOPWRldmNlcnRcIiAteDUwOSAtZGF5cyA3MDAwIC1leHRlbnNpb25zIHYzX2NhYCk7XG5cbiAgZGVidWcoYGFkZGluZyByb290IGNlcnRpZmljYXRlIGF1dGhvcml0eSB0byB0cnVzdCBzdG9yZXNgKVxuICBpZiAoaXNNYWMpIHtcbiAgICBhd2FpdCBhZGRUb01hY1RydXN0U3RvcmVzKGluc3RhbGxDZXJ0dXRpbCk7XG4gIH0gZWxzZSBpZiAoaXNMaW51eCkge1xuICAgIGF3YWl0IGFkZFRvTGludXhUcnVzdFN0b3JlcyhpbnN0YWxsQ2VydHV0aWwpO1xuICB9IGVsc2Uge1xuICAgIGF3YWl0IGFkZFRvV2luZG93c1RydXN0U3RvcmVzKCk7XG4gIH1cbn1cblxuLy8gQ29weSBvdXIgb3BlbnNzbCBjb25mIHRlbXBsYXRlIHRvIHRoZSBsb2NhbCBjb25maWcgZm9sZGVyLCBhbmQgdXBkYXRlIHRoZSBwYXRocyB0byBiZSBPU1xuLy8gc3BlY2lmaWMuIEFsc28gaW5pdGlhbGl6ZXMgdGhlIGZpbGVzIG9wZW5zc2wgbmVlZHMgdG8gc2lnbiBjZXJ0aWZpY2F0ZXMgYXMgYSBjZXJ0aWZpY2F0ZVxuLy8gYXV0aG9yaXR5XG5mdW5jdGlvbiBnZW5lcmF0ZU9wZW5TU0xDb25mRmlsZXMoKSB7XG4gIGxldCBjb25mVGVtcGxhdGUgPSByZWFkRmlsZVN5bmMob3BlbnNzbENvbmZUZW1wbGF0ZSwgJ3V0Zi04Jyk7XG4gIGNvbmZUZW1wbGF0ZSA9IGNvbmZUZW1wbGF0ZS5yZXBsYWNlKC9EQVRBQkFTRV9QQVRILywgY29uZmlnUGF0aCgnaW5kZXgudHh0JykucmVwbGFjZSgvXFxcXC9nLCAnXFxcXFxcXFwnKSk7XG4gIGNvbmZUZW1wbGF0ZSA9IGNvbmZUZW1wbGF0ZS5yZXBsYWNlKC9TRVJJQUxfUEFUSC8sIGNvbmZpZ1BhdGgoJ3NlcmlhbCcpLnJlcGxhY2UoL1xcXFwvZywgJ1xcXFxcXFxcJykpO1xuICBjb25mVGVtcGxhdGUgPSBlb2wuYXV0byhjb25mVGVtcGxhdGUpO1xuICB3cml0ZUZpbGVTeW5jKG9wZW5zc2xDb25mUGF0aCwgY29uZlRlbXBsYXRlKTtcbiAgd3JpdGVGaWxlU3luYyhjb25maWdQYXRoKCdpbmRleC50eHQnKSwgJycpO1xuICB3cml0ZUZpbGVTeW5jKGNvbmZpZ1BhdGgoJ3NlcmlhbCcpLCAnMDEnKTtcbiAgLy8gVGhpcyB2ZXJzaW9uIG51bWJlciBsZXRzIHVzIHdyaXRlIGNvZGUgaW4gdGhlIGZ1dHVyZSB0aGF0IGludGVsbGlnZW50bHkgdXBncmFkZXMgYW4gZXhpc3RpbmdcbiAgLy8gZGV2Y2VydCBpbnN0YWxsYXRpb24uIFRoaXMgXCJjYS12ZXJzaW9uXCIgaXMgaW5kZXBlbmRlbnQgb2YgdGhlIGRldmNlcnQgcGFja2FnZSB2ZXJzaW9uLCBhbmRcbiAgLy8gdHJhY2tzIGNoYW5nZXMgdG8gdGhlIHJvb3QgY2VydGlmaWNhdGUgc2V0dXAgb25seS5cbiAgd3JpdGVGaWxlU3luYyhjb25maWdQYXRoKCdkZXZjZXJ0LWNhLXZlcnNpb24nKSwgJzEnKTtcbn1cblxuLy8gbWFjT1MgaXMgcHJldHR5IHNpbXBsZSAtIGp1c3QgYWRkIHRoZSBjZXJ0aWZpY2F0ZSB0byB0aGUgc3lzdGVtIGtleWNoYWluLCBhbmQgbW9zdCBhcHBsaWNhdGlvbnNcbi8vIHdpbGwgZGVsZWdhdGUgdG8gdGhhdCBmb3IgZGV0ZXJtaW5pbmcgdHJ1c3RlZCBjZXJ0aWZpY2F0ZXMuIEZpcmVmb3gsIG9mIGNvdXJzZSwgZG9lcyBpdCdzIG93blxuLy8gdGhpbmcuIFdlIGNhbiB0cnkgdG8gYXV0b21hdGljYWxseSBpbnN0YWxsIHRoZSBjZXJ0IHdpdGggRmlyZWZveCBpZiB3ZSBjYW4gdXNlIGNlcnR1dGlsIHZpYSB0aGVcbi8vIGBuc3NgIEhvbWVicmV3IHBhY2thZ2UsIG90aGVyd2lzZSB3ZSBnbyBtYW51YWwgd2l0aCB1c2VyLWZhY2luZyBwcm9tcHRzLlxuYXN5bmMgZnVuY3Rpb24gYWRkVG9NYWNUcnVzdFN0b3JlcyhpbnN0YWxsQ2VydHV0aWw6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgLy8gQ2hyb21lLCBTYWZhcmksIHN5c3RlbSB1dGlsc1xuICBkZWJ1ZygnYWRkaW5nIGRldmNlcnQgcm9vdCBDQSB0byBtYWNPUyBzeXN0ZW0ga2V5Y2hhaW4nKTtcbiAgcnVuKGBzdWRvIHNlY3VyaXR5IGFkZC10cnVzdGVkLWNlcnQgLWQgLXIgdHJ1c3RSb290IC1rIC9MaWJyYXJ5L0tleWNoYWlucy9TeXN0ZW0ua2V5Y2hhaW4gLXAgc3NsIC1wIGJhc2ljIFwiJHsgcm9vdENlcnRQYXRoIH1cImApO1xuICAvLyBGaXJlZm94XG4gIHRyeSB7XG4gICAgLy8gVHJ5IHRvIHVzZSBjZXJ0dXRpbCB0byBpbnN0YWxsIHRoZSBjZXJ0IGF1dG9tYXRpY2FsbHlcbiAgICBkZWJ1ZygnYWRkaW5nIGRldmNlcnQgcm9vdCBDQSB0byBmaXJlZm94Jyk7XG4gICAgYXdhaXQgYWRkQ2VydGlmaWNhdGVUb05TU0NlcnREQihwYXRoLmpvaW4ocHJvY2Vzcy5lbnYuSE9NRSwgJ0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9GaXJlZm94L1Byb2ZpbGVzLyonKSwge1xuICAgICAgaW5zdGFsbENlcnR1dGlsLFxuICAgICAgY2hlY2tGb3JPcGVuRmlyZWZveDogdHJ1ZVxuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgLy8gT3RoZXJ3aXNlLCBvcGVuIHRoZSBjZXJ0IGluIEZpcmVmb3ggdG8gaW5zdGFsbCBpdFxuICAgIGF3YWl0IG9wZW5DZXJ0aWZpY2F0ZUluRmlyZWZveCgnL0FwcGxpY2F0aW9ucy9GaXJlZm94LmFwcC9Db250ZW50cy9NYWNPUy9maXJlZm94Jyk7XG4gIH1cbn1cblxuLy8gTGludXggaXMgc3VycHJpc2luZ2x5IGRpZmZpY3VsdC4gVGhlcmUgc2VlbXMgdG8gYmUgbXVsdGlwbGUgc3lzdGVtLXdpZGUgcmVwb3NpdG9yaWVzIGZvciBjZXJ0cyxcbi8vIHNvIHdlIGNvcHkgb3VycyB0byBlYWNoLiBIb3dldmVyLCBGaXJlZm94IGRvZXMgaXQncyB1c3VhbCBzZXBhcmF0ZSB0cnVzdCBzdG9yZS4gUGx1cyBDaHJvbWVcbi8vIHJlbGllcyBvbiB0aGUgTlNTIHRvb2xpbmcgKGxpa2UgRmlyZWZveCksIGJ1dCB1c2VzIHRoZSB1c2VyJ3MgTlNTIGRhdGFiYXNlLCB1bmxpa2UgRmlyZWZveCB3aGljaFxuLy8gdXNlcyBhIHNlcGFyYXRlIE1vemlsbGEgb25lLiBBbmQgc2luY2UgQ2hyb21lIGRvZXNuJ3QgcHJvbXB0IHRoZSB1c2VyIHdpdGggYSBHVUkgZmxvdyB3aGVuXG4vLyBvcGVuaW5nIGNlcnRzLCBpZiB3ZSBjYW4ndCB1c2UgY2VydHV0aWwsIHdlJ3JlIG91dCBvZiBsdWNrLlxuYXN5bmMgZnVuY3Rpb24gYWRkVG9MaW51eFRydXN0U3RvcmVzKGluc3RhbGxDZXJ0dXRpbDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAvLyBzeXN0ZW0gdXRpbHNcbiAgZGVidWcoJ2FkZGluZyBkZXZjZXJ0IHJvb3QgQ0EgdG8gbGludXggc3lzdGVtLXdpZGUgY2VydGlmaWNhdGVzJyk7XG4gIHJ1bihgc3VkbyBjcCAkeyByb290Q2VydFBhdGggfSAvZXRjL3NzbC9jZXJ0cy9kZXZjZXJ0LnBlbWApO1xuICBydW4oYHN1ZG8gY3AgJHsgcm9vdENlcnRQYXRoIH0gL3Vzci9sb2NhbC9zaGFyZS9jYS1jZXJ0aWZpY2F0ZXMvZGV2Y2VydC5jZXJgKTtcbiAgcnVuKGBzdWRvIHVwZGF0ZS1jYS1jZXJ0aWZpY2F0ZXNgKTtcbiAgLy8gRmlyZWZveFxuICB0cnkge1xuICAgIC8vIFRyeSB0byB1c2UgY2VydHV0aWwgdG8gaW5zdGFsbCB0aGUgY2VydCBhdXRvbWF0aWNhbGx5XG4gICAgZGVidWcoJ2FkZGluZyBkZXZjZXJ0IHJvb3QgQ0EgdG8gZmlyZWZveCcpO1xuICAgIGF3YWl0IGFkZENlcnRpZmljYXRlVG9OU1NDZXJ0REIocGF0aC5qb2luKHByb2Nlc3MuZW52LkhPTUUsICcubW96aWxsYS9maXJlZm94LyonKSwge1xuICAgICAgaW5zdGFsbENlcnR1dGlsLFxuICAgICAgY2hlY2tGb3JPcGVuRmlyZWZveDogdHJ1ZVxuICAgIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgLy8gT3RoZXJ3aXNlLCBvcGVuIHRoZSBjZXJ0IGluIEZpcmVmb3ggdG8gaW5zdGFsbCBpdFxuICAgIGF3YWl0IG9wZW5DZXJ0aWZpY2F0ZUluRmlyZWZveCgnZmlyZWZveCcpO1xuICB9XG4gIC8vIENocm9tZVxuICB0cnkge1xuICAgIGRlYnVnKCdhZGRpbmcgZGV2Y2VydCByb290IENBIHRvIGNocm9tZScpO1xuICAgIGF3YWl0IGFkZENlcnRpZmljYXRlVG9OU1NDZXJ0REIocGF0aC5qb2luKHByb2Nlc3MuZW52LkhPTUUsICcucGtpL25zc2RiJyksIHsgaW5zdGFsbENlcnR1dGlsIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgY29uc29sZS53YXJuKGBcbldBUk5JTkc6IEJlY2F1c2UgeW91IGRpZCBub3QgcGFzcyBpbiBcXGBpbnN0YWxsQ2VydHV0aWw6IHRydWVcXGAgdG8gZGV2Y2VydCwgd2VcbmFyZSB1bmFibGUgdG8gdXBkYXRlIENocm9tZSB0byBhdXRvbWF0aWNhbGx5IHRydXN0IGdlbmVyYXRlZCBkZXZlbG9wbWVudFxuY2VydGlmaWNhdGVzLiBUaGUgY2VydGlmaWNhdGVzIHdpbGwgd29yaywgYnV0IENocm9tZSB3aWxsIGNvbnRpbnVlIHRvIHdhcm4geW91XG50aGF0IHRoZXkgYXJlIHVudHJ1c3RlZC5gKTtcbiAgfVxufVxuXG4vLyBXaW5kb3dzIGlzIGF0IGxlYXN0IHNpbXBsZS4gTGlrZSBtYWNPUywgbW9zdCBhcHBsaWNhdGlvbnMgd2lsbCBkZWxlZ2F0ZSB0byB0aGUgc3lzdGVtIHRydXN0XG4vLyBzdG9yZSwgd2hpY2ggaXMgdXBkYXRlZCB3aXRoIHRoZSBjb25mdXNpbmdseSBuYW1lZCBgY2VydHV0aWxgIGV4ZSAobm90IHRoZSBzYW1lIGFzIHRoZVxuLy8gTlNTL01vemlsbGEgY2VydHV0aWwpLiBGaXJlZm94IGRvZXMgaXQncyBvd24gdGhpbmcgYXMgdXN1YWwsIGFuZCBnZXR0aW5nIGEgY29weSBvZiBOU1MgY2VydHV0aWxcbi8vIG9udG8gdGhlIFdpbmRvd3MgbWFjaGluZSB0byB0cnkgdXBkYXRpbmcgdGhlIEZpcmVmb3ggc3RvcmUgaXMgYmFzaWNhbGx5IGEgbmlnaHRtYXJlLCBzbyB3ZSBkb24ndFxuLy8gZXZlbiB0cnkgaXQgLSB3ZSBqdXN0IGJhaWwgb3V0IHRvIHRoZSBHVUkuXG5hc3luYyBmdW5jdGlvbiBhZGRUb1dpbmRvd3NUcnVzdFN0b3JlcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgLy8gSUUsIENocm9tZSwgc3lzdGVtIHV0aWxzXG4gIGRlYnVnKCdhZGRpbmcgZGV2Y2VydCByb290IHRvIFdpbmRvd3MgT1MgdHJ1c3Qgc3RvcmUnKVxuICBydW4oYGNlcnR1dGlsIC1hZGRzdG9yZSAtdXNlciByb290ICR7IHJvb3RDZXJ0UGF0aCB9YCk7XG4gIC8vIEZpcmVmb3ggKGRvbid0IGV2ZW4gdHJ5IE5TUyBjZXJ0dXRpbCwgbm8gZWFzeSBpbnN0YWxsIGZvciBXaW5kb3dzKVxuICBhd2FpdCBvcGVuQ2VydGlmaWNhdGVJbkZpcmVmb3goJ3N0YXJ0IGZpcmVmb3gnKTtcbn1cblxuLy8gR2l2ZW4gYSBkaXJlY3Rvcnkgb3IgZ2xvYiBwYXR0ZXJuIG9mIGRpcmVjdG9yaWVzLCBhdHRlbXB0IHRvIGluc3RhbGwgdGhlIGNlcnRpZmljYXRlIHRvIGVhY2hcbi8vIGRpcmVjdG9yeSBjb250YWluaW5nIGFuIE5TUyBkYXRhYmFzZS5cbmFzeW5jIGZ1bmN0aW9uIGFkZENlcnRpZmljYXRlVG9OU1NDZXJ0REIobnNzRGlyR2xvYjogc3RyaW5nLCBvcHRpb25zOiB7IGluc3RhbGxDZXJ0dXRpbD86IGJvb2xlYW4sIGNoZWNrRm9yT3BlbkZpcmVmb3g/OiBib29sZWFuIH0gPSB7fSk6IFByb21pc2U8dm9pZD4ge1xuICBsZXQgY2VydHV0aWxQYXRoID0gbG9va3VwT3JJbnN0YWxsQ2VydHV0aWwob3B0aW9ucy5pbnN0YWxsQ2VydHV0aWwpO1xuICBpZiAoIWNlcnR1dGlsUGF0aCkge1xuICAgIHRocm93IG5ldyBFcnJvcignY2VydHV0aWwgbm90IGF2YWlsYWJsZSwgYW5kIGBpbnN0YWxsQ2VydHV0aWxgIHdhcyBmYWxzZScpO1xuICB9XG4gIC8vIEZpcmVmb3ggYXBwZWFycyB0byBsb2FkIHRoZSBOU1MgZGF0YWJhc2UgaW4tbWVtb3J5IG9uIHN0YXJ0dXAsIGFuZCBvdmVyd3JpdGUgb24gZXhpdC4gU28gd2VcbiAgLy8gaGF2ZSB0byBhc2sgdGhlIHVzZXIgdG8gcXVpdGUgRmlyZWZveCBmaXJzdCBzbyBvdXIgY2hhbmdlcyBkb24ndCBnZXQgb3ZlcndyaXR0ZW4uXG4gIGlmIChvcHRpb25zLmNoZWNrRm9yT3BlbkZpcmVmb3gpIHtcbiAgICBsZXQgcnVubmluZ1Byb2Nlc3NlcyA9IHJ1bigncHMgYXV4Jyk7XG4gICAgaWYgKHJ1bm5pbmdQcm9jZXNzZXMuaW5kZXhPZignZmlyZWZveCcpID4gLTEpIHtcbiAgICAgIGNvbnNvbGUubG9nKCdQbGVhc2UgY2xvc2UgRmlyZWZveCBiZWZvcmUgY29udGludWluZyAoUHJlc3MgPEVudGVyPiB3aGVuIHJlYWR5KScpO1xuICAgICAgYXdhaXQgd2FpdEZvclVzZXIoKTtcbiAgICB9XG4gIH1cbiAgZGVidWcoYHRyeWluZyB0byBpbnN0YWxsIGNlcnRpZmljYXRlIGludG8gTlNTIGRhdGFiYXNlcyBpbiAkeyBuc3NEaXJHbG9iIH1gKTtcbiAgZ2xvYi5zeW5jKG5zc0Rpckdsb2IpLmZvckVhY2goKHBvdGVudGlhbE5TU0RCRGlyKSA9PiB7XG4gICAgZGVidWcoYGNoZWNraW5nIHRvIHNlZSBpZiAkeyBwb3RlbnRpYWxOU1NEQkRpciB9IGlzIGEgdmFsaWQgTlNTIGRhdGFiYXNlIGRpcmVjdG9yeWApO1xuICAgIGlmIChleGlzdHNTeW5jKHBhdGguam9pbihwb3RlbnRpYWxOU1NEQkRpciwgJ2NlcnQ4LmRiJykpKSB7XG4gICAgICBkZWJ1ZyhgRm91bmQgbGVnYWN5IE5TUyBkYXRhYmFzZSBpbiAkeyBwb3RlbnRpYWxOU1NEQkRpciB9LCBhZGRpbmcgZGV2Y2VydCAuLi5gKVxuICAgICAgcnVuKGAkeyBjZXJ0dXRpbFBhdGggfSAtQSAtZCBcIiR7IHBvdGVudGlhbE5TU0RCRGlyIH1cIiAtdCAnQywsJyAtaSAkeyByb290Q2VydFBhdGggfSAtbiBkZXZjZXJ0YCk7XG4gICAgfSBlbHNlIGlmIChleGlzdHNTeW5jKHBhdGguam9pbihwb3RlbnRpYWxOU1NEQkRpciwgJ2NlcnQ5LmRiJykpKSB7XG4gICAgICBkZWJ1ZyhgRm91bmQgbW9kZXJuIE5TUyBkYXRhYmFzZSBpbiAkeyBwb3RlbnRpYWxOU1NEQkRpciB9LCBhZGRpbmcgZGV2Y2VydCAuLi5gKVxuICAgICAgcnVuKGAkeyBjZXJ0dXRpbFBhdGggfSAtQSAtZCBcInNxbDokeyBwb3RlbnRpYWxOU1NEQkRpciB9XCIgLXQgJ0MsLCcgLWkgJHsgcm9vdENlcnRQYXRoIH0gLW4gZGV2Y2VydGApO1xuICAgIH1cbiAgfSk7XG59XG5cbi8vIFdoZW4gYSBGaXJlZm94IHRhYiBpcyBkaXJlY3RlZCB0byBhIFVSTCB0aGF0IHJldHVybnMgYSBjZXJ0aWZpY2F0ZSwgaXQgd2lsbCBhdXRvbWF0aWNhbGx5IHByb21wdFxuLy8gdGhlIHVzZXIgaWYgdGhleSB3YW50IHRvIGFkZCBpdCB0byB0aGVpciB0cnVzdGVkIGNlcnRpZmljYXRlcy4gVGhpcyBpcyBoYW5keSBzaW5jZSBGaXJlZm94IGlzIGJ5XG4vLyBmYXIgdGhlIG1vc3QgdHJvdWJsZXNvbWUgdG8gaGFuZGxlLiBJZiB3ZSBjYW4ndCBhdXRvbWF0aWNhbGx5IGluc3RhbGwgdGhlIGNlcnRpZmljYXRlIChiZWNhdXNlXG4vLyBjZXJ0dXRpbCBpcyBub3QgYXZhaWxhYmxlIC8gaW5zdGFsbGFibGUpLCB3ZSBpbnN0ZWFkIHN0YXJ0IGEgcXVpY2sgd2ViIHNlcnZlciBhbmQgaG9zdCBvdXJcbi8vIGNlcnRpZmljYXRlIGZpbGUuIFRoZW4gd2Ugb3BlbiB0aGUgaG9zdGVkIGNlcnQgVVJMIGluIEZpcmVmb3ggdG8ga2ljayBvZmYgdGhlIEdVSSBmbG93LlxuYXN5bmMgZnVuY3Rpb24gb3BlbkNlcnRpZmljYXRlSW5GaXJlZm94KGZpcmVmb3hQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgZGVidWcoJ2FkZGluZyBkZXZlcnQgdG8gZmlyZWZveCBtYW51YWxseSAtIGxhdW5jaCB3ZWJzZXJ2ZXIgZm9yIGNlcnRpZmljYXRlIGhvc3RpbmcnKTtcbiAgbGV0IHBvcnQgPSBhd2FpdCBnZXRQb3J0KCk7XG4gIGxldCBzZXJ2ZXIgPSBodHRwLmNyZWF0ZVNlcnZlcigocmVxLCByZXMpID0+IHtcbiAgICByZXMud3JpdGVIZWFkKDIwMCwgeyAnQ29udGVudC10eXBlJzogJ2FwcGxpY2F0aW9uL3gteDUwOS1jYS1jZXJ0JyB9KTtcbiAgICByZXMud3JpdGUocmVhZEZpbGVTeW5jKHJvb3RDZXJ0UGF0aCkpO1xuICAgIHJlcy5lbmQoKTtcbiAgfSkubGlzdGVuKHBvcnQpO1xuICBkZWJ1ZygnY2VydGlmaWNhdGUgaXMgaG9zdGVkLCBzdGFydGluZyBmaXJlZm94IGF0IGhvc3RlZCBVUkwnKTtcbiAgY29uc29sZS5sb2coYFVuYWJsZSB0byBhdXRvbWF0aWNhbGx5IGluc3RhbGwgU1NMIGNlcnRpZmljYXRlIC0gcGxlYXNlIGZvbGxvdyB0aGUgcHJvbXB0cyBhdCBodHRwOi8vbG9jYWxob3N0OiR7IHBvcnQgfSBpbiBGaXJlZm94IHRvIHRydXN0IHRoZSByb290IGNlcnRpZmljYXRlYCk7XG4gIGNvbnNvbGUubG9nKCdTZWUgaHR0cHM6Ly9naXRodWIuY29tL2RhdmV3YXNtZXIvZGV2Y2VydCNob3ctaXQtd29ya3MgZm9yIG1vcmUgZGV0YWlscycpO1xuICBjb25zb2xlLmxvZygnLS0gUHJlc3MgPEVudGVyPiBvbmNlIHlvdSBmaW5pc2ggdGhlIEZpcmVmb3ggcHJvbXB0cyAtLScpO1xuICBleGVjKGAkeyBmaXJlZm94UGF0aCB9IGh0dHA6Ly9sb2NhbGhvc3Q6JHsgcG9ydCB9YCk7XG4gIGF3YWl0IHdhaXRGb3JVc2VyKCk7XG59XG5cbi8vIFRyeSB0byBpbnN0YWxsIGNlcnR1dGlsIGlmIGl0J3Mgbm90IGFscmVhZHkgYXZhaWxhYmxlLCBhbmQgcmV0dXJuIHRoZSBwYXRoIHRvIHRoZSBleGVjdXRhYmxlXG5mdW5jdGlvbiBsb29rdXBPckluc3RhbGxDZXJ0dXRpbChpbnN0YWxsQ2VydHV0aWw6IGJvb2xlYW4pOiBib29sZWFuIHwgc3RyaW5nIHtcbiAgZGVidWcoJ2xvb2tpbmcgZm9yIG5zcyB0b29saW5nIC4uLicpXG4gIGlmIChpc01hYykge1xuICAgIGRlYnVnKCdvbiBtYWMsIGxvb2tpbmcgZm9yIGhvbWVicmV3ICh0aGUgb25seSBtZXRob2QgdG8gaW5zdGFsbCBuc3MgdGhhdCBpcyBjdXJyZW50bHkgc3VwcG9ydGVkIGJ5IGRldmNlcnQnKTtcbiAgICBpZiAoY29tbWFuZEV4aXN0cygnYnJldycpKSB7XG4gICAgICBsZXQgbnNzUGF0aDogc3RyaW5nO1xuICAgICAgbGV0IGNlcnR1dGlsUGF0aDogc3RyaW5nO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY2VydHV0aWxQYXRoID0gcGF0aC5qb2luKHJ1bignYnJldyAtLXByZWZpeCBuc3MnKS50b1N0cmluZygpLnRyaW0oKSwgJ2JpbicsICdjZXJ0dXRpbCcpO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBkZWJ1ZygnYnJldyB3YXMgZm91bmQsIGJ1dCBuc3MgaXMgbm90IGluc3RhbGxlZCcpO1xuICAgICAgICBpZiAoaW5zdGFsbENlcnR1dGlsKSB7XG4gICAgICAgICAgZGVidWcoJ2F0dGVtcHRpbmcgdG8gaW5zdGFsbCBuc3MgdmlhIGJyZXcnKTtcbiAgICAgICAgICBydW4oJ2JyZXcgaW5zdGFsbCBuc3MnKTtcbiAgICAgICAgICBjZXJ0dXRpbFBhdGggPSBwYXRoLmpvaW4ocnVuKCdicmV3IC0tcHJlZml4IG5zcycpLnRvU3RyaW5nKCkudHJpbSgpLCAnYmluJywgJ2NlcnR1dGlsJyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBkZWJ1ZyhgRm91bmQgbnNzIGluc3RhbGxlZCBhdCAkeyBjZXJ0dXRpbFBhdGggfWApO1xuICAgICAgcmV0dXJuIGNlcnR1dGlsUGF0aDtcbiAgICB9XG4gIH0gZWxzZSBpZiAoaXNMaW51eCkge1xuICAgIGRlYnVnKCdvbiBsaW51eCwgY2hlY2tpbmcgaXMgbnNzIGlzIGFscmVhZHkgaW5zdGFsbGVkJyk7XG4gICAgaWYgKCFjb21tYW5kRXhpc3RzKCdjZXJ0dXRpbCcpKSB7XG4gICAgICBpZiAoaW5zdGFsbENlcnR1dGlsKSB7XG4gICAgICAgIGRlYnVnKCdub3QgYWxyZWFkeSBpbnN0YWxsZWQsIGluc3RhbGxpbmcgaXQgb3Vyc2VsdmVzJyk7XG4gICAgICAgIHJ1bignc3VkbyBhcHQgaW5zdGFsbCBsaWJuc3MzLXRvb2xzJyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkZWJ1Zygnbm90IGluc3RhbGxlZCBhbmQgZG8gbm90IHdhbnQgdG8gaW5zdGFsbCcpO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIGRlYnVnKCdsb29rcyBsaWtlIG5zcyBpcyBpbnN0YWxsZWQnKTtcbiAgICByZXR1cm4gcnVuKCd3aGljaCBjZXJ0dXRpbCcpLnRvU3RyaW5nKCkudHJpbSgpO1xuICB9XG4gIC8vIFdpbmRvd3M/IEhhIVxuICByZXR1cm4gZmFsc2U7XG59Il19
\No newline at end of file