🔐 Free SSL, Free Wildcard SSL, and Fully Automated HTTPS for node.js, issued by Let's Encrypt v2 via ACME. Issues and PRs on Github.
Go to file
AJ ONeal 35971160e6 update 2015-12-12 15:52:55 +00:00
bin tested working :-) 2015-12-12 15:19:11 +00:00
tests update 2015-12-12 15:27:06 +00:00
.gitignore updates 2015-12-12 15:05:45 +00:00
LICENSE initial commit 2015-12-11 03:23:47 -08:00
README.md update 2015-12-12 15:52:55 +00:00
index.js needRegistration callback 2015-12-12 15:50:00 +00:00
package.json updates 2015-12-12 15:05:45 +00:00
utils.js progress 2015-12-11 06:22:46 -08:00

README.md

letsencrypt

Let's Encrypt for node.js

This allows you to get Free SSL Certificates for Automatic HTTPS.

NOT YET PUBLISHED

  • Dec 12 2015: gettin' really close
  • Dec 11 2015: almost done (node-letsencrypt-python complete)
  • Dec 10 2015: began tinkering

Install

npm install --save letsencrypt

Right now this uses letsencrypt-python, but it's built to be able to use a pure javasript version.

# install the python client (takes 2 minutes normally, 20 on a rasberry pi)
git clone https://github.com/letsencrypt/letsencrypt
pushd letsencrypt

./letsencrypt-auto

Usage

  • Letsencrypt.create(backend, bkDefaults);
    • { webrootPath, configDir, fullchainTpl, privkeyTpl }
  • le.middleware();
  • le.sniCallback(hostname, function (err, tlsContext) {});
  • le.register({ domains, email, agreeTos, ... }) returns promise
var leBinPath = '/home/user/.local/share/letsencrypt/bin/letsencrypt';
var lep = require('letsencrypt-python').create(leBinPath);

// backend-specific defaults
// Note: For legal reasons you should NOT set email or agreeTos as a default
var bkDefaults = {
  webroot: true
, webrootPath: __dirname, '/acme-challenge'
, fullchainTpl: '/live/:hostname/fullchain.pem'
, privkeyTpl: '/live/:hostname/fullchain.pem'
, configDir: '/etc/letsencrypt'
, logsDir: '/var/log/letsencrypt'
, workDir: '/var/lib/letsencrypt'
, text: true
};
var leConfig = {
, webrootPath: __dirname, '/acme-challenge'
, configDir: '/etc/letsencrypt'
};
var le = require('letsencrypt').create(le, bkDefaults, leConfig);


var leBinPath = '/home/user/.local/share/letsencrypt/bin/letsencrypt';
var lep = require('letsencrypt-python').create(leBinPath);

// backend-specific defaults
// Note: For legal reasons you should NOT set email or agreeTos as a default
var bkDefaults = {
  webroot: true
, webrootPath: __dirname, '/acme-challenge'
, fullchainTpl: '/live/:hostname/fullchain.pem'
, privkeyTpl: '/live/:hostname/fullchain.pem'
, configDir: '/etc/letsencrypt'
, logsDir: '/var/log/letsencrypt'
, workDir: '/var/lib/letsencrypt'
, text: true
};
var leConfig = {
, webrootPath: __dirname, '/acme-challenge'
, configDir: '/etc/letsencrypt'
};
var le = require('letsencrypt').create(le, bkDefaults, leConfig);

var localCerts = require('localhost.daplie.com-certificates');
var express = require('express');
var app = express();

app.use(le.middleware);

server = require('http').createServer();
server.on('request', app);
server.listen(80, function () {
  console.log('Listening http', server.address());
});

tlsServer = require('https').createServer({
  key: localCerts.key
, cert: localCerts.cert
, SNICallback: le.SNICallback
});
tlsServer.on('request', app);
tlsServer.listen(443, function () {
  console.log('Listening http', server.address());
});

le.register({
, domains: ['example.com']
, agreeTos: true
, email: 'user@example.com'
}).then(function () {
  server.close();
  tlsServer.close();
});
lep.register('certonly', {
, domains: ['example.com']
, agreeTos: true
, email: 'user@example.com'

, configDir: '/etc/letsencrypt'
, logsDir: '/var/log/letsencrypt'
, workDir: '/var/lib/letsencrypt'
, text: true
});
// if you would like to register new domains on-the-fly
// you can use this function to return the user to which
// it should be registered (or null if none)
, needsRegistration: function (hostname, cb) {
    cb(null, {
      agreeTos: true
    , email:  'user@example.com'
    });
  }

Backends

How to write a backend

A backend must implement (or be wrapped to implement) this API:

  • fetch(hostname, cb) will cb(err, certs) will get registered certs or null unless there is an error
  • register(args, challengeCb, done) will register and or renew a cert
    • 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:

{ 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
, privkeyTpl: '/live/:hostname/privkey.pem'    // :hostname
}

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)

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
    , expiresIn: 90 * 60          // assumes 90-days unless specified
    });
  }
, 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() {
      // it is not neccessary to to return the certificates here
      // the client will call fetch() when it needs them
      completeCallback(err);
    }
  }
};

See Also

LICENSE

Dual-licensed MIT and Apache-2.0

See LICENSE