/
certificate-authority.ts
144 lines (127 loc) · 4.77 KB
/
certificate-authority.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
137
138
139
140
141
142
143
144
import {
unlinkSync as rm,
readFileSync as readFile,
writeFileSync as writeFile
} from 'fs';
import createDebug from 'debug';
import {
domainsDir,
rootCADir,
ensureConfigDirs,
getLegacyConfigDir,
rootCAKeyPath,
rootCACertPath,
caSelfSignConfig,
opensslSerialFilePath,
opensslDatabaseFilePath,
caVersionFile
} from './constants';
import currentPlatform from './platforms';
import { openssl, mktmp } from './utils';
import { generateKey } from './certificates';
import { Options } from './index';
const debug = createDebug('devcert:certificate-authority');
/**
* Install the once-per-machine trusted root CA. We'll use this CA to sign
* per-app certs.
*/
export default async function installCertificateAuthority(options: Options = {}): Promise<void> {
debug(`Uninstalling existing certificates, which will be void once any existing CA is gone`);
uninstall();
ensureConfigDirs();
debug(`Making a temp working directory for files to copied in`);
let rootKeyPath = mktmp();
debug(`Generating the OpenSSL configuration needed to setup the certificate authority`);
seedConfigFiles();
debug(`Generating a private key`);
generateKey(rootKeyPath);
debug(`Generating a CA certificate`);
openssl(['req', '-new', '-x509', '-config', caSelfSignConfig, '-key', rootKeyPath, '-out', rootCACertPath, '-days', '825']);
debug('Saving certificate authority credentials');
await saveCertificateAuthorityCredentials(rootKeyPath);
debug(`Adding the root certificate authority to trust stores`);
await currentPlatform.addToTrustStores(rootCACertPath, options);
}
/**
* Initializes the files OpenSSL needs to sign certificates as a certificate
* authority, as well as our CA setup version
*/
function seedConfigFiles() {
// This is v2 of the devcert certificate authority setup
writeFile(caVersionFile, '2');
// OpenSSL CA files
writeFile(opensslDatabaseFilePath, '');
writeFile(opensslSerialFilePath, '01');
}
export async function withCertificateAuthorityCredentials(cb: ({ caKeyPath, caCertPath }: { caKeyPath: string, caCertPath: string }) => Promise<void> | void) {
debug(`Retrieving devcert's certificate authority credentials`);
let tmpCAKeyPath = mktmp();
let caKey = await currentPlatform.readProtectedFile(rootCAKeyPath);
writeFile(tmpCAKeyPath, caKey);
await cb({ caKeyPath: tmpCAKeyPath, caCertPath: rootCACertPath });
rm(tmpCAKeyPath);
}
async function saveCertificateAuthorityCredentials(keypath: string) {
debug(`Saving devcert's certificate authority credentials`);
let key = readFile(keypath, 'utf-8');
await currentPlatform.writeProtectedFile(rootCAKeyPath, key);
}
function certErrors(): string {
try {
openssl(['x509', '-in', rootCACertPath, '-noout']);
return '';
} catch (e) {
return e.toString();
}
}
// This function helps to migrate from v1.0.x to >= v1.1.0.
/**
* Smoothly migrate the certificate storage from v1.0.x to >= v1.1.0.
* In v1.1.0 there are new options for retrieving the CA cert directly,
* to help third-party Node apps trust the root CA.
*
* If a v1.0.x cert already exists, then devcert has written it with
* platform.writeProtectedFile(), so an unprivileged readFile cannot access it.
* Pre-detect and remedy this; it should only happen once per installation.
*/
export async function ensureCACertReadable(options: Options = {}): Promise<void> {
if (!certErrors()) {
return;
}
/**
* on windows, writeProtectedFile left the cert encrypted on *nix, the cert
* has no read permissions either way, openssl will fail and that means we
* have to fix it
*/
try {
const caFileContents = await currentPlatform.readProtectedFile(rootCACertPath);
currentPlatform.deleteProtectedFiles(rootCACertPath);
writeFile(rootCACertPath, caFileContents);
} catch (e) {
return installCertificateAuthority(options);
}
// double check that we have a live one
const remainingErrors = certErrors();
if (remainingErrors) {
return installCertificateAuthority(options);
}
}
/**
* Remove as much of the devcert files and state as we can. This is necessary
* when generating a new root certificate, and should be available to API
* consumers as well.
*
* Not all of it will be removable. If certutil is not installed, we'll leave
* Firefox alone. We try to remove files with maximum permissions, and if that
* fails, we'll silently fail.
*
* It's also possible that the command to untrust will not work, and we'll
* silently fail that as well; with no existing certificates anymore, the
* security exposure there is minimal.
*/
export function uninstall(): void {
currentPlatform.removeFromTrustStores(rootCACertPath);
currentPlatform.deleteProtectedFiles(domainsDir);
currentPlatform.deleteProtectedFiles(rootCADir);
currentPlatform.deleteProtectedFiles(getLegacyConfigDir());
}