greenlock.js/README.md

332 lines
9.9 KiB
Markdown
Raw Normal View History

2015-12-11 11:23:47 +00:00
letsencrypt
===========
Let's Encrypt for node.js
2015-12-13 05:03:48 +00:00
This enables you to get Free SSL Certificates for Automatic HTTPS.
2015-12-11 11:23:47 +00:00
2015-12-13 01:05:03 +00:00
#### NOT YET PUBLISHED
2015-12-11 11:23:47 +00:00
2015-12-12 15:52:55 +00:00
* Dec 12 2015: gettin' really close
* Dec 11 2015: almost done (node-letsencrypt-python complete)
* Dec 10 2015: began tinkering
2015-12-12 13:11:05 +00:00
Install
=======
```bash
npm install --save letsencrypt
```
Right now this uses [`letsencrypt-python`](https://github.com/Daplie/node-letsencrypt-python),
but it's built to be able to use a pure javasript version.
```bash
# install the python client (takes 2 minutes normally, 20 on a rasberry pi)
git clone https://github.com/letsencrypt/letsencrypt
pushd letsencrypt
./letsencrypt-auto
```
2015-12-12 22:16:02 +00:00
Usage Examples
========
Here's a small snippet:
2015-12-12 22:17:32 +00:00
```javascript
2015-12-12 22:16:02 +00:00
le.register({
domains: ['example.com', 'www.example.com']
, email: 'user@example.com'
, agreeTos: true
2015-12-13 01:04:12 +00:00
, webrootPath: '/srv/www/example.com/public'
2015-12-12 22:16:02 +00:00
}, function (err, certs) {
// do stuff
});
```
**However**, due to the nature of what this library does, it has a few more "moving parts"
2015-12-12 22:16:44 +00:00
than what makes sense to show in a minimal snippet.
2015-12-12 22:16:02 +00:00
* [commandline (standalone with "webroot")](https://github.com/Daplie/node-letsencrypt/blob/master/examples/commandline.js)
* [expressjs (fully automatic https)](https://github.com/Daplie/node-letsencrypt/blob/master/examples/express.js)
2015-12-13 06:00:30 +00:00
### non-root
If you want to run this as non-root, you can.
You just have to set node to be allowed to use root ports
```
# node
sudo setcap cap_net_bind_service=+ep /usr/local/bin/node
```
and then make sure to set all of of the following to a directory that your user is permitted to write to
* `webrootPath`
* `configDir`
* `workDir` (python backend only)
* `logsDir` (python backend only)
2015-12-12 22:06:36 +00:00
See Also
========
2015-12-12 22:16:02 +00:00
* See [Examples](https://github.com/Daplie/node-letsencrypt/tree/master/examples)
2015-12-12 22:06:36 +00:00
* [Let's Encrypt in (exactly) 90 seconds with Caddy](https://daplie.com/articles/lets-encrypt-in-literally-90-seconds/)
* [lego](https://github.com/xenolf/lego): Let's Encrypt for golang
2015-12-12 22:16:02 +00:00
API
===
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
* `LetsEncrypt.create(backend, bkDefaults, handlers)`
* `le.middleware()`
* `le.sniCallback(hostname, function (err, tlsContext) {})`
* `le.register({ domains, email, agreeTos, ... }, cb)`
* `le.fetch({domains, email, agreeTos, ... }, cb)`
* `le.validate(domains, cb)`
2015-12-13 05:03:48 +00:00
* `le.registrationFailureCallback(err, args, certInfo, cb)`
2015-12-12 15:38:14 +00:00
2015-12-12 22:16:02 +00:00
### `LetsEncrypt.create(backend, bkDefaults, handlers)`
2015-12-12 22:06:36 +00:00
#### backend
2015-12-12 15:38:14 +00:00
2015-12-12 22:06:36 +00:00
Currently only `letsencrypt-python` is supported, but we plan to work on
native javascript support in February or so (when ECDSA keys are available).
2015-12-12 15:38:14 +00:00
2015-12-12 22:06:36 +00:00
If you'd like to help with that, see **how to write a backend** below and also
look at the wrapper `backend-python.js`.
**Example**:
```javascript
{ fetch: function (args, cb) {
// cb(err) when there is an actual error (db, fs, etc)
// cb(null, null) when the certificate was NOT available on disk
// cb(null, { cert: '<fullchain.pem>', key: '<privkey.pem>', renewedAt: 0, duration: 0 }) cert + meta
}
, register: function (args, setChallenge, cb) {
// setChallenge(hostnames, key, value, cb) when a challenge needs to be set
// cb(err) when there is an error
// cb(null, null) when the registration is successful, but fetch still needs to be called
// cb(null, cert /*see above*/) if registration can easily return the same as fetch
}
2015-12-12 22:18:18 +00:00
}
2015-12-12 15:38:14 +00:00
```
2015-12-12 22:06:36 +00:00
#### bkDefualts
The arguments passed here (typically `webpathRoot`, `configDir`, etc) will be merged with
2015-12-12 22:19:28 +00:00
any `args` (typically `domains`, `email`, and `agreeTos`) and passed to the backend whenever
it is called.
2015-12-12 22:06:36 +00:00
Typically the backend wrapper will already merge any necessary backend-specific arguments.
**Example**:
2015-12-12 13:11:05 +00:00
```javascript
2015-12-12 22:06:36 +00:00
{ webrootPath: __dirname, '/acme-challenge'
2015-12-12 13:11:05 +00:00
, fullchainTpl: '/live/:hostname/fullchain.pem'
, privkeyTpl: '/live/:hostname/fullchain.pem'
, configDir: '/etc/letsencrypt'
2015-12-12 22:06:36 +00:00
}
```
2015-12-12 13:11:05 +00:00
2015-12-13 01:04:12 +00:00
Note: `webrootPath` can be set as a default, semi-locally with `webrootPathTpl`, or per
regesitration as `webrootPath` (which overwrites `defaults.webrootPath`).
2015-12-12 22:06:36 +00:00
#### handlers *optional*
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.setChallenge(hostnames, name, value, cb)`:
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
default is to write to fs
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.getChallenge(hostnames, value cb)`
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
default is to read from fs
`h.sniRegisterCallback(args, currentCerts, cb)`
The default is to immediately call `cb(null, null)` and register (or renew) in the background
during the `SNICallback` phase. Right now it isn't reasonable to renew during SNICallback,
but around February when it is possible to use ECDSA keys (as opposed to RSA at present),
registration will take very little time.
This will not be called while another registration is already in progress.
**SECURITY WARNING**: If you use this option with a custom `h.validate()`, make sure that `args.domains`
refers to domains you expect, otherwise an attacker will spoof SNI and cause your server to rate-limit
letsencrypt.org and get blocked. Note that `le.validate()` will check A records before attempting to
register to help prevent such possible attacks.
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.validate(domains, cb)`
When specified this will override `le.validate()`. You will need to do this if the ip address of this
server is not one specified in the A records for your domain.
### `le.middleware()`
An express handler for `/.well-known/acme-challenge/<challenge>`.
Will call `getChallenge([hostname], key, cb)` if present or otherwise read `challenge` from disk.
Example:
```javascript
app.use('/', le.middleware())
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.sniCallback(hostname, function (err, tlsContext) {});`
Will call `fetch`. If fetch does not return certificates or returns expired certificates
it will call `sniRegisterCallback(args, currentCerts, cb)` and then return the error,
the new certificates, or call `fetch` a final time.
Example:
```javascript
2015-12-12 22:21:02 +00:00
var server = require('https').createServer({ SNICallback: le.sniCallback, cert: '...', key: '...' });
2015-12-12 22:06:36 +00:00
server.on('request', app);
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.register({ domains, email, agreeTos, ... }, cb)`
Get certificates for a domain
Example:
2015-12-12 22:17:32 +00:00
```javascript
2015-12-12 22:06:36 +00:00
le.register({
domains: ['example.com', 'www.example.com']
, email: 'user@example.com'
2015-12-13 01:04:12 +00:00
, webrootPath: '/srv/www/example.com/public'
2015-12-12 22:06:36 +00:00
, agreeTos: true
}, function (err, certs) {
// err is some error
console.log(certs);
/*
{ cert: "contents of fullchain.pem"
, key: "contents of privkey.pem"
, renewedAt: <date in milliseconds>
, duration: <duration in milliseconds (90-days)>
2015-12-12 13:11:05 +00:00
}
2015-12-12 22:06:36 +00:00
*/
});
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.isValidDomain(hostname)`
returns `true` if `hostname` is a valid ascii or punycode domain name.
(also exposed on the main exported module as `LetsEncrypt.isValidDomain()`)
### `le.validate(args, cb)`
Used internally, but exposed for convenience. Checks `LetsEncrypt.isValidDomain()`
and then checks to see that the current server
Called before `backend.register()` to validate the following:
* the hostnames don't use any illegal characters
* the server's actual public ip (via api.apiify.org)
* the A records for said hostnames
### `le.fetch(args, cb)`
Used internally, but exposed for convenience.
Checks in-memory cache of certificates for `args.domains` and calls then calls `backend.fetch(args, cb)`
**after** merging `args` if necessary.
2015-12-13 05:03:48 +00:00
### `le.registrationFailureCallback(err, args, certInfo, cb)`
Not yet implemented
2015-12-12 13:11:05 +00:00
Backends
--------
* [`letsencrypt-python`](https://github.com/Daplie/node-letsencrypt-python) (complete)
* [`lejs`](https://github.com/Daplie/node-lejs) (in progress)
#### How to write a backend
A backend must implement (or be wrapped to implement) this API:
2015-12-12 22:22:56 +00:00
* `fetch(hostname, cb)` will cb(err, certs) with certs from disk (or null or error)
* `register(args, challengeCb, done)` will register and or renew a cert
2015-12-12 13:11:05 +00:00
* args = `{ domains, email, agreeTos }` MUST check that agreeTos === true
* challengeCb = `function (challenge, cb) { }` handle challenge as needed, call cb()
This is what `args` looks like:
```javascript
{ domains: ['example.com', 'www.example.com']
, email: 'user@email.com'
, agreeTos: true
, configDir: '/etc/letsencrypt'
, fullchainTpl: '/live/:hostname/fullchain.pem' // :hostname will be replaced with the domainname
2015-12-13 01:04:12 +00:00
, privkeyTpl: '/live/:hostname/privkey.pem'
, webrootPathTpl: '/srv/www/:hostname/public'
, webrootPath: '/srv/www/example.com/public' // templated from webrootPathTpl
2015-12-12 13:11:05 +00:00
}
```
This is what the implementation should look like:
(it's expected that the client will follow the same conventions as
the python client, but it's not necessary)
```javascript
return {
fetch: function (args, cb) {
// NOTE: should return an error if args.domains cannot be satisfied with a single cert
// (usually example.com and www.example.com will be handled on the same cert, for example)
if (errorHappens) {
// return an error if there is an actual error (db, etc)
cb(err);
return;
}
// return null if there is no error, nor a certificate
else if (!cert) {
cb(null, null);
return;
}
// NOTE: if the certificate is available but expired it should be
// returned and the calling application will decide to renew when
// it is convenient
// NOTE: the application should handle caching, not the library
// return the cert with metadata
cb(null, {
cert: "/*contcatonated certs in pem format: cert + intermediate*/"
, key: "/*private keypair in pem format*/"
, renewedAt: new Date() // fs.stat cert.pem should also work
2015-12-12 22:06:36 +00:00
, duration: 90 * 24 * 60 * 60 * 1000 // assumes 90-days unless specified
2015-12-12 13:11:05 +00:00
});
}
, register: function (args, challengeCallback, completeCallback) {
// **MUST** reject if args.agreeTos is not true
// once you're ready for the caller to know the challenge
if (challengeCallback) {
challengeCallback(challenge, function () {
continueRegistration();
})
} else {
continueRegistration();
}
function continueRegistration() {
2015-12-12 22:06:36 +00:00
// it is not necessary to to return the certificates here
2015-12-12 13:11:05 +00:00
// the client will call fetch() when it needs them
completeCallback(err);
}
}
};
```
2015-12-11 11:23:47 +00:00
LICENSE
=======
Dual-licensed MIT and Apache-2.0
See LICENSE