UNPKG

11.9 kBMarkdownView Raw
1# devcert - Development SSL made easy
2
3So, running a local HTTPS server usually sucks. There's a range of
4approaches, each with their own tradeoff. The common one, using self-signed
5certificates, means having to ignore scary browser warnings for each project.
6
7devcert makes the process easy. Want a private key and certificate file to
8use with your server? Just ask:
9
10```js
11let ssl = await devcert.certificateFor('my-app.test');
12https.createServer(ssl, app).listen(3000);
13```
14
15Now open https://my-app.test:3000 and voila - your page loads with no scary
16warnings or hoops to jump through.
17
18> Certificates are cached by name, so two calls for
19`certificateFor('foo')` will return the same key and certificate.
20
21## Options
22
23### skipHostsFile
24
25If you supply a custom domain name (i.e. any domain other than `localhost`)
26when requesting a certificate from devcert, it will attempt to modify your
27system to redirect requests for that domain to your local machine (rather
28than to the real domain). It does this by modifying your `/etc/hosts` file.
29
30If you pass in the `skipHostsFile` option, devcert will skip this step. This
31means that if you ask for certificates for `my-app.test` (for example), and
32don't have some other DNS redirect method in place, that you won't be able to
33access your app at `https://my-app.test` because your computer wouldn't know
34that `my-app.test` should resolve your local machine.
35
36Keep in mind that SSL certificates are issued for _domains_, so if you ask
37for a certificate for `my-app.test`, and don't have any kind of DNS redirect
38in place (`/etc/hosts` or otherwise), trying to hit `localhost` won't work,
39even if the app you intended to serve via `my-app.test` is running on your
40local machine (since the SSL certificate won't say `localhost`).
41
42### skipCertutil
43
44This option will tell devcert to avoid installing `certutil` tooling.
45
46`certutil` is a tooling package used to automated the installation of SSL
47certificates in certain circumstances; specifically, Firefox (for every OS)
48and Chrome (on Linux only).
49
50Normally, devcert will attempt to install `certutil` if it's need and not
51already present on your system. If don't want devcert to install this
52package, pass `skipCertutil: true`.
53
54If you decide to `skipCertutil`, the initial setup process for devcert
55changes in these two scenarios:
56
57* **Firefox on all platforms**: Thankully, Firefox makes this easy. There's a
58 point-and-click wizard for importing and trusting a certificate, so if you
59 specify `skipCertutil: true`, devcert will instead automatically open Firefox
60 and kick off this wizard for you. Simply follow the prompts to trust the
61 certificate. **Reminder: you'll only need to do this once per machine**
62
63* **Chrome on Linux**: Unfortunately, it appears that the **only** way to get
64 Chrome to trust an SSL certificate on Linux is via the `certutil` tooling -
65 there is no manual process for it. Thus, if you are using Chrome on Linux, do
66 **not** supply `skipCertuil: true`. If you do, devcert certificates will not
67 be trusted by Chrome.
68
69The `certutil` tooling is installed in OS-specific ways:
70
71* Mac: `brew install nss`
72* Linux: `apt install libnss3-tools`
73* Windows: N/A (there is no easy, hands-off way to install certutil on Windows,
74 so devcert will simply fallback to the wizard approach for Firefox outlined
75 above)
76
77## How it works
78
79When you ask for a development certificate, devcert will first check to see
80if it has run on this machine before. If not, it will create a root
81certificate authority and add it to your OS and various browser trust stores.
82You'll likely see password prompts from your OS at this point to authorize
83the new root CA.
84
85Since your machine now trusts this root CA, it will trust any certificates
86signed by it. So when you ask for a certificate for a new domain, devcert
87will use the root CA credentials to generate a certificate specific to the
88domain you requested, and returns the new certificate to you.
89
90If you request a domain that has already had certificates generated for it,
91devcert will simply return the cached certificates.
92
93This setup ensures that browsers won't show scary warnings about untrusted
94certificates, since your OS and browsers will now trust devcert's
95certificates.
96
97## Security Concerns
98
99There's a reason that your OS prompts you for your root password when devcert
100attempts to install it's root certificate authority. By adding it to your
101machine's trust stores, your browsers will automatically trust _any_ certificate
102generated with it.
103
104This exposes a potential attack vector on your local machine: if someone else
105could use the devcert certificate authority to generate certificates, and if
106they could intercept / manipulate your network traffic, they could theoretically
107impersonate some websites, and your browser would not show any warnings (because
108it trusts the devcert authority).
109
110To prevent this, devcert takes steps to ensure that no one can access the
111devcert certificate authority credentials to generate malicious certificates
112without you knowing. The exact approach varies by platform:
113
114* **macOS and Linux**: the certificate authority's credentials are written to files that are only readable by the root user (i.e. `chown 0 ca-cert.crt` and
115`chmod 600 ca-cert.crt`). When devcert itself needs these, it shells out to
116`sudo` invocations to read / write the credentials.
117* **Windows**: because of my unfamiliarity with Windows file permissions, I
118wasn't confident I would be able to correctly set permissions to mimic the setup
119on macOS and Linux. So instead, devcert will prompt you for a password, and then
120use that to encrypt the credentials with an AES256 cipher. The password is never
121written to disk.
122
123To further protect these credentials, any time they are written to disk, they
124are written to temporary files, and are immediately deleted after they are no longer needed.
125
126Additionally, the root CA certificate is unique to your machine only: it's
127generated on-the-fly when it is first installed. ensuring there are no
128central / shared keys to crack across machines.
129
130### Why install a root certificate authority at all?
131
132The root certificate authority makes it simpler to manage which domains are
133configured for SSL by devcert. The alternative is to generate and trust
134self-signed certificates for each domain. The problem is that while devcert
135is able to add a certificate to your machine's trust stores, the tooling to
136remove a certificate doesn't cover every case. So if you ever wanted to
137_untrust_ devcert's certificates, you'd have to manually remove each one from
138each trust store.
139
140By trusting only a single root CA, devcert is able to guarantee that when you
141want to _disable_ SSL for a domain, it can do so with no manual intervention
142- we just delete the domain-specific certificate files. Since these
143domain-specific files aren't installed in your trust stores, once they are
144gone, they are gone.
145
146
147## Integration
148
149devcert has been designed from day one to work as low-level library that other
150tools can delegate to. The goal is to make HTTPS development easy for everyone,
151regardless of framework or library choice.
152
153With that in mind, if you'd like to use devcert in your library/framework/CLI,
154devcert makes that easy.
155
156In addition to the options above, devcert exposes a `ui` option. This option
157allows you to control all the points where devcert requries user interaction,
158substituting your own prompts and user interface. You can use this to brand
159the experience with your own tool's name, localize the messages, or integrate
160devcert into a larger existing workflow.
161
162The `ui` option should be an object with the following methods:
163
164```ts
165{
166 async getWindowsEncryptionPassword(): Promise<string> {
167 // Invoked when devcert needs the password used to encrypt the root
168 // certificate authority credentials on Windows. May be invoked multiple
169 // times if the user's supplied password is incorrect
170 },
171 async warnChromeOnLinuxWithoutCertutil(): Promise<string> {
172 // Invoked when devcert is run on Linux, detects that Chrome is installed,
173 // and the `skipCertutil` option is `true`. Used to warn the user that
174 // Chrome will not work with `skipCertutil: true` on Linux.
175 },
176 async closeFirefoxBeforeContinuing() {
177 // Invoked when devcert detects that Firefox is running while it is trying
178 // to programmatically install it's certificate authority in the Firefox
179 // trust store. Firefox appears to overwrite changes to the trust store on
180 // exit, so Firefox must be closed before devcert can continue. devcert will
181 // wait for Firefox to exit - this is just to prompt the user that they
182 // need to close the application.
183 },
184 async startFirefoxWizard(certificateHost: string) {
185 // Invoked when devcert detects a Firefox installation and `skipCertutil:
186 // true` was specified. This is invoked right before devcert launches the
187 // Firefox certificate import wizard GUI. Used to give the user a heads up
188 // as to why they are about to see Firefox pop up.
189 //
190 // The certificateHost provided is the URL for the temporary server that
191 // devcert has spun up in order to trigger the wizard(Firefox needs try to
192 // "download" the cert to trigger the wizard). This URL will load the page
193 // supplied in the `firefoxWizardPromptPage()` method below.
194 //
195 // Normally, devcert will automatically open this URL, but in case it fails
196 // you may want to print it out to the console with an explanatory message
197 // so the user isn't left hanging wondering what's happening.
198 },
199 async firefoxWizardPromptPage(certificateURL: string): Promise<string> {
200 // When devcert starts the Firefox certificate installation wizard GUI, it
201 // first loads an HTML page in Firefox. The template used for that page is
202 // the return value of this method. The supplied certificateURL is the path
203 // to the actual certificate. The Firefox tab must attempt to load this URL
204 // to trigger the wizard.
205 //
206 // The default implemenation is a simple redirect to that URL. But you could
207 // supply your own branded template here, with a button that says "Install
208 // certificate" that is linked to the certificateURL, along with a more in
209 // depth explanation of what is happening for example.
210 }
211 async waitForFirefoxWizard() {
212 // Invoked _after_ the Firefox certificate import wizard is kicked off. This
213 // method should not resolve until the user indicates that the wizard is
214 // complete (unfortunately, we have no way of determining that
215 // programmatically)
216 }
217}
218```
219
220You can supply any or all of these methods - ones you do not supply will fall
221back to the default implemenation.
222
223## Testing
224
225Testing a tool like devcert can be a pain. I haven't found a good automated
226solution for cross platform GUI testing (the GUI part is necessary to test
227each browser's handling of devcert certificates, as well as test the Firefox
228wizard flow).
229
230To make things easier, devcert comes with a series of virtual machine images. Each one is a snapshot taken right before running a test - just launch the machine and hit <Enter>.
231
232You can also use the snapshotted state of the VMs to roll them back to a
233pristine state for another round of testing.
234
235> **Note**: Be aware that the macOS license terms prohibit running it on
236> non-Apple hardware, so you must own a Mac to test that platform. If you don't
237> own a Mac - that's okay, just mention in the PR that you were unable to test
238> on a Mac and we're happy to test it for you.
239
240### Virtual Machine Snapshots
241
242* [macOS](https://s3-us-west-1.amazonaws.com/devcert-test-snapshots/macOS.pvm.zip)
243* [Windows](https://s3-us-west-1.amazonaws.com/devcert-test-snapshots/MSEdge+-+Win10.zip)
244* [Ubuntu](https://s3-us-west-1.amazonaws.com/devcert-test-snapshots/Ubuntu+Linux.zip)
245
246## License
247
248MIT © [Dave Wasmer](http://davewasmer.com)