1 | import { readFileSync, writeFileSync, existsSync } from 'fs';
|
2 | import { exec } from 'child_process';
|
3 | import * as http from 'http';
|
4 | import * as path from 'path';
|
5 | import * as getPort from 'get-port';
|
6 | import * as createDebug from 'debug';
|
7 | import { sync as commandExists } from 'command-exists';
|
8 | import * as glob from 'glob';
|
9 | import * as eol from 'eol';
|
10 |
|
11 | import {
|
12 | isMac,
|
13 | isLinux,
|
14 | isWindows,
|
15 | configPath,
|
16 | rootKeyPath,
|
17 | rootCertPath,
|
18 | opensslConfPath,
|
19 | opensslConfTemplate
|
20 | } from './constants';
|
21 | import {
|
22 | openssl,
|
23 | generateKey,
|
24 | run,
|
25 | waitForUser
|
26 | } from './utils';
|
27 |
|
28 | const debug = createDebug('devcert');
|
29 |
|
30 |
|
31 |
|
32 | export default async function installCertificateAuthority(installCertutil: boolean): Promise<void> {
|
33 | debug(`generating openssl configuration`);
|
34 | generateOpenSSLConfFiles();
|
35 |
|
36 | debug(`generating root certificate authority key`);
|
37 | generateKey(rootKeyPath);
|
38 |
|
39 | debug(`generating root certificate authority certificate`);
|
40 | openssl(`req -config ${ opensslConfPath } -key ${ rootKeyPath } -out ${ rootCertPath } -new -subj "/CN=devcert" -x509 -days 7000 -extensions v3_ca`);
|
41 |
|
42 | debug(`adding root certificate authority to trust stores`)
|
43 | if (isMac) {
|
44 | await addToMacTrustStores(installCertutil);
|
45 | } else if (isLinux) {
|
46 | await addToLinuxTrustStores(installCertutil);
|
47 | } else {
|
48 | await addToWindowsTrustStores();
|
49 | }
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function generateOpenSSLConfFiles() {
|
56 | let confTemplate = readFileSync(opensslConfTemplate, 'utf-8');
|
57 | confTemplate = confTemplate.replace(/DATABASE_PATH/, configPath('index.txt').replace(/\\/g, '\\\\'));
|
58 | confTemplate = confTemplate.replace(/SERIAL_PATH/, configPath('serial').replace(/\\/g, '\\\\'));
|
59 | confTemplate = eol.auto(confTemplate);
|
60 | writeFileSync(opensslConfPath, confTemplate);
|
61 | writeFileSync(configPath('index.txt'), '');
|
62 | writeFileSync(configPath('serial'), '01');
|
63 |
|
64 |
|
65 |
|
66 | writeFileSync(configPath('devcert-ca-version'), '1');
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | async function addToMacTrustStores(installCertutil: boolean): Promise<void> {
|
74 |
|
75 | debug('adding devcert root CA to macOS system keychain');
|
76 | run(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain -p ssl -p basic "${ rootCertPath }"`);
|
77 |
|
78 | try {
|
79 |
|
80 | debug('adding devcert root CA to firefox');
|
81 | await addCertificateToNSSCertDB(path.join(process.env.HOME, 'Library/Application Support/Firefox/Profiles/*'), {
|
82 | installCertutil,
|
83 | checkForOpenFirefox: true
|
84 | });
|
85 | } catch (e) {
|
86 |
|
87 | await openCertificateInFirefox('/Applications/Firefox.app/Contents/MacOS/firefox');
|
88 | }
|
89 | }
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | async function addToLinuxTrustStores(installCertutil: boolean): Promise<void> {
|
97 |
|
98 | debug('adding devcert root CA to linux system-wide certificates');
|
99 | run(`sudo cp ${ rootCertPath } /etc/ssl/certs/devcert.pem`);
|
100 | run(`sudo cp ${ rootCertPath } /usr/local/share/ca-certificates/devcert.cer`);
|
101 | run(`sudo update-ca-certificates`);
|
102 |
|
103 | try {
|
104 |
|
105 | debug('adding devcert root CA to firefox');
|
106 | await addCertificateToNSSCertDB(path.join(process.env.HOME, '.mozilla/firefox/*'), {
|
107 | installCertutil,
|
108 | checkForOpenFirefox: true
|
109 | });
|
110 | } catch (e) {
|
111 |
|
112 | await openCertificateInFirefox('firefox');
|
113 | }
|
114 |
|
115 | try {
|
116 | debug('adding devcert root CA to chrome');
|
117 | await addCertificateToNSSCertDB(path.join(process.env.HOME, '.pki/nssdb'), { installCertutil });
|
118 | } catch (e) {
|
119 | console.warn(`
|
120 | WARNING: Because you did not pass in \`installCertutil: true\` to devcert, we
|
121 | are unable to update Chrome to automatically trust generated development
|
122 | certificates. The certificates will work, but Chrome will continue to warn you
|
123 | that they are untrusted.`);
|
124 | }
|
125 | }
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | async function addToWindowsTrustStores(): Promise<void> {
|
133 |
|
134 | debug('adding devcert root to Windows OS trust store')
|
135 | run(`certutil -addstore -user root ${ rootCertPath }`);
|
136 |
|
137 | await openCertificateInFirefox('start firefox');
|
138 | }
|
139 |
|
140 |
|
141 |
|
142 | async function addCertificateToNSSCertDB(nssDirGlob: string, options: { installCertutil?: boolean, checkForOpenFirefox?: boolean } = {}): Promise<void> {
|
143 | let certutilPath = lookupOrInstallCertutil(options.installCertutil);
|
144 | if (!certutilPath) {
|
145 | throw new Error('certutil not available, and `installCertutil` was false');
|
146 | }
|
147 |
|
148 |
|
149 | if (options.checkForOpenFirefox) {
|
150 | let runningProcesses = run('ps aux');
|
151 | if (runningProcesses.indexOf('firefox') > -1) {
|
152 | console.log('Please close Firefox before continuing (Press <Enter> when ready)');
|
153 | await waitForUser();
|
154 | }
|
155 | }
|
156 | debug(`trying to install certificate into NSS databases in ${ nssDirGlob }`);
|
157 | glob.sync(nssDirGlob).forEach((potentialNSSDBDir) => {
|
158 | debug(`checking to see if ${ potentialNSSDBDir } is a valid NSS database directory`);
|
159 | if (existsSync(path.join(potentialNSSDBDir, 'cert8.db'))) {
|
160 | debug(`Found legacy NSS database in ${ potentialNSSDBDir }, adding devcert ...`)
|
161 | run(`${ certutilPath } -A -d "${ potentialNSSDBDir }" -t 'C,,' -i ${ rootCertPath } -n devcert`);
|
162 | } else if (existsSync(path.join(potentialNSSDBDir, 'cert9.db'))) {
|
163 | debug(`Found modern NSS database in ${ potentialNSSDBDir }, adding devcert ...`)
|
164 | run(`${ certutilPath } -A -d "sql:${ potentialNSSDBDir }" -t 'C,,' -i ${ rootCertPath } -n devcert`);
|
165 | }
|
166 | });
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | async function openCertificateInFirefox(firefoxPath: string): Promise<void> {
|
175 | debug('adding devert to firefox manually - launch webserver for certificate hosting');
|
176 | let port = await getPort();
|
177 | let server = http.createServer((req, res) => {
|
178 | res.writeHead(200, { 'Content-type': 'application/x-x509-ca-cert' });
|
179 | res.write(readFileSync(rootCertPath));
|
180 | res.end();
|
181 | }).listen(port);
|
182 | debug('certificate is hosted, starting firefox at hosted URL');
|
183 | console.log(`Unable to automatically install SSL certificate - please follow the prompts at http://localhost:${ port } in Firefox to trust the root certificate`);
|
184 | console.log('See https://github.com/davewasmer/devcert#how-it-works for more details');
|
185 | console.log('-- Press <Enter> once you finish the Firefox prompts --');
|
186 | exec(`${ firefoxPath } http://localhost:${ port }`);
|
187 | await waitForUser();
|
188 | }
|
189 |
|
190 |
|
191 | function lookupOrInstallCertutil(installCertutil: boolean): boolean | string {
|
192 | debug('looking for nss tooling ...')
|
193 | if (isMac) {
|
194 | debug('on mac, looking for homebrew (the only method to install nss that is currently supported by devcert');
|
195 | if (commandExists('brew')) {
|
196 | let nssPath: string;
|
197 | let certutilPath: string;
|
198 | try {
|
199 | certutilPath = path.join(run('brew --prefix nss').toString().trim(), 'bin', 'certutil');
|
200 | } catch (e) {
|
201 | debug('brew was found, but nss is not installed');
|
202 | if (installCertutil) {
|
203 | debug('attempting to install nss via brew');
|
204 | run('brew install nss');
|
205 | certutilPath = path.join(run('brew --prefix nss').toString().trim(), 'bin', 'certutil');
|
206 | } else {
|
207 | return false;
|
208 | }
|
209 | }
|
210 | debug(`Found nss installed at ${ certutilPath }`);
|
211 | return certutilPath;
|
212 | }
|
213 | } else if (isLinux) {
|
214 | debug('on linux, checking is nss is already installed');
|
215 | if (!commandExists('certutil')) {
|
216 | if (installCertutil) {
|
217 | debug('not already installed, installing it ourselves');
|
218 | run('sudo apt install libnss3-tools');
|
219 | } else {
|
220 | debug('not installed and do not want to install');
|
221 | return false;
|
222 | }
|
223 | }
|
224 | debug('looks like nss is installed');
|
225 | return run('which certutil').toString().trim();
|
226 | }
|
227 |
|
228 | return false;
|
229 | } |
\ | No newline at end of file |