Name Last Update
..
example Loading commit data...
lib Loading commit data...
node_modules Loading commit data...
test Loading commit data...
AUTHORS Loading commit data...
History.md Loading commit data...
LICENSE.txt Loading commit data...
README.md Loading commit data...
browser.js Loading commit data...
node.js Loading commit data...
package.json Loading commit data...

le-acme-core

Looking for letiny-core? Check the v1.x branch.

A framework for building letsencrypt clients, forked from letiny.

Supports all of:

  • node with ursa (works fast)
  • node with forge (works on windows)
  • browser WebCrypto (not implemented, but... Let's Encrypt over WebRTC anyone?)
  • any javascript implementation

NEW: Let's Encrypt v2 Support

Let's Encrypt v2 (aka ACME v2 or ACME draft 11) is available in acme-v2.js

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:

Install & Usage:

npm install --save le-acme-core

To use the default dependencies:

'use strict';

var ACME = require('le-acme-core').ACME.create();

For testing and development, you can also inject the dependencies you want to use:

'use strict';

var ACME = require('le-acme-core').ACME.create({
, RSA: require('rsa-compat').RSA
});

ACME.getAcmeUrls(discoveryUrl, function (err, urls) {
  console.log(urls);
});

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
  • 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

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

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).

Install le-acme-core and its dependencies. Note: it's okay if you're on windows and ursa fails to compile. It'll still work.

git clone https://git.coolaj86.com/coolaj86/le-acme-core.js.git ~/le-acme-core
pushd ~/le-acme-core

npm install

Run the demo:

node examples/letsencrypt.js user@example.com example.com

Note: use YOUR TEMPORARY DOMAIN and YOUR EMAIL.

API

The Goodies

// Accounts
ACME.registerNewAccount(options, cb)        // returns "regr" registration data

    { newRegUrl: '<url>'                      //    no defaults, specify acmeUrls.newAuthz
    , email: '<email>'                        //    valid email (server checks MX records)
    , accountKeypair: {                       //    privateKeyPem or privateKeyJwt
        privateKeyPem: '<ASCII PEM>'
      }
    , agreeToTerms: fn (tosUrl, cb) {}        //    must specify agree=tosUrl to continue (or falsey to end)
    }

// Registration
ACME.getCertificate(options, cb)            // returns (err, pems={ privkey (key), cert, chain (ca) })

    { newAuthzUrl: '<url>'                    //    specify acmeUrls.newAuthz
    , newCertUrl: '<url>'                     //    specify acmeUrls.newCert

    , domainKeypair: {
        privateKeyPem: '<ASCII PEM>'
      }
    , accountKeypair: {
        privateKeyPem: '<ASCII PEM>'
      }
    , domains: ['example.com']

    , setChallenge: fn (hostname, key, val, cb)
    , removeChallenge: fn (hostname, key, cb)
    }

// Discovery URLs
ACME.getAcmeUrls(acmeDiscoveryUrl, cb)      // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert})

Helpers & Stuff

// Constants
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


// HTTP Client Helpers
ACME.Acme                               // Signs requests with JWK
    acme = new Acme(keypair)                // 'keypair' is an object with `privateKeyPem` and/or `privateKeyJwk`
    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.

Register Account & Domain

This is how you register an ACME account and get an HTTPS certificate

'use strict';

var ACME = require('le-acme-core').ACME.create();
var RSA = require('rsa-compat').RSA;

var email = 'user@example.com';                   // CHANGE TO YOUR EMAIL
var domains = 'example.com';                      // CHANGE TO YOUR DOMAIN
var acmeDiscoveryUrl = ACME.stagingServerUrl;   // CHANGE to production, when ready

var accountKeypair = null;                        // { privateKeyPem: null, privateKeyJwk: null };
var domainKeypair = null;                         // same as above
var acmeUrls = null;

RSA.generateKeypair(2048, 65537, function (err, keypair) {
    accountKeypair = keypair;
    // ...
    ACME.getAcmeUrls(acmeDiscoveryUrl, function (err, urls) {
        // ...
        runDemo();
    });
});

function runDemo() {
    ACME.registerNewAccount(
        { newRegUrl: acmeUrls.newReg
        , email: email
        , accountKeypair: accountKeypair
        , agreeToTerms: function (tosUrl, done) {

              // agree to the exact version of these terms
              done(null, tosUrl);
          }
        }
      , function (err, regr) {

            ACME.getCertificate(
                { newAuthzUrl: acmeUrls.newAuthz
                , newCertUrl: acmeUrls.newCert

                , domainKeypair: domainKeypair
                , accountKeypair: accountKeypair
                , domains: domains

                , setChallenge: challengeStore.set
                , removeChallenge: challengeStore.remove
                }
              , function (err, certs) {

                  // Note: you should save certs to disk (or db)
                  certStore.set(domains[0], certs, function () {

                      // ...

                  });

                }
            );
        }
    );
}

But wait, there's more! See example/letsencrypt.js

Run a Server on 80, 443, and 5001 (https/tls)

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

var https = require('https');
var http = require('http');


var LeCore = deps.LeCore;
var tlsOptions = deps.tlsOptions;
var challengeStore = deps.challengeStore;
var certStore = deps.certStore;


//
// Challenge Handler
//
function acmeResponder(req, res) {
  if (0 !== req.url.indexOf(LeCore.acmeChallengePrefix)) {
    res.end('Hello World!');
    return;
  }

  var key = req.url.slice(LeCore.acmeChallengePrefix.length);

  challengeStore.get(req.hostname, key, function (err, val) {
    res.end(val || 'Error');
  });
}


//
// Server
//
https.createServer(tlsOptions, acmeResponder).listen(5001, function () {
  console.log('Listening https on', this.address());
});
http.createServer(acmeResponder).listen(80, function () {
  console.log('Listening http on', this.address());
});

But wait, there's more! See example/serve.js

Put some storage in place

Finally, you need an implementation of challengeStore:

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);
  }
};

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);
  }
};

But wait, there's more! See

Authors

Licence

MPL 2.0

All of the code is available under the MPL-2.0.

Some of the files are original work not modified from letiny and are made available under MIT and Apache-2.0 as well (check file headers).