acme.js/README.md

356 lines
10 KiB
Markdown
Raw Normal View History

# [ACME.js](https://git.rootprojects.org/root/acme.js) v3
2019-09-04 01:31:09 +00:00
| Built by [Root](https://therootcompany.com) for [Greenlock](https://greenlock.domains)
2019-09-04 01:31:09 +00:00
2019-10-08 10:33:14 +00:00
Free SSL Certificates from Let's Encrypt, for Node.js and Web Browsers
2019-09-04 01:31:09 +00:00
Lightweight. Fast. Modern Crypto. Zero external dependecies.
2018-04-16 01:04:06 +00:00
2019-05-07 07:52:33 +00:00
# Features
2018-03-21 07:35:28 +00:00
2019-05-07 07:52:33 +00:00
| 15k gzipped | 55k minified | 88k (2,500 loc) source with comments |
2019-06-14 07:32:54 +00:00
The primary goal of this library is to make it easy to
get Accounts and Certificates through Let's Encrypt.
2018-04-16 01:04:06 +00:00
- [x] Let's Encrypt v2 / ACME RFC 8555 (November 2019)
- [x] POST-as-GET support
2019-10-08 10:33:14 +00:00
- [x] Secure support for EC and RSA for account and server keys
- [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations
- [ ] (in-progress) StartTLS Everywhere™
2019-10-08 10:33:14 +00:00
- [x] Supports International Domain Names (i.e. `.中国`)
- [x] Works with any [generic ACME challenge handler](https://git.rootprojects.org/root/acme-challenge-test.js)
- [x] **http-01** for single or multiple domains per certificate
- [x] **dns-01** for wildcards, localhost, private networks, etc
2019-10-08 10:33:14 +00:00
- [x] VanillaJS, Zero External Dependencies
- [x] Safe, Efficient, Maintained
2019-10-08 21:13:13 +00:00
- [x] Node.js\* (v6+)
2019-10-08 10:33:14 +00:00
- [x] WebPack
- [x] Online Demo
- See https://greenlock.domains
2019-06-14 07:32:54 +00:00
2019-10-08 21:13:13 +00:00
\* Although we use `async/await` in the examples, the code is written in CommonJS,
with Promises, so you can use it in Node.js and Browsers without transpiling.
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
# Want Quick and Easy?
2019-10-08 10:33:14 +00:00
ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
2018-04-16 01:04:06 +00:00
2019-10-08 10:33:14 +00:00
If you're looking for maximum convenience, try
[Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
2018-04-16 01:04:06 +00:00
2019-10-08 10:33:14 +00:00
- <https://git.rootprojects.org/root/greenlock-express.js>
2018-04-16 01:04:06 +00:00
2019-05-07 07:52:33 +00:00
# Online Demos
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
- Greenlock for the Web <https://greenlock.domains>
- ACME.js Demo <https://rootprojects.org/acme/>
2019-06-14 07:32:54 +00:00
2019-05-07 07:52:33 +00:00
We expect that our hosted versions will meet all of yours needs.
If they don't, please open an issue to let us know why.
2019-06-14 07:32:54 +00:00
2019-05-07 07:52:33 +00:00
We'd much rather improve the app than have a hundred different versions running in the wild.
However, in keeping to our values we've made the source visible for others to inspect, improve, and modify.
2019-06-14 07:32:54 +00:00
2019-05-07 07:52:33 +00:00
# QuickStart
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
To make it easy to generate, encode, and decode keys and certificates,
ACME.js uses [Keypairs.js](https://git.rootprojects.org/root/keypairs.js)
and [CSR.js](https://git.rootprojects.org/root/csr.js)
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
## Node.js
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
```js
var ACME = require('@root/acme');
```
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
## WebPack
2019-06-14 07:32:54 +00:00
2019-10-08 19:07:57 +00:00
```html
<meta charset="UTF-8" />
```
2019-06-14 07:32:54 +00:00
2019-10-08 19:07:57 +00:00
(necessary in case the webserver headers don't specify `plain/text; charset="UTF-8"`)
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
```js
var ACME = require('@root/acme');
2019-06-14 07:32:54 +00:00
```
2019-10-08 10:33:14 +00:00
## Vanilla JS
2019-10-08 19:07:57 +00:00
```html
<meta charset="UTF-8" />
2019-06-14 07:32:54 +00:00
```
2019-10-08 19:07:57 +00:00
(necessary in case the webserver headers don't specify `plain/text; charset="UTF-8"`)
2019-10-08 10:33:14 +00:00
`acme.js`
2019-05-07 07:52:33 +00:00
```html
2019-10-08 19:07:57 +00:00
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.js"></script>
2019-06-14 07:32:54 +00:00
```
2019-05-07 07:52:33 +00:00
2019-10-08 10:33:14 +00:00
`acme.min.js`
2019-05-07 07:52:33 +00:00
```html
2019-10-08 19:07:57 +00:00
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.min.js"></script>
2019-06-14 07:32:54 +00:00
```
2019-10-08 19:07:57 +00:00
Use
2018-04-16 01:04:06 +00:00
2019-10-08 19:07:57 +00:00
```js
var ACME = window['@root/acme'];
2019-05-07 07:52:33 +00:00
```
2018-04-16 01:04:06 +00:00
2019-10-08 10:33:14 +00:00
## Examples
2018-04-16 01:04:06 +00:00
2019-10-08 10:33:14 +00:00
You can see `tests/index.js`, `examples/index.html`, `examples/app.js` in the repo for full example usage.
2019-05-07 07:52:33 +00:00
2019-10-08 10:48:31 +00:00
### Emails: Maintainer vs Subscriber vs Customer
- `maintainerEmail` should be the email address of the **author of the code**.
This person will receive critical security and API change notifications.
- `subscriberEmail` should be the email of the **admin of the hosting service**.
This person agrees to the Let's Encrypt Terms of Service and will be notified
when a certificate fails to renew.
- `customerEmail` should be the email of individual who owns the domain.
This is optional (not currently implemented).
Generally speaking **YOU** are the _maintainer_ and you **or your employer** is the _subscriber_.
If you (or your employer) is running any type of service
you **SHOULD NOT** pass the _customer_ email as the subscriber email.
If you are not running a service (you may be building a CLI, for example),
then you should prompt the user for their email address, and they are the subscriber.
2019-10-08 10:33:14 +00:00
### Instantiate ACME.js
Although built for Let's Encrypt, ACME.js will work with any server
2019-05-07 07:52:33 +00:00
that supports draft-15 of the ACME spec (includes POST-as-GET support).
The `init()` method takes a _directory url_ and initializes internal state according to its response.
```js
2019-10-08 10:48:31 +00:00
var acme = ACME.create({
maintainerEmail: 'jon@example.com'
});
2019-10-08 10:33:14 +00:00
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(
function() {
// Ready to use, show page
$('body').hidden = false;
}
);
2018-04-16 01:04:06 +00:00
```
2019-05-07 07:52:33 +00:00
### Create ACME Account with Let's Encrypt
ACME Accounts are key and device based, with an email address as a backup identifier.
2018-03-15 06:41:00 +00:00
2019-05-07 07:52:33 +00:00
A public account key must be registered before an SSL certificate can be requested.
```js
var accountPrivateKey;
var account;
2019-10-08 10:33:14 +00:00
Keypairs.generate({ kty: 'EC' }).then(function(pair) {
accountPrivateKey = pair.private;
return acme.accounts
.create({
agreeToTerms: function(tos) {
if (
window.confirm(
"Do you agree to the ACME.js and Let's Encrypt Terms of Service?"
)
) {
return Promise.resolve(tos);
}
},
accountKeypair: { privateKeyJwk: pair.private },
2019-10-08 10:48:31 +00:00
subscriberEmail: $('.js-email-input').value
2019-10-08 10:33:14 +00:00
})
.then(function(_account) {
account = _account;
});
2019-05-07 07:52:33 +00:00
});
2018-04-16 01:04:06 +00:00
```
2019-10-08 21:13:13 +00:00
### Generate a Certificate Private Key
2018-04-16 01:04:06 +00:00
2019-10-08 21:13:13 +00:00
```js
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
var pem = await Keypairs.export({
jwk: certKeypair.private,
encoding: 'pem'
});
2018-04-16 01:04:06 +00:00
2019-10-08 21:13:13 +00:00
// This should be saved as `privkey.pem`
console.log(pem);
2018-04-16 01:04:06 +00:00
```
2019-10-08 21:13:13 +00:00
### Generate a CSR
2018-04-16 01:04:06 +00:00
2019-10-08 21:13:13 +00:00
The easiest way to generate a Certificate Signing Request will be either with `openssl` or with `@root/CSR`.
2018-04-16 01:04:06 +00:00
2019-10-08 21:13:13 +00:00
```js
var CSR = require('@root/csr');
var Enc = require('@root/encoding');
// 'subject' should be first in list
var sortedDomains = ['example.com', 'www.example.com'];
var csr = await CSR.csr({
jwk: certKeypair.private,
domains: sortedDomains,
encoding: 'der'
}).then(function(der) {
return Enc.bufToUrlBase64(der);
});
2018-04-16 01:04:06 +00:00
```
2019-10-08 21:13:13 +00:00
2019-05-07 07:52:33 +00:00
### Get Free 90-day SSL Certificate
Creating an ACME "order" for a 90-day SSL certificate requires use of the account private key,
the names of domains to be secured, and a distinctly separate server private key.
A domain ownership verification "challenge" (uploading a file to an unsecured HTTP url or setting a DNS record)
is a required part of the process, which requires `set` and `remove` callbacks/promises.
```js
2019-10-08 21:13:13 +00:00
var certinfo = await acme.certificates.create({
agreeToTerms: function(tos) {
return tos;
},
account: account,
accountKeypair: { privateKeyJwk: accountPrivateKey },
csr: csr,
domains: sortedDomains,
challenges: challenges, // must be implemented
customerEmail: null,
skipChallengeTests: false,
skipDryRun: false
});
2019-05-07 07:52:33 +00:00
2019-10-08 21:13:13 +00:00
console.log('Got SSL Certificate:');
console.log(results.expires);
2019-10-08 10:33:14 +00:00
2019-10-08 21:13:13 +00:00
// This should be saved as `fullchain.pem`
console.log([results.cert, results.chain].join('\n'));
2018-04-16 01:04:06 +00:00
```
2018-03-15 06:43:41 +00:00
2019-05-07 07:52:33 +00:00
### Example "Challenge" Implementation
2018-04-11 17:34:18 +00:00
2019-05-07 07:52:33 +00:00
Typically here you're just presenting some sort of dialog to the user to ask them to
upload a file or set a DNS record.
It may be possible to do something fancy like using OAuth2 to login to Google Domanis
to set a DNS address, etc, but it seems like that sort of fanciness is probably best
reserved for server-side plugins.
```js
var challenges = {
2019-10-08 10:33:14 +00:00
'http-01': {
set: function(opts) {
console.info('http-01 set challenge:');
console.info(opts.challengeUrl);
console.info(opts.keyAuthorization);
while (
!window.confirm('Upload the challenge file before continuing.')
) {}
return Promise.resolve();
},
remove: function(opts) {
console.log('http-01 remove challenge:', opts.challengeUrl);
return Promise.resolve();
}
}
2019-05-07 07:52:33 +00:00
};
```
2018-04-11 17:34:18 +00:00
2019-10-08 10:33:14 +00:00
# IDN - International Domain Names
2018-04-11 17:34:18 +00:00
2019-10-08 10:33:14 +00:00
Convert domain names to `punycode` before creating the certificate:
2019-06-13 07:55:25 +00:00
```js
2019-10-08 10:33:14 +00:00
var punycode = require('punycode');
2019-06-14 07:32:54 +00:00
2019-10-08 10:33:14 +00:00
acme.certificates.create({
// ...
domains: ['example.com', 'www.example.com'].map(function(name) {
return punycode.toASCII(name);
})
});
2019-10-08 10:33:14 +00:00
```
2019-10-08 10:33:14 +00:00
The punycode library itself is lightweight and dependency-free.
It is available both in node and for browsers.
2018-04-20 07:48:17 +00:00
2019-10-08 19:07:57 +00:00
# Testing
2019-10-08 19:07:57 +00:00
You will need to use one of the [`acme-dns-01-*` plugins](https://www.npmjs.com/search?q=acme-dns-01-)
to run the test locally.
2019-10-08 19:07:57 +00:00
You'll also need a `.env` that looks something like the one in `examples/example.env`:
2019-06-14 07:32:54 +00:00
2019-10-08 19:07:57 +00:00
```bash
ENV=DEV
SUBSCRIBER_EMAIL=letsencrypt+staging@example.com
BASE_DOMAIN=test.example.com
CHALLENGE_TYPE=dns-01
CHALLENGE_PLUGIN=acme-dns-01-digitalocean
CHALLENGE_OPTIONS='{"token":"xxxxxxxxxxxx"}'
```
2019-06-14 07:32:54 +00:00
2019-10-08 19:07:57 +00:00
For example:
2019-05-07 07:52:33 +00:00
2019-10-08 19:07:57 +00:00
```bash
# Get the repo and change directories into it
git clone https://git.rootprojects.org/root/bluecrypt-acme.js
pushd bluecrypt-acme.js/
2019-05-07 07:52:33 +00:00
2019-10-08 19:07:57 +00:00
# Install the challenge plugin you'll use for the tests
npm install --save-dev acme-dns-01-digitalocean
# Copy the sample .env file
rsync -av examples/example.env .env
# Edit the config file to use a domain in your account, and your API token
#vim .env
code .env
# Run the tests
node tests/index.js
```
2019-05-07 07:52:33 +00:00
# Developing
You can see `<script>` tags in the `index.html` in the repo, which references the original
source files.
Join `@rootprojects` `#general` on [Keybase](https://keybase.io) if you'd like to chat with us.
# Commercial Support
We have both commercial support and commercial licensing available.
You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem,
Enterprise, and Internal installations, integrations, and deployments.
We also offer consulting for all-things-ACME and Let's Encrypt.
# Legal &amp; Rules of the Road
2019-10-08 10:33:14 +00:00
Greenlock&trade; is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal
2019-05-07 07:52:33 +00:00
The rule of thumb is "attribute, but don't confuse". For example:
2019-10-08 10:33:14 +00:00
> Built with [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js) (a [Root](https://rootprojects.org) project).
2019-05-07 07:52:33 +00:00
Please [contact us](mailto:aj@therootcompany.com) if have any questions in regards to our trademark,
2019-05-09 20:57:17 +00:00
attribution, and/or visible source policies. We want to build great software and a great community.
2019-05-07 07:52:33 +00:00
2019-10-08 10:33:14 +00:00
[ACME.js](https://git.rootprojects.org/root/acme.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)