greenlock.js/README.md

6.5 KiB

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 = require('os').homedir() + '/.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