2016-12-30 07:52:44 +00:00
<!-- AD_TPL_BEGIN -->
About Daplie: We're taking back the Internet!
2016-11-25 17:38:39 +00:00
--------------
2016-12-30 07:52:44 +00:00
Down with Google, Apple, and Facebook!
We're re-decentralizing the web and making it read-write again - one home cloud system at a time.
Tired of serving the Empire? Come join the Rebel Alliance:
2016-11-25 17:38:39 +00:00
2016-12-30 07:52:44 +00:00
< a href = "mailto:jobs@daplie.com" > jobs@daplie.com</ a > | [Invest in Daplie on Wefunder ](https://daplie.com/invest/ ) | [Pre-order Cloud ](https://daplie.com/preorder/ ), The World's First Home Server for Everyone
2016-11-25 17:38:39 +00:00
2016-12-30 07:52:44 +00:00
<!-- AD_TPL_END -->
2016-11-25 17:38:39 +00:00
2016-08-08 17:58:08 +00:00
# le-acme-core
2016-08-09 20:02:47 +00:00
Looking for **letiny-core** ? Check the [v1.x branch ](https://github.com/Daplie/le-acme-core/tree/v1.x ).
2016-08-08 18:04:44 +00:00
2016-08-08 17:58:08 +00:00
<!-- rename to le - acme - core -->
2015-12-13 16:50:04 +00:00
2015-12-15 22:07:02 +00:00
A framework for building letsencrypt clients, forked from `letiny` .
2015-12-16 03:33:33 +00:00
Supports all of:
2015-12-15 22:07:02 +00:00
* node with `ursa` (works fast)
2015-12-16 00:58:36 +00:00
* node with `forge` (works on windows)
2015-12-16 03:33:33 +00:00
* browser WebCrypto (not implemented, but... Let's Encrypt over WebRTC anyone?)
2015-12-15 22:07:02 +00:00
* any javascript implementation
2015-12-13 16:50:04 +00:00
2015-12-16 01:33:05 +00:00
### These aren't the droids you're looking for
This is a library / framework for building letsencrypt clients.
You probably want one of these pre-built clients instead:
2015-12-16 05:01:40 +00:00
* [`letsencrypt` ](https://github.com/Daplie/node-letsencrypt ) (compatible with the official client)
* `letiny` (lightweight client cli)
2015-12-17 01:21:03 +00:00
* [`letsencrypt-express` ](https://github.com/Daplie/letsencrypt-express ) (automatic https for express)
2015-12-16 01:33:05 +00:00
2015-12-16 04:46:47 +00:00
## Install & Usage:
2015-12-13 16:50:04 +00:00
2015-12-15 22:07:02 +00:00
```bash
2016-08-08 17:58:08 +00:00
npm install --save le-acme-core
```
To use the default dependencies:
```javascript
'use strict';
var ACME = require('le-acme-core').ACME.create();
```
For **testing** and **development** , you can also inject the dependencies you want to use:
```javascript
'use strict';
var ACME = require('le-acme-core').ACME.create({
request: require('request')
, RSA: require('rsa-compat').RSA
});
// now uses node `request` (could also use jQuery or Angular in the browser)
ACME.getAcmeUrls(discoveryUrl, function (err, urls) {
console.log(urls);
});
2015-12-13 16:50:04 +00:00
```
2015-12-16 00:58:36 +00:00
You will follow these steps to obtain certificates:
* discover ACME registration urls with `getAcmeUrls`
* register a user account with `registerNewAccount`
* implement a method to agree to the terms of service as `agreeToTos`
* get certificates with `getCertificate`
2015-12-16 01:18:40 +00:00
* implement a method to store the challenge token as `setChallenge`
* implement a method to get the challenge token as `getChallenge`
* implement a method to remove the challenge token as `removeChallenge`
2015-12-16 00:58:36 +00:00
2015-12-16 08:06:19 +00:00
### Demo
You can see this working for yourself, but you'll need to be on an internet connected computer with a domain.
Get a temporary domain for testing
```bash
npm install -g ddns-cli
ddns --random --email user@example.com --agree
```
Note: use **YOUR EMAIL** and accept the terms of service (run `ddns --help` to see them).
<!-- TODO tutorial on ddns -->
2016-08-08 17:58:08 +00:00
Install le-acme-core and its dependencies. **Note** : it's okay if you're on windows
2015-12-16 08:06:19 +00:00
and `ursa` fails to compile. It'll still work.
```bash
2016-08-08 17:58:08 +00:00
git clone https://github.com/Daplie/le-acme-core.git ~/le-acme-core
pushd ~/le-acme-core
2015-12-16 08:06:19 +00:00
npm install
```
Run the demo:
2015-12-16 08:15:29 +00:00
```bash
2015-12-16 08:06:19 +00:00
node examples/letsencrypt.js user@example.com example.com
```
Note: use **YOUR TEMPORARY DOMAIN** and **YOUR EMAIL** .
2015-12-16 04:46:47 +00:00
## API
The Goodies
```javascript
// Accounts
2016-08-08 17:58:08 +00:00
ACME.registerNewAccount(options, cb) // returns "regr" registration data
2015-12-16 04:46:47 +00:00
{ newRegUrl: '< url > ' // no defaults, specify acmeUrls.newAuthz
, email: '< email > ' // valid email (server checks MX records)
2016-08-02 00:00:04 +00:00
, accountKeypair: { // privateKeyPem or privateKeyJwt
privateKeyPem: '< ASCII PEM > '
}
2015-12-16 04:46:47 +00:00
, agreeToTerms: fn (tosUrl, cb) {} // must specify agree=tosUrl to continue (or falsey to end)
}
// Registration
2016-08-08 17:58:08 +00:00
ACME.getCertificate(options, cb) // returns (err, pems={ privkey (key), cert, chain (ca) })
2015-12-16 04:46:47 +00:00
{ newAuthzUrl: '< url > ' // specify acmeUrls.newAuthz
, newCertUrl: '< url > ' // specify acmeUrls.newCert
2016-08-02 00:00:04 +00:00
, domainKeypair: {
privateKeyPem: '< ASCII PEM > '
}
, accountKeypair: {
privateKeyPem: '< ASCII PEM > '
}
2015-12-16 04:46:47 +00:00
, domains: ['example.com']
, setChallenge: fn (hostname, key, val, cb)
, removeChallenge: fn (hostname, key, cb)
}
2016-08-01 16:48:24 +00:00
2015-12-16 06:03:02 +00:00
// Discovery URLs
2016-08-08 17:58:08 +00:00
ACME.getAcmeUrls(acmeDiscoveryUrl, cb) // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert})
2015-12-16 04:46:47 +00:00
```
Helpers & Stuff
```javascript
// Constants
2016-08-08 17:58:08 +00:00
ACME.productionServerUrl // https://acme-v01.api.letsencrypt.org/directory
ACME.stagingServerUrl // https://acme-staging.api.letsencrypt.org/directory
ACME.acmeChallengePrefix // /.well-known/acme-challenge/
ACME.knownEndpoints // new-authz, new-cert, new-reg, revoke-cert
2015-12-16 04:46:47 +00:00
// HTTP Client Helpers
2016-08-08 17:58:08 +00:00
ACME.Acme // Signs requests with JWK
2016-08-02 00:31:34 +00:00
acme = new Acme(keypair) // 'keypair' is an object with `privateKeyPem` and/or `privateKeyJwk`
2015-12-16 04:46:47 +00:00
acme.post(url, body, cb) // POST with signature
acme.parseLinks(link) // (internal) parses 'link' header
acme.getNonce(url, cb) // (internal) HEAD request to get 'replay-nonce' strings
```
## Example
Below you'll find a stripped-down example. You can see the full example in the example folder.
2016-08-08 17:58:08 +00:00
* [example/ ](https://github.com/Daplie/le-acme-core/blob/master/example/ )
2015-12-16 04:46:47 +00:00
2015-12-16 04:18:01 +00:00
#### Register Account & Domain
2015-12-16 04:46:47 +00:00
This is how you **register an ACME account** and **get an HTTPS certificate**
2015-12-15 22:07:02 +00:00
```javascript
'use strict';
2016-08-08 17:58:08 +00:00
var ACME = require('le-acme-core').ACME.create();
2016-08-02 00:00:04 +00:00
var RSA = require('rsa-compat').RSA;
2015-12-16 01:18:40 +00:00
2015-12-16 04:32:40 +00:00
var email = 'user@example.com'; // CHANGE TO YOUR EMAIL
var domains = 'example.com'; // CHANGE TO YOUR DOMAIN
2016-08-08 17:58:08 +00:00
var acmeDiscoveryUrl = ACME.stagingServerUrl; // CHANGE to production, when ready
2015-12-16 04:32:40 +00:00
2016-08-02 00:00:04 +00:00
var accountKeypair = null; // { privateKeyPem: null, privateKeyJwk: null };
var domainKeypair = null; // same as above
2015-12-16 04:32:40 +00:00
var acmeUrls = null;
2016-08-02 00:00:04 +00:00
RSA.generateKeypair(2048, 65537, function (err, keypair) {
accountKeypair = keypair;
2015-12-16 04:32:40 +00:00
// ...
2016-08-08 17:58:08 +00:00
ACME.getAcmeUrls(acmeDiscoveryUrl, function (err, urls) {
2015-12-16 04:32:40 +00:00
// ...
runDemo();
});
});
function runDemo() {
2016-08-08 17:58:08 +00:00
ACME.registerNewAccount(
2015-12-16 04:32:40 +00:00
{ newRegUrl: acmeUrls.newReg
, email: email
2016-08-02 00:00:04 +00:00
, accountKeypair: accountKeypair
2015-12-16 04:32:40 +00:00
, agreeToTerms: function (tosUrl, done) {
// agree to the exact version of these terms
done(null, tosUrl);
}
2015-12-16 00:51:44 +00:00
}
2015-12-16 04:32:40 +00:00
, function (err, regr) {
2015-12-16 00:51:44 +00:00
2016-08-08 17:58:08 +00:00
ACME.getCertificate(
2015-12-16 04:32:40 +00:00
{ newAuthzUrl: acmeUrls.newAuthz
, newCertUrl: acmeUrls.newCert
2015-12-16 00:51:44 +00:00
2016-08-02 00:00:04 +00:00
, domainKeypair: domainKeypair
, accountKeypair: accountKeypair
2015-12-16 04:32:40 +00:00
, domains: domains
2015-12-16 03:23:34 +00:00
2015-12-16 04:32:40 +00:00
, setChallenge: challengeStore.set
, removeChallenge: challengeStore.remove
}
, function (err, certs) {
2015-12-16 03:23:34 +00:00
2015-12-16 04:32:40 +00:00
// Note: you should save certs to disk (or db)
certStore.set(domains[0], certs, function () {
2015-12-16 00:51:44 +00:00
2015-12-16 04:32:40 +00:00
// ...
});
2015-12-16 00:51:44 +00:00
2015-12-16 04:32:40 +00:00
}
);
}
2015-12-16 00:51:44 +00:00
);
2015-12-16 04:32:40 +00:00
}
2015-12-13 16:50:04 +00:00
```
2015-12-16 08:17:33 +00:00
**But wait**, there's more!
2016-08-08 17:58:08 +00:00
See [example/letsencrypt.js ](https://github.com/Daplie/le-acme-core/blob/master/example/letsencrypt.js )
2015-12-16 08:17:33 +00:00
2015-12-16 04:18:01 +00:00
#### Run a Server on 80, 443, and 5001 (https/tls)
2015-12-16 01:18:40 +00:00
That will fail unless you have a webserver running on 80 and 443 (or 5001)
to respond to `/.well-known/acme-challenge/xxxxxxxx` with the proper token
```javascript
2015-12-16 04:32:40 +00:00
var https = require('https');
2015-12-16 01:18:40 +00:00
var http = require('http');
2015-12-16 04:32:40 +00:00
var LeCore = deps.LeCore;
var httpsOptions = deps.httpsOptions;
var challengeStore = deps.challengeStore;
var certStore = deps.certStore;
//
// Challenge Handler
//
2015-12-16 01:18:40 +00:00
function acmeResponder(req, res) {
2015-12-16 04:32:40 +00:00
if (0 !== req.url.indexOf(LeCore.acmeChallengePrefix)) {
2015-12-16 01:18:40 +00:00
res.end('Hello World!');
return;
}
2015-12-16 04:32:40 +00:00
var key = req.url.slice(LeCore.acmeChallengePrefix.length);
challengeStore.get(req.hostname, key, function (err, val) {
res.end(val || 'Error');
});
2015-12-16 01:18:40 +00:00
}
2015-12-16 04:32:40 +00:00
//
// Server
//
https.createServer(httpsOptions, acmeResponder).listen(5001, function () {
console.log('Listening https on', this.address());
});
http.createServer(acmeResponder).listen(80, function () {
console.log('Listening http on', this.address());
});
2015-12-16 01:18:40 +00:00
```
2015-12-16 08:17:33 +00:00
**But wait**, there's more!
2016-08-08 17:58:08 +00:00
See [example/serve.js ](https://github.com/Daplie/le-acme-core/blob/master/example/serve.js )
2015-12-16 08:17:33 +00:00
2015-12-16 04:32:40 +00:00
#### Put some storage in place
2015-12-16 01:18:40 +00:00
Finally, you need an implementation of `challengeStore` :
```javascript
var challengeCache = {};
var challengeStore = {
set: function (hostname, key, value, cb) {
challengeCache[key] = value;
cb(null);
}
, get: function (hostname, key, cb) {
cb(null, challengeCache[key]);
}
, remove: function (hostname, key, cb) {
delete challengeCache[key];
cb(null);
}
};
2015-12-16 04:32:40 +00:00
var certCache = {};
var certStore = {
set: function (hostname, certs, cb) {
certCache[hostname] = certs;
cb(null);
}
, get: function (hostname, cb) {
cb(null, certCache[hostname]);
}
, remove: function (hostname, cb) {
delete certCache[hostname];
cb(null);
}
};
2015-12-16 01:18:40 +00:00
```
2015-12-16 08:17:33 +00:00
**But wait**, there's more!
See
2016-08-08 17:58:08 +00:00
* [example/challenge-store.js ](https://github.com/Daplie/le-acme-core/blob/master/challenge-store.js )
* [example/cert-store.js ](https://github.com/Daplie/le-acme-core/blob/master/cert-store.js )
2015-12-16 08:17:33 +00:00
2015-12-16 01:18:40 +00:00
## Authors
* ISRG
* Anatol Sommer (https://github.com/anatolsommer)
* AJ ONeal < aj @ daplie . com > (https://daplie.com)
2015-12-13 16:50:04 +00:00
## Licence
2015-12-15 22:07:02 +00:00
2015-12-13 16:50:04 +00:00
MPL 2.0
2015-12-15 22:07:02 +00:00
All of the code is available under the MPL-2.0.
Some of the files are original work not modified from `letiny`
2016-08-02 00:00:04 +00:00
and are made available under MIT and Apache-2.0 as well (check file headers).