-
Notifications
You must be signed in to change notification settings - Fork 73
/
shared.ts
136 lines (127 loc) · 5.71 KB
/
shared.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import path from 'path';
import url from 'url';
import createDebug from 'debug';
import assert from 'assert';
import getPort from 'get-port';
import http from 'http';
import { sync as glob } from 'glob';
import { readFileSync as readFile, existsSync as exists } from 'fs';
import { run } from '../utils';
import { isMac, isLinux , configDir, getLegacyConfigDir } from '../constants';
import UI from '../user-interface';
import { execSync as exec } from 'child_process';
const debug = createDebug('devcert:platforms:shared');
/**
* Given a directory or glob pattern of directories, run a callback for each db
* directory, with a version argument.
*/
function doForNSSCertDB(nssDirGlob: string, callback: (dir: string, version: "legacy" | "modern") => void): void {
glob(nssDirGlob).forEach((potentialNSSDBDir) => {
debug(`checking to see if ${ potentialNSSDBDir } is a valid NSS database directory`);
if (exists(path.join(potentialNSSDBDir, 'cert8.db'))) {
debug(`Found legacy NSS database in ${ potentialNSSDBDir }, running callback...`)
callback(potentialNSSDBDir, 'legacy');
}
if (exists(path.join(potentialNSSDBDir, 'cert9.db'))) {
debug(`Found modern NSS database in ${ potentialNSSDBDir }, running callback...`)
callback(potentialNSSDBDir, 'modern');
}
});
}
/**
* Given a directory or glob pattern of directories, attempt to install the
* CA certificate to each directory containing an NSS database.
*/
export function addCertificateToNSSCertDB(nssDirGlob: string, certPath: string, certutilPath: string): void {
debug(`trying to install certificate into NSS databases in ${ nssDirGlob }`);
doForNSSCertDB(nssDirGlob, (dir, version) => {
const dirArg = version === 'modern' ? `sql:${ dir }` : dir;
run(certutilPath, ['-A', '-d', dirArg, '-t', 'C,,', '-i', certPath, '-n', 'devcert']);
});
debug(`finished scanning & installing certificate in NSS databases in ${ nssDirGlob }`);
}
export function removeCertificateFromNSSCertDB(nssDirGlob: string, certPath: string, certutilPath: string): void {
debug(`trying to remove certificates from NSS databases in ${ nssDirGlob }`);
doForNSSCertDB(nssDirGlob, (dir, version) => {
const dirArg = version === 'modern' ? `sql:${ dir }` : dir;
try {
run(certutilPath, ['-A', '-d', dirArg, '-t', 'C,,', '-i', certPath, '-n', 'devcert']);
} catch (e) {
debug(`failed to remove ${ certPath } from ${ dir }, continuing. ${ e.toString() }`)
}
});
debug(`finished scanning & installing certificate in NSS databases in ${ nssDirGlob }`);
}
/**
* Check to see if Firefox is still running, and if so, ask the user to close
* it. Poll until it's closed, then return.
*
* This is needed because Firefox appears to load the NSS database in-memory on
* startup, and overwrite on exit. So we have to ask the user to quite Firefox
* first so our changes don't get overwritten.
*/
export async function closeFirefox(): Promise<void> {
if (isFirefoxOpen()) {
await UI.closeFirefoxBeforeContinuing();
while(isFirefoxOpen()) {
await sleep(50);
}
}
}
/**
* Check if Firefox is currently open
*/
function isFirefoxOpen() {
// NOTE: We use some Windows-unfriendly methods here (ps) because Windows
// never needs to check this, because it doesn't update the NSS DB
// automaticaly.
assert(isMac || isLinux, 'checkForOpenFirefox was invoked on a platform other than Mac or Linux');
return exec('ps aux').indexOf('firefox') > -1;
}
async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Firefox manages it's own trust store for SSL certificates, which can be
* managed via the certutil command (supplied by NSS tooling packages). In the
* event that certutil is not already installed, and either can't be installed
* (Windows) or the user doesn't want to install it (skipCertutilInstall:
* true), it means that we can't programmatically tell Firefox to trust our
* root CA certificate.
*
* There is a recourse though. When a Firefox tab is directed to a URL that
* responds with a certificate, it will automatically prompt the user if they
* want to add it to their trusted certificates. So if we can't automatically
* install the certificate via certutil, we instead start a quick web server
* and host our certificate file. Then we open the hosted cert URL in Firefox
* to kick off the GUI flow.
*
* This method does all this, along with providing user prompts in the terminal
* to walk them through this process.
*/
export async function openCertificateInFirefox(firefoxPath: string, certPath: string): Promise<void> {
debug('Adding devert to Firefox trust stores manually. Launching a webserver to host our certificate temporarily ...');
let port = await getPort();
let server = http.createServer(async (req, res) => {
let { pathname } = url.parse(req.url);
if (pathname === '/certificate') {
res.writeHead(200, { 'Content-type': 'application/x-x509-ca-cert' });
res.write(readFile(certPath));
res.end();
} else {
res.writeHead(200);
res.write(await UI.firefoxWizardPromptPage(`http://localhost:${ port }/certificate`));
res.end();
}
}).listen(port);
debug('Certificate server is up. Printing instructions for user and launching Firefox with hosted certificate URL');
await UI.startFirefoxWizard(`http://localhost:${ port }`);
run(firefoxPath, [`http://localhost:${ port }`]);
await UI.waitForFirefoxWizard();
server.close();
}
export function assertNotTouchingFiles(filepath: string, operation: string): void {
if (!filepath.startsWith(configDir) && !filepath.startsWith(getLegacyConfigDir())) {
throw new Error(`Devcert cannot ${ operation } ${ filepath }; it is outside known devcert config directories!`);
}
}