/
index.ts
140 lines (122 loc) · 5.26 KB
/
index.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
import { readFileSync as readFile, readdirSync as readdir, existsSync as exists } from 'fs';
import createDebug from 'debug';
import { sync as commandExists } from 'command-exists';
import rimraf from 'rimraf';
import {
isMac,
isLinux,
isWindows,
pathForDomain,
getStableDomainPath,
domainsDir,
rootCAKeyPath,
rootCACertPath,
} from './constants';
import currentPlatform from './platforms';
import installCertificateAuthority, { ensureCACertReadable, uninstall } from './certificate-authority';
import generateDomainCertificate from './certificates';
import UI, { UserInterface } from './user-interface';
import isValidDomain from 'is-valid-domain';
export { uninstall };
const debug = createDebug('devcert');
export interface Options /* extends Partial<ICaBufferOpts & ICaPathOpts> */ {
/** Return the CA certificate data? */
getCaBuffer?: boolean;
/** Return the path to the CA certificate? */
getCaPath?: boolean;
/** If `certutil` is not installed already (for updating nss databases; e.g. firefox), do not attempt to install it */
skipCertutilInstall?: boolean,
/** Do not update your systems host file with the domain name of the certificate */
skipHostsFile?: boolean,
/** User interface hooks */
ui?: UserInterface
}
interface ICaBuffer {
ca: Buffer;
}
interface ICaPath {
caPath: string;
}
interface IDomainData {
key: Buffer;
cert: Buffer;
}
type IReturnCa<O extends Options> = O['getCaBuffer'] extends true ? ICaBuffer : false;
type IReturnCaPath<O extends Options> = O['getCaPath'] extends true ? ICaPath : false;
type IReturnData<O extends Options = {}> = (IDomainData) & (IReturnCa<O>) & (IReturnCaPath<O>);
/**
* Request an SSL certificate for the given app name signed by the devcert root
* certificate authority. If devcert has previously generated a certificate for
* that app name on this machine, it will reuse that certificate.
*
* If this is the first time devcert is being run on this machine, it will
* generate and attempt to install a root certificate authority.
*
* Returns a promise that resolves with { key, cert }, where `key` and `cert`
* are Buffers with the contents of the certificate private key and certificate
* file, respectively
*
* If `options.getCaBuffer` is true, return value will include the ca certificate data
* as { ca: Buffer }
*
* If `options.getCaPath` is true, return value will include the ca certificate path
* as { caPath: string }
*/
export async function certificateFor<O extends Options>(requestedDomains: string | string[], options: O = {} as O): Promise<IReturnData<O>> {
const domains = Array.isArray(requestedDomains) ? requestedDomains : [requestedDomains];
domains.forEach((domain) => {
if (!isValidDomain(domain, { subdomain: false, wildcard: false, allowUnicode: true, topLevel: false })) {
throw new Error(`"${domain}" is not a valid domain name.`);
}
});
const domainPath = getStableDomainPath(domains);
debug(`Certificate requested for ${domains}. Skipping certutil install: ${Boolean(options.skipCertutilInstall)}. Skipping hosts file: ${Boolean(options.skipHostsFile)}`);
if (options.ui) {
Object.assign(UI, options.ui);
}
if (!isMac && !isLinux && !isWindows) {
throw new Error(`Platform not supported: "${process.platform}"`);
}
if (!commandExists('openssl')) {
throw new Error('OpenSSL not found: OpenSSL is required to generate SSL certificates - make sure it is installed and available in your PATH');
}
let domainKeyPath = pathForDomain(domainPath, `private-key.key`);
let domainCertPath = pathForDomain(domainPath, `certificate.crt`);
if (!exists(rootCAKeyPath)) {
debug('Root CA is not installed yet, so it must be our first run. Installing root CA ...');
await installCertificateAuthority(options);
} else if (options.getCaBuffer || options.getCaPath) {
debug('Root CA is not readable, but it probably is because an earlier version of devcert locked it. Trying to fix...');
await ensureCACertReadable(options);
}
if (!exists(pathForDomain(domainPath, `certificate.crt`))) {
debug(`Can't find certificate file for ${domains}, so it must be the first request for ${domains}. Generating and caching ...`);
await generateDomainCertificate(domains);
}
if (!options.skipHostsFile) {
domains.forEach(async (domain) => {
await currentPlatform.addDomainToHostFileIfMissing(domain);
})
}
debug(`Returning domain certificate`);
const ret = {
key: readFile(domainKeyPath),
cert: readFile(domainCertPath)
} as IReturnData<O>;
if (options.getCaBuffer) (ret as unknown as ICaBuffer).ca = readFile(rootCACertPath);
if (options.getCaPath) (ret as unknown as ICaPath).caPath = rootCACertPath;
return ret;
}
export function hasCertificateFor(requestedDomains: string | string[]) {
const domains = Array.isArray(requestedDomains) ? requestedDomains : [requestedDomains];
const domainPath = getStableDomainPath(domains);
return exists(pathForDomain(domainPath, `certificate.crt`));
}
export function configuredDomains() {
return readdir(domainsDir);
}
export function removeDomain(requestedDomains: string | string[]) {
const domains = Array.isArray(requestedDomains) ? requestedDomains : [requestedDomains];
const domainPath = getStableDomainPath(domains);
return rimraf.sync(pathForDomain(domainPath));
}