Nihilistic Pool Man

    le-acme-core

    2.1.4 • Public • Published

    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

    • ISRG
    • Anatol Sommer (https://github.com/anatolsommer)
    • AJ ONeal coolaj86@gmail.com (https://coolaj86.com)

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

    Install

    npm i le-acme-core

    DownloadsWeekly Downloads

    27,376

    Version

    2.1.4

    License

    MPL-2.0

    Unpacked Size

    64.4 kB

    Total Files

    19

    Last publish

    Collaborators

    • coolaj86