mirror of
				https://github.com/therootcompany/greenlock.js.git
				synced 2025-11-04 13:42:47 +00:00 
			
		
		
		
	partial refactor
This commit is contained in:
		
							parent
							
								
									4a389606b0
								
							
						
					
					
						commit
						fd02f44c13
					
				@ -29,9 +29,6 @@ No, I wanted node-letsencrypt
 | 
			
		||||
=============================
 | 
			
		||||
 | 
			
		||||
Well, take a look at the API in the main README
 | 
			
		||||
and you can also check out the [scraps](https://github.com/Daplie/node-letsencrypt/tree/master/scraps).
 | 
			
		||||
and you can also check out the code in the repos above.
 | 
			
		||||
 | 
			
		||||
Feel free to create issues for examples that don't work and pull requests if you fix one.
 | 
			
		||||
 | 
			
		||||
And please, please, do open an issue. We haven't updated the scrap examples
 | 
			
		||||
(hence being moved), but we do have it on the roadmap to bring back some raw API examples.
 | 
			
		||||
Feel free to open an issues to request any particular type of example.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								examples/simple.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								examples/simple.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
//var le = require('letsencrypt');
 | 
			
		||||
var LE = require('../');
 | 
			
		||||
var db = {};
 | 
			
		||||
 | 
			
		||||
var config = {
 | 
			
		||||
  server: LE.stagingServerUrl                               // or LE.productionServerUrl
 | 
			
		||||
 | 
			
		||||
, configDir: require('homedir')() + '/letsencrypt/etc'      // or /etc/letsencrypt or wherever
 | 
			
		||||
 | 
			
		||||
, privkeyPath: ':config/live/:hostname/privkey.pem'         //
 | 
			
		||||
, fullchainPath: ':config/live/:hostname/fullchain.pem'     // Note: both that :config and :hostname
 | 
			
		||||
, certPath: ':config/live/:hostname/cert.pem'               //       will be templated as expected
 | 
			
		||||
, chainPath: ':config/live/:hostname/chain.pem'             //
 | 
			
		||||
 | 
			
		||||
, rsaKeySize: 2048
 | 
			
		||||
 | 
			
		||||
, debug: true
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var handlers = {
 | 
			
		||||
  setChallenge: function (opts, hostname, key, val, cb) {   // called during the ACME server handshake, before validation
 | 
			
		||||
    db[key] = {
 | 
			
		||||
      hostname: hostname
 | 
			
		||||
    , key: key
 | 
			
		||||
    , val: val
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    cb(null);
 | 
			
		||||
  }
 | 
			
		||||
, removeChallenge: function (opts, hostname, key, cb) {     // called after validation on both success and failure
 | 
			
		||||
    db[key] = null;
 | 
			
		||||
    cb(null);
 | 
			
		||||
  }
 | 
			
		||||
, getChallenge: function (opts, hostname, key, cb) {        // this is special because it is called by the webserver
 | 
			
		||||
    cb(null, db[key].val);                                  // (see letsencrypt-cli/bin & letsencrypt-express/standalone),
 | 
			
		||||
                                                            // not by the library itself
 | 
			
		||||
  }
 | 
			
		||||
, agreeToTerms: function (tosUrl, cb) {                     // gives you an async way to expose the legal agreement
 | 
			
		||||
    cb(null, tosUrl);                                       // (terms of use) to your users before accepting
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var le = LE.create(config, handlers);
 | 
			
		||||
                                                            // checks :conf/renewal/:hostname.conf
 | 
			
		||||
le.register({                                               // and either renews or registers
 | 
			
		||||
  domains: ['example.com']                                  // CHANGE TO YOUR DOMAIN
 | 
			
		||||
, email: 'user@email.com'                                   // CHANGE TO YOUR EMAIL
 | 
			
		||||
, agreeTos: false                                           // set to true to automatically accept an agreement
 | 
			
		||||
                                                            // which you have pre-approved (not recommended)
 | 
			
		||||
, rsaKeySize: 2048
 | 
			
		||||
}, function (err) {
 | 
			
		||||
  if (err) {
 | 
			
		||||
    // Note: you must have a webserver running
 | 
			
		||||
    // and expose handlers.getChallenge to it
 | 
			
		||||
    // in order to pass validation
 | 
			
		||||
    // See letsencrypt-cli and or letsencrypt-express
 | 
			
		||||
    console.error('[Error]: node-letsencrypt/examples/standalone');
 | 
			
		||||
    console.error(err.stack);
 | 
			
		||||
  } else {
 | 
			
		||||
    console.log('success');
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										69
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								index.js
									
									
									
									
									
								
							@ -4,45 +4,34 @@
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
var leCore = require('letiny-core');
 | 
			
		||||
var utils = require('./lib/common');
 | 
			
		||||
var merge = require('./lib/common').merge;
 | 
			
		||||
var tplCopy = require('./lib/common').tplCopy;
 | 
			
		||||
 | 
			
		||||
var LE = module.exports;
 | 
			
		||||
LE.productionServerUrl = leCore.productionServerUrl;
 | 
			
		||||
LE.stagingServerUrl = leCore.stagingServerUrl;
 | 
			
		||||
LE.configDir = leCore.configDir;
 | 
			
		||||
LE.logsDir = leCore.logsDir;
 | 
			
		||||
LE.workDir = leCore.workDir;
 | 
			
		||||
LE.acmeChallengPrefix = leCore.acmeChallengPrefix;
 | 
			
		||||
LE.knownEndpoints = leCore.knownEndpoints;
 | 
			
		||||
 | 
			
		||||
LE.privkeyPath = ':config/live/:hostname/privkey.pem';
 | 
			
		||||
LE.fullchainPath = ':config/live/:hostname/fullchain.pem';
 | 
			
		||||
LE.certPath = ':config/live/:hostname/cert.pem';
 | 
			
		||||
LE.chainPath = ':config/live/:hostname/chain.pem';
 | 
			
		||||
LE.renewalPath = ':config/renewal/:hostname.conf';
 | 
			
		||||
LE.accountsDir = ':config/accounts/:server';
 | 
			
		||||
LE.merge = require('./lib/common').merge;
 | 
			
		||||
 | 
			
		||||
LE.defaults = {
 | 
			
		||||
  privkeyPath: LE.privkeyPath
 | 
			
		||||
, fullchainPath: LE.fullchainPath
 | 
			
		||||
, certPath: LE.certPath
 | 
			
		||||
, chainPath: LE.chainPath
 | 
			
		||||
, renewalPath: LE.renewalPath
 | 
			
		||||
, accountsDir: LE.accountsDir
 | 
			
		||||
, server: LE.productionServerUrl
 | 
			
		||||
  server: leCore.productionServerUrl
 | 
			
		||||
, stagingServer: leCore.stagingServerUrl
 | 
			
		||||
, liveServer: leCore.productionServerUrl
 | 
			
		||||
 | 
			
		||||
, productionServerUrl: leCore.productionServerUrl
 | 
			
		||||
, stagingServerUrl: leCore.stagingServerUrl
 | 
			
		||||
 | 
			
		||||
, acmeChallengePrefix: leCore.acmeChallengePrefix
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// backwards compat
 | 
			
		||||
LE.stagingServer = leCore.stagingServerUrl;
 | 
			
		||||
LE.liveServer = leCore.productionServerUrl;
 | 
			
		||||
LE.knownUrls = leCore.knownEndpoints;
 | 
			
		||||
 | 
			
		||||
LE.merge = require('./lib/common').merge;
 | 
			
		||||
LE.tplConfigDir = require('./lib/common').tplConfigDir;
 | 
			
		||||
Object.keys(LE.defaults).forEach(function (key) {
 | 
			
		||||
  LE[key] = LE.defaults[key];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
                    // backend, defaults, handlers
 | 
			
		||||
LE.create = function (defaults, handlers, backend) {
 | 
			
		||||
  if (!backend) { backend = require('./lib/core'); }
 | 
			
		||||
  var Backend = require('./lib/core');
 | 
			
		||||
  if (!backend) { backend = require('./lib/pycompat').create(defaults); }
 | 
			
		||||
  if (!handlers) { handlers = {}; }
 | 
			
		||||
  if (!handlers.lifetime) { handlers.lifetime = 90 * 24 * 60 * 60 * 1000; }
 | 
			
		||||
  if (!handlers.renewWithin) { handlers.renewWithin = 3 * 24 * 60 * 60 * 1000; }
 | 
			
		||||
@ -64,14 +53,15 @@ LE.create = function (defaults, handlers, backend) {
 | 
			
		||||
      // the request it came from... it's kinda stateless in that way
 | 
			
		||||
      // but realistically there only needs to be one handler and one
 | 
			
		||||
      // "directory" for this. It's not that big of a deal.
 | 
			
		||||
      var defaultos = LE.merge(defaults, {});
 | 
			
		||||
      var defaultos = LE.merge({}, defaults);
 | 
			
		||||
      var getChallenge = require('./lib/default-handlers').getChallenge;
 | 
			
		||||
      var copy = merge(defaults, { domains: [hostname] });
 | 
			
		||||
      var copy = merge({ domains: [hostname] }, defaults);
 | 
			
		||||
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
      defaultos.domains = [hostname];
 | 
			
		||||
 | 
			
		||||
      if (3 === getChallenge.length) {
 | 
			
		||||
        console.warn('[WARNING] Deprecated use. Define getChallenge as function (opts, domain, key, cb) { }');
 | 
			
		||||
        getChallenge(defaultos, key, done);
 | 
			
		||||
      }
 | 
			
		||||
      else if (4 === getChallenge.length) {
 | 
			
		||||
@ -102,22 +92,10 @@ LE.create = function (defaults, handlers, backend) {
 | 
			
		||||
    }
 | 
			
		||||
    handlers.agreeToTerms = require('./lib/default-handlers').agreeToTerms;
 | 
			
		||||
  }
 | 
			
		||||
  if ('function' === typeof backend.create) {
 | 
			
		||||
    backend = backend.create(defaults, handlers);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    // ignore
 | 
			
		||||
    // this backend was created the v1.0.0 way
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // replaces strings of workDir, certPath, etc
 | 
			
		||||
  // if they have :config/etc/live or :conf/etc/archive
 | 
			
		||||
  // to instead have the path of the configDir
 | 
			
		||||
  LE.tplConfigDir(defaults.configDir, defaults);
 | 
			
		||||
 | 
			
		||||
  backend = Backend.create(defaults, handlers);
 | 
			
		||||
  backend = PromiseA.promisifyAll(backend);
 | 
			
		||||
 | 
			
		||||
  var utils = require('./lib/common');
 | 
			
		||||
  //var attempts = {};  // should exist in master process only
 | 
			
		||||
  var le;
 | 
			
		||||
 | 
			
		||||
@ -151,7 +129,7 @@ LE.create = function (defaults, handlers, backend) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var copy = LE.merge(defaults, args);
 | 
			
		||||
      var copy = LE.merge(args, defaults);
 | 
			
		||||
      var err;
 | 
			
		||||
 | 
			
		||||
      if (!utils.isValidDomain(args.domains[0])) {
 | 
			
		||||
@ -185,6 +163,11 @@ LE.create = function (defaults, handlers, backend) {
 | 
			
		||||
      if (defaults.debug || args.debug) {
 | 
			
		||||
        console.log('[LE] fetch');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // TODO figure out what TPLs are needed
 | 
			
		||||
      var copy = merge(args, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return backend.fetchAsync(args).then(function (certInfo) {
 | 
			
		||||
        if (args.debug) {
 | 
			
		||||
          console.log('[LE] raw fetch certs', certInfo && Object.keys(certInfo));
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										195
									
								
								lib/accounts.js
									
									
									
									
									
								
							
							
						
						
									
										195
									
								
								lib/accounts.js
									
									
									
									
									
								
							@ -1,195 +0,0 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
var crypto = require('crypto');
 | 
			
		||||
var LeCore = require('letiny-core');
 | 
			
		||||
var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA);
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
 | 
			
		||||
var fs = PromiseA.promisifyAll(require('fs'));
 | 
			
		||||
 | 
			
		||||
function createAccount(args, handlers) {
 | 
			
		||||
  var os = require("os");
 | 
			
		||||
  var localname = os.hostname();
 | 
			
		||||
 | 
			
		||||
  // arg.rsaBitLength args.rsaExponent
 | 
			
		||||
  return RSA.generateKeypairAsync(args.rsaKeySize || 2048, 65537, { public: true, pem: true }).then(function (keypair) {
 | 
			
		||||
 | 
			
		||||
    return LeCore.registerNewAccountAsync({
 | 
			
		||||
      email: args.email
 | 
			
		||||
    , newRegUrl: args._acmeUrls.newReg
 | 
			
		||||
    , agreeToTerms: function (tosUrl, agree) {
 | 
			
		||||
        // args.email = email; // already there
 | 
			
		||||
        args.tosUrl = tosUrl;
 | 
			
		||||
        handlers.agreeToTerms(args, agree);
 | 
			
		||||
      }
 | 
			
		||||
    , accountKeypair: keypair
 | 
			
		||||
 | 
			
		||||
    , debug: args.debug || handlers.debug
 | 
			
		||||
    }).then(function (body) {
 | 
			
		||||
      // TODO XXX use sha256 (the python client uses md5)
 | 
			
		||||
      // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
 | 
			
		||||
      keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
 | 
			
		||||
      keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
 | 
			
		||||
      var accountId = keypair.publicKeyMd5;
 | 
			
		||||
      var accountDir = path.join(args.accountsDir, accountId);
 | 
			
		||||
      var regr = { body: body };
 | 
			
		||||
 | 
			
		||||
      args.accountId = accountId;
 | 
			
		||||
      args.accountDir = accountDir;
 | 
			
		||||
 | 
			
		||||
      return mkdirpAsync(accountDir).then(function () {
 | 
			
		||||
 | 
			
		||||
        var isoDate = new Date().toISOString();
 | 
			
		||||
        var accountMeta = {
 | 
			
		||||
          creation_host: localname
 | 
			
		||||
        , creation_dt: isoDate
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // TODO abstract file writing
 | 
			
		||||
        return PromiseA.all([
 | 
			
		||||
          // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"}
 | 
			
		||||
          fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(accountMeta), 'utf8')
 | 
			
		||||
          // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" }
 | 
			
		||||
        , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(keypair)), 'utf8')
 | 
			
		||||
          // regr.json:
 | 
			
		||||
          /*
 | 
			
		||||
          { body: { contact: [ 'mailto:coolaj86@gmail.com' ],
 | 
			
		||||
           agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf',
 | 
			
		||||
           key: { e: 'AQAB', kty: 'RSA', n: '...' } },
 | 
			
		||||
            uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272',
 | 
			
		||||
            new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz',
 | 
			
		||||
            terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' }
 | 
			
		||||
           */
 | 
			
		||||
        , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(regr), 'utf8')
 | 
			
		||||
        ]).then(function () {
 | 
			
		||||
          var pems = {};
 | 
			
		||||
 | 
			
		||||
          // pems.private_key;
 | 
			
		||||
          pems.meta = accountMeta;
 | 
			
		||||
          pems.keypair = keypair;
 | 
			
		||||
          pems.regr = regr;
 | 
			
		||||
          pems.accountId = accountId;
 | 
			
		||||
          pems.id = accountId;
 | 
			
		||||
          return pems;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAccount(args, handlers) {
 | 
			
		||||
  var accountId = args.accountId;
 | 
			
		||||
  var accountDir = path.join(args.accountsDir, accountId);
 | 
			
		||||
  var files = {};
 | 
			
		||||
  var configs = ['meta.json', 'private_key.json', 'regr.json'];
 | 
			
		||||
 | 
			
		||||
  return PromiseA.all(configs.map(function (filename) {
 | 
			
		||||
    var keyname = filename.slice(0, -5);
 | 
			
		||||
 | 
			
		||||
    return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) {
 | 
			
		||||
      var data;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        data = JSON.parse(text);
 | 
			
		||||
      } catch(e) {
 | 
			
		||||
        files[keyname] = { error: e };
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      files[keyname] = data;
 | 
			
		||||
    }, function (err) {
 | 
			
		||||
      files[keyname] = { error: err };
 | 
			
		||||
    });
 | 
			
		||||
  })).then(function () {
 | 
			
		||||
 | 
			
		||||
    if (!Object.keys(files).every(function (key) {
 | 
			
		||||
      return !files[key].error;
 | 
			
		||||
    })) {
 | 
			
		||||
      // TODO log renewal.conf
 | 
			
		||||
      console.warn("Account '" + accountId + "' was corrupt. No big deal (I think?). Creating a new one...");
 | 
			
		||||
      //console.log(accountId, files);
 | 
			
		||||
      return createAccount(args, handlers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var keypair = { privateKeyJwk: files.private_key };
 | 
			
		||||
    keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
 | 
			
		||||
    keypair.publicKeyPem = RSA.exportPublicPem(keypair);
 | 
			
		||||
 | 
			
		||||
    //files.private_key;
 | 
			
		||||
    //files.regr;
 | 
			
		||||
    //files.meta;
 | 
			
		||||
    files.accountId = accountId;                  // preserve current account id
 | 
			
		||||
    files.id = accountId;
 | 
			
		||||
    files.keypair = keypair;
 | 
			
		||||
 | 
			
		||||
    return files;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAccountIdByEmail(args) {
 | 
			
		||||
  // If we read 10,000 account directories looking for
 | 
			
		||||
  // just one email address, that could get crazy.
 | 
			
		||||
  // We should have a folder per email and list
 | 
			
		||||
  // each account as a file in the folder
 | 
			
		||||
  // TODO
 | 
			
		||||
  var email = args.email;
 | 
			
		||||
  if ('string' !== typeof email) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log("[LE] No email given");
 | 
			
		||||
    }
 | 
			
		||||
    return PromiseA.resolve(null);
 | 
			
		||||
  }
 | 
			
		||||
  return fs.readdirAsync(args.accountsDir).then(function (nodes) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log("[LE] arg.accountsDir success");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.all(nodes.map(function (node) {
 | 
			
		||||
      return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) {
 | 
			
		||||
        var regr = JSON.parse(text);
 | 
			
		||||
        regr.__accountId = node;
 | 
			
		||||
 | 
			
		||||
        return regr;
 | 
			
		||||
      });
 | 
			
		||||
    })).then(function (regrs) {
 | 
			
		||||
      var accountId;
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
      if (args.debug) {
 | 
			
		||||
        console.log('read many regrs');
 | 
			
		||||
        console.log('regrs', regrs);
 | 
			
		||||
      }
 | 
			
		||||
      */
 | 
			
		||||
 | 
			
		||||
      regrs.some(function (regr) {
 | 
			
		||||
        return regr.body.contact.some(function (contact) {
 | 
			
		||||
          var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase();
 | 
			
		||||
          if (match) {
 | 
			
		||||
            accountId = regr.__accountId;
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!accountId) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return accountId;
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function (accountId) {
 | 
			
		||||
    return accountId;
 | 
			
		||||
  }, function (err) {
 | 
			
		||||
    if ('ENOENT' === err.code) {
 | 
			
		||||
      // ignore error
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.reject(err);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.getAccountIdByEmail = getAccountIdByEmail;
 | 
			
		||||
module.exports.getAccount = getAccount;
 | 
			
		||||
module.exports.createAccount = createAccount;
 | 
			
		||||
@ -22,7 +22,7 @@ module.exports.isValidDomain = function (domain) {
 | 
			
		||||
  return '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.tplConfigDir = function merge(configDir, defaults) {
 | 
			
		||||
module.exports.tplConfigDir = function (configDir, defaults) {
 | 
			
		||||
  var homedir = require('homedir')();
 | 
			
		||||
  Object.keys(defaults).forEach(function (key) {
 | 
			
		||||
    if ('string' === typeof defaults[key]) {
 | 
			
		||||
@ -32,12 +32,17 @@ module.exports.tplConfigDir = function merge(configDir, defaults) {
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.merge = function merge(defaults, args) {
 | 
			
		||||
module.exports.merge = function (/*defaults, args*/) {
 | 
			
		||||
  var allDefaults = Array.prototype.slice.apply(arguments);
 | 
			
		||||
  var args = args.shift();
 | 
			
		||||
  var copy = {};
 | 
			
		||||
 | 
			
		||||
  Object.keys(defaults).forEach(function (key) {
 | 
			
		||||
    copy[key] = defaults[key];
 | 
			
		||||
  allDefaults.forEach(function (defaults) {
 | 
			
		||||
    Object.keys(defaults).forEach(function (key) {
 | 
			
		||||
      copy[key] = defaults[key];
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  Object.keys(args).forEach(function (key) {
 | 
			
		||||
    copy[key] = args[key];
 | 
			
		||||
  });
 | 
			
		||||
@ -45,7 +50,19 @@ module.exports.merge = function merge(defaults, args) {
 | 
			
		||||
  return copy;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.tplCopy = function merge(copy) {
 | 
			
		||||
module.exports.tplCopy = function (copy) {
 | 
			
		||||
  var url = require('url');
 | 
			
		||||
  var acmeLocation = url.parse(copy.server);
 | 
			
		||||
  var acmeHostpath = path.join(acmeLocation.hostname, acmeLocation.pathname);
 | 
			
		||||
  copy.accountsDir = copy.accountsDir || path.join(copy.configDir, 'accounts', acmeHostpath);
 | 
			
		||||
  // TODO move these defaults elsewhere?
 | 
			
		||||
  //args.renewalDir = args.renewalDir || ':config/renewal/';
 | 
			
		||||
  args.renewalPath = args.renewalPath || ':config/renewal/:hostname.conf';
 | 
			
		||||
  // Note: the /directory is part of the server url and, as such, bleeds into the pathname
 | 
			
		||||
  // So :config/accounts/:server/directory is *incorrect*, but the following *is* correct:
 | 
			
		||||
  args.accountsDir = args.accountsDir || ':config/accounts/:server';
 | 
			
		||||
  hargs.renewalDir = hargs.renewalDir || ':config/renewal/';
 | 
			
		||||
  copy.renewalPath = copy.renewalPath || path.join(copy.configDir, 'renewal', copy.domains[0] + '.conf');
 | 
			
		||||
  var homedir = require('homedir')();
 | 
			
		||||
  var tpls = {
 | 
			
		||||
    hostname: (copy.domains || [])[0]
 | 
			
		||||
@ -71,55 +88,3 @@ module.exports.tplCopy = function merge(copy) {
 | 
			
		||||
 | 
			
		||||
  //return copy;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.fetchFromDisk = function (args) {
 | 
			
		||||
  // TODO NO HARD-CODED DEFAULTS
 | 
			
		||||
  if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) {
 | 
			
		||||
    console.warn("missing one or more of args.privkeyPath, args.fullchainPath, args.certPath, args.chainPath");
 | 
			
		||||
    console.warn("hard-coded conventional pathnames were for debugging and are not a stable part of the API");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //, fs.readFileAsync(fullchainPath, 'ascii')
 | 
			
		||||
  // note: if this ^^ gets added back in, the arrays below must change
 | 
			
		||||
  return PromiseA.all([
 | 
			
		||||
    fs.readFileAsync(args.privkeyPath, 'ascii')   // 0
 | 
			
		||||
  , fs.readFileAsync(args.certPath, 'ascii')      // 1
 | 
			
		||||
  , fs.readFileAsync(args.chainPath, 'ascii')     // 2
 | 
			
		||||
 | 
			
		||||
    // stat the file, not the link
 | 
			
		||||
  , fs.statAsync(args.certPath)                   // 3
 | 
			
		||||
  ]).then(function (arr) {
 | 
			
		||||
    var cert = arr[1];
 | 
			
		||||
    var getCertInfo = require('./cert-info').getCertInfo;
 | 
			
		||||
 | 
			
		||||
    // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | 
			
		||||
    var certInfo = getCertInfo(cert);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      key: arr[0]                           // privkey.pem
 | 
			
		||||
    , privkey: arr[0]                       // privkey.pem
 | 
			
		||||
 | 
			
		||||
    , fullchain: arr[1] + '\n' + arr[2]     // fullchain.pem
 | 
			
		||||
    , cert: cert                            // cert.pem
 | 
			
		||||
 | 
			
		||||
    , chain: arr[2]                         // chain.pem
 | 
			
		||||
    , ca: arr[2]                            // chain.pem
 | 
			
		||||
 | 
			
		||||
    , privkeyPath: args.privkeyPath
 | 
			
		||||
    , fullchainPath: args.fullchainPath
 | 
			
		||||
    , certPath: args.certPath
 | 
			
		||||
    , chainPath: args.chainPath
 | 
			
		||||
 | 
			
		||||
    //, issuedAt: arr[3].mtime.valueOf()
 | 
			
		||||
    , issuedAt: Date(certInfo.notBefore.value).valueOf() // Date.now()
 | 
			
		||||
    , expiresAt: Date(certInfo.notAfter.value).valueOf()
 | 
			
		||||
    , lifetime: args.lifetime
 | 
			
		||||
    };
 | 
			
		||||
  }, function (err) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.error("[letsencrypt/lib/common.js] fetchFromDisk");
 | 
			
		||||
      console.error(err.stack);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										646
									
								
								lib/core.js
									
									
									
									
									
								
							
							
						
						
									
										646
									
								
								lib/core.js
									
									
									
									
									
								
							@ -1,454 +1,228 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA);
 | 
			
		||||
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var fs = PromiseA.promisifyAll(require('fs'));
 | 
			
		||||
var sfs = require('safe-replace');
 | 
			
		||||
var LE = require('../');
 | 
			
		||||
var LeCore = PromiseA.promisifyAll(require('letiny-core'));
 | 
			
		||||
var Accounts = require('./accounts');
 | 
			
		||||
 | 
			
		||||
var merge = require('./common').merge;
 | 
			
		||||
var tplCopy = require('./common').tplCopy;
 | 
			
		||||
var fetchFromConfigLiveDir = require('./common').fetchFromDisk;
 | 
			
		||||
 | 
			
		||||
var ipc = {}; // in-process cache
 | 
			
		||||
 | 
			
		||||
function getAcmeUrls(args) {
 | 
			
		||||
  var now = Date.now();
 | 
			
		||||
module.exports.create = function (defaults, handlers, backend) {
 | 
			
		||||
  defaults.server = defaults.server || LE.liveServer;
 | 
			
		||||
  handlers.merge = require('./common').merge;
 | 
			
		||||
  handlers.tplCopy = require('./common').tplCopy;
 | 
			
		||||
 | 
			
		||||
  // TODO check response header on request for cache time
 | 
			
		||||
  if ((now - ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
 | 
			
		||||
    return PromiseA.resolve(ipc.acmeUrls);
 | 
			
		||||
  }
 | 
			
		||||
  var PromiseA = require('bluebird');
 | 
			
		||||
  var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA);
 | 
			
		||||
  var LeCore = PromiseA.promisifyAll(require('letiny-core'));
 | 
			
		||||
  var crypto = require('crypto');
 | 
			
		||||
 | 
			
		||||
  return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
 | 
			
		||||
    ipc.acmeUrlsUpdatedAt = Date.now();
 | 
			
		||||
    ipc.acmeUrls = data;
 | 
			
		||||
  function createAccount(args, handlers) {
 | 
			
		||||
    // arg.rsaBitLength args.rsaExponent
 | 
			
		||||
    return RSA.generateKeypairAsync(args.rsaKeySize || 2048, 65537, { public: true, pem: true }).then(function (keypair) {
 | 
			
		||||
 | 
			
		||||
    return ipc.acmeUrls;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
      return LeCore.registerNewAccountAsync({
 | 
			
		||||
        email: args.email
 | 
			
		||||
      , newRegUrl: args._acmeUrls.newReg
 | 
			
		||||
      , agreeToTerms: function (tosUrl, agree) {
 | 
			
		||||
          // args.email = email; // already there
 | 
			
		||||
          args.tosUrl = tosUrl;
 | 
			
		||||
          handlers.agreeToTerms(args, agree);
 | 
			
		||||
        }
 | 
			
		||||
      , accountKeypair: keypair
 | 
			
		||||
 | 
			
		||||
function readRenewalConfig(args) {
 | 
			
		||||
  var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
      , debug: defaults.debug || args.debug || handlers.debug
 | 
			
		||||
      }).then(function (body) {
 | 
			
		||||
        // TODO XXX use sha256 (the python client uses md5)
 | 
			
		||||
        // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
 | 
			
		||||
        keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
 | 
			
		||||
        keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
 | 
			
		||||
 | 
			
		||||
  return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) {
 | 
			
		||||
    return pyobj;
 | 
			
		||||
  }, function () {
 | 
			
		||||
    return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) {
 | 
			
		||||
      return pyobj;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
        var accountId = keypair.publicKeyMd5;
 | 
			
		||||
        var regr = { body: body };
 | 
			
		||||
        var account = {};
 | 
			
		||||
 | 
			
		||||
function writeRenewalConfig(args) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
        args.accountId = accountId;
 | 
			
		||||
 | 
			
		||||
  var pyobj = args.pyobj;
 | 
			
		||||
  pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
 | 
			
		||||
        account.keypair = keypair;
 | 
			
		||||
        account.regr = regr;
 | 
			
		||||
        account.accountId = accountId;
 | 
			
		||||
        account.id = accountId;
 | 
			
		||||
 | 
			
		||||
  var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
        args.account = account;
 | 
			
		||||
 | 
			
		||||
  var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
 | 
			
		||||
  var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
 | 
			
		||||
  var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
 | 
			
		||||
  var privkeyPath = args.privkeyPath || pyobj.privkey
 | 
			
		||||
    //|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath
 | 
			
		||||
    || path.join(liveDir, 'privkey.pem');
 | 
			
		||||
 | 
			
		||||
  log('[le/core.js] privkeyPath', privkeyPath);
 | 
			
		||||
 | 
			
		||||
  var updates = {
 | 
			
		||||
    account: args.account.id
 | 
			
		||||
  , configDir: args.configDir
 | 
			
		||||
  , domains: args.domains
 | 
			
		||||
 | 
			
		||||
  , email: args.email
 | 
			
		||||
  , tos: args.agreeTos && true
 | 
			
		||||
    // yes, it's an array. weird, right?
 | 
			
		||||
  , webrootPath: args.webrootPath && [args.webrootPath] || []
 | 
			
		||||
  , server: args.server || args.acmeDiscoveryUrl
 | 
			
		||||
 | 
			
		||||
  , privkey: privkeyPath
 | 
			
		||||
  , fullchain: fullchainPath
 | 
			
		||||
  , cert: certPath
 | 
			
		||||
  , chain: chainPath
 | 
			
		||||
 | 
			
		||||
  , http01Port: args.http01Port
 | 
			
		||||
  , keyPath: args.domainPrivateKeyPath || args.privkeyPath
 | 
			
		||||
  , rsaKeySize: args.rsaKeySize
 | 
			
		||||
  , checkpoints: pyobj.checkpoints
 | 
			
		||||
    /* // TODO XXX what's the deal with these? they don't make sense
 | 
			
		||||
    // are they just old junk? or do they have a meaning that I don't know about?
 | 
			
		||||
  , fullchainPath: path.join(args.configDir, 'chain.pem')
 | 
			
		||||
  , certPath: path.join(args.configDir, 'cert.pem')
 | 
			
		||||
  , chainPath: path.join(args.configDir, 'chain.pem')
 | 
			
		||||
    */ // TODO XXX end
 | 
			
		||||
  , workDir: args.workDir
 | 
			
		||||
  , logsDir: args.logsDir
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // final section is completely dynamic
 | 
			
		||||
  // :hostname = :webroot_path
 | 
			
		||||
  args.domains.forEach(function (hostname) {
 | 
			
		||||
    updates[hostname] = args.webrootPath;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // must write back to the original pyobject or
 | 
			
		||||
  // annotations will be lost
 | 
			
		||||
  Object.keys(updates).forEach(function (key) {
 | 
			
		||||
    pyobj[key] = updates[key];
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return mkdirpAsync(path.dirname(args.renewalPath)).then(function () {
 | 
			
		||||
    return pyconf.writeFileAsync(args.renewalPath, pyobj);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    // NOTE
 | 
			
		||||
    // writing twice seems to causes a bug,
 | 
			
		||||
    // so instead we re-read the file from the disk
 | 
			
		||||
    return pyconf.readFileAsync(args.renewalPath);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOrCreateRenewal(args) {
 | 
			
		||||
  return readRenewalConfig(args).then(function (pyobj) {
 | 
			
		||||
    var minver = pyobj.checkpoints >= 0;
 | 
			
		||||
 | 
			
		||||
    args.pyobj = pyobj;
 | 
			
		||||
 | 
			
		||||
    if (!minver) {
 | 
			
		||||
      args.checkpoints = 0;
 | 
			
		||||
      pyobj.checkpoints = 0;
 | 
			
		||||
      return writeRenewalConfig(args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // args.account.id = pyobj.account
 | 
			
		||||
    // args.configDir = args.configDir || pyobj.configDir;
 | 
			
		||||
 | 
			
		||||
    args.checkpoints = pyobj.checkpoints;
 | 
			
		||||
 | 
			
		||||
    args.agreeTos = (args.agreeTos || pyobj.tos) && true;
 | 
			
		||||
    args.email = args.email || pyobj.email;
 | 
			
		||||
    args.domains = args.domains || pyobj.domains;
 | 
			
		||||
 | 
			
		||||
    // yes, it's an array. weird, right?
 | 
			
		||||
    args.webrootPath = args.webrootPath || pyobj.webrootPath[0];
 | 
			
		||||
    args.server = args.server || args.acmeDiscoveryUrl || pyobj.server;
 | 
			
		||||
 | 
			
		||||
    args.certPath = args.certPath || pyobj.cert;
 | 
			
		||||
    args.privkeyPath = args.privkeyPath || pyobj.privkey;
 | 
			
		||||
    args.chainPath = args.chainPath || pyobj.chain;
 | 
			
		||||
    args.fullchainPath = args.fullchainPath || pyobj.fullchain;
 | 
			
		||||
 | 
			
		||||
  //, workDir: args.workDir
 | 
			
		||||
  //, logsDir: args.logsDir
 | 
			
		||||
    args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize;
 | 
			
		||||
    args.http01Port = args.http01Port || pyobj.http01Port;
 | 
			
		||||
    args.domainKeyPath = args.domainPrivateKeyPath || args.domainKeyPath || args.keyPath || pyobj.keyPath;
 | 
			
		||||
 | 
			
		||||
    return writeRenewalConfig(args);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function writeCertificateAsync(args, defaults, handlers) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  log("[le/core.js] got certificate!");
 | 
			
		||||
 | 
			
		||||
  var obj = args.pyobj;
 | 
			
		||||
  var result = args.pems;
 | 
			
		||||
 | 
			
		||||
  result.fullchain = result.cert + '\n' + (result.chain || result.ca);
 | 
			
		||||
  obj.checkpoints = parseInt(obj.checkpoints, 10) || 0;
 | 
			
		||||
 | 
			
		||||
  var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var certPath = args.certPath || obj.cert || path.join(liveDir, 'cert.pem');
 | 
			
		||||
  var fullchainPath = args.fullchainPath || obj.fullchain || path.join(liveDir, 'fullchain.pem');
 | 
			
		||||
  var chainPath = args.chainPath || obj.chain || path.join(liveDir, 'chain.pem');
 | 
			
		||||
  var privkeyPath = args.privkeyPath || obj.privkey
 | 
			
		||||
    //|| args.domainPrivateKeyPath || args.domainKeyPath || obj.keyPath
 | 
			
		||||
    || path.join(liveDir, 'privkey.pem');
 | 
			
		||||
 | 
			
		||||
  log('[le/core.js] privkeyPath', privkeyPath);
 | 
			
		||||
 | 
			
		||||
  var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var checkpoints = obj.checkpoints.toString();
 | 
			
		||||
  var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem');
 | 
			
		||||
  var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem');
 | 
			
		||||
  var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem');
 | 
			
		||||
  var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem');
 | 
			
		||||
 | 
			
		||||
  return mkdirpAsync(archiveDir).then(function () {
 | 
			
		||||
    return PromiseA.all([
 | 
			
		||||
      sfs.writeFileAsync(certArchive, result.cert, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(chainArchive, (result.chain || result.ca), 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(
 | 
			
		||||
        privkeyArchive
 | 
			
		||||
        // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      , (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
      , 'ascii'
 | 
			
		||||
      )
 | 
			
		||||
    ]);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return mkdirpAsync(liveDir);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return PromiseA.all([
 | 
			
		||||
      sfs.writeFileAsync(certPath, result.cert, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(chainPath, (result.chain || result.ca), 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(
 | 
			
		||||
        privkeyPath
 | 
			
		||||
        // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      , (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
      , 'ascii'
 | 
			
		||||
      )
 | 
			
		||||
    ]);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    obj.checkpoints += 1;
 | 
			
		||||
    args.checkpoints += 1;
 | 
			
		||||
 | 
			
		||||
    return writeRenewalConfig(args);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    var getCertInfo = require('./cert-info').getCertInfo;
 | 
			
		||||
 | 
			
		||||
    // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | 
			
		||||
    var certInfo = getCertInfo(result.cert);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      certPath: certPath
 | 
			
		||||
    , chainPath: chainPath
 | 
			
		||||
    , fullchainPath: fullchainPath
 | 
			
		||||
    , privkeyPath: privkeyPath
 | 
			
		||||
 | 
			
		||||
      // TODO nix keypair
 | 
			
		||||
    , keypair: args.domainKeypair
 | 
			
		||||
 | 
			
		||||
      // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      // some ambiguity here...
 | 
			
		||||
    , privkey: (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
    , fullchain: result.fullchain || (result.cert + '\n' + result.chain)
 | 
			
		||||
    , chain:  (result.chain || result.ca)
 | 
			
		||||
      // especially this one... might be cert only, might be fullchain
 | 
			
		||||
    , cert: result.cert
 | 
			
		||||
 | 
			
		||||
    , issuedAt: Date(certInfo.notBefore.value).valueOf() // Date.now()
 | 
			
		||||
    , expiresAt: Date(certInfo.notAfter.value).valueOf()
 | 
			
		||||
    , lifetime: defaults.lifetime || handlers.lifetime
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCertificateAsync(args, defaults, handlers) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug || defaults.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var account = args.account;
 | 
			
		||||
  var promise;
 | 
			
		||||
  var keypairOpts = { public: true, pem: true };
 | 
			
		||||
 | 
			
		||||
  log('[le/core.js] domainKeyPath:', args.domainKeyPath);
 | 
			
		||||
 | 
			
		||||
  promise = fs.readFileAsync(args.domainKeyPath, 'ascii').then(function (pem) {
 | 
			
		||||
    return RSA.import({ privateKeyPem: pem });
 | 
			
		||||
  }, function (/*err*/) {
 | 
			
		||||
    return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
 | 
			
		||||
      return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () {
 | 
			
		||||
        return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () {
 | 
			
		||||
          return keypair;
 | 
			
		||||
        return backend.setAccountAsync(args, account).then(function () {
 | 
			
		||||
          return account;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return promise.then(function (domainKeypair) {
 | 
			
		||||
    log("[le/core.js] get certificate");
 | 
			
		||||
 | 
			
		||||
    args.domainKeypair = domainKeypair;
 | 
			
		||||
    //args.registration = domainKey;
 | 
			
		||||
 | 
			
		||||
    return LeCore.getCertificateAsync({
 | 
			
		||||
      debug: args.debug
 | 
			
		||||
 | 
			
		||||
    , newAuthzUrl: args._acmeUrls.newAuthz
 | 
			
		||||
    , newCertUrl: args._acmeUrls.newCert
 | 
			
		||||
 | 
			
		||||
    , accountKeypair: RSA.import(account.keypair)
 | 
			
		||||
    , domainKeypair: domainKeypair
 | 
			
		||||
    , domains: args.domains
 | 
			
		||||
 | 
			
		||||
      //
 | 
			
		||||
      // IMPORTANT
 | 
			
		||||
      //
 | 
			
		||||
      // setChallenge and removeChallenge are handed defaults
 | 
			
		||||
      // instead of args because getChallenge does not have
 | 
			
		||||
      // access to args
 | 
			
		||||
      // (args is per-request, defaults is per instance)
 | 
			
		||||
      //
 | 
			
		||||
    , setChallenge: function (domain, key, value, done) {
 | 
			
		||||
        var copy = merge(defaults, { domains: [domain] });
 | 
			
		||||
        tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
        args.domains = [domain];
 | 
			
		||||
        args.webrootPath = args.webrootPath;
 | 
			
		||||
        if (4 === handlers.setChallenge.length) {
 | 
			
		||||
          handlers.setChallenge(copy, key, value, done);
 | 
			
		||||
        }
 | 
			
		||||
        else if (5 === handlers.setChallenge.length) {
 | 
			
		||||
          handlers.setChallenge(copy, domain, key, value, done);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          done(new Error("handlers.setChallenge receives the wrong number of arguments"));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    , removeChallenge: function (domain, key, done) {
 | 
			
		||||
        var copy = merge(defaults, { domains: [domain] });
 | 
			
		||||
        tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
        if (3 === handlers.removeChallenge.length) {
 | 
			
		||||
          handlers.removeChallenge(copy, key, done);
 | 
			
		||||
        }
 | 
			
		||||
        else if (4 === handlers.removeChallenge.length) {
 | 
			
		||||
          handlers.removeChallenge(copy, domain, key, done);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          done(new Error("handlers.removeChallenge receives the wrong number of arguments"));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function (results) {
 | 
			
		||||
    // { cert, chain, fullchain, privkey }
 | 
			
		||||
    args.pems = results;
 | 
			
		||||
    return writeCertificateAsync(args, defaults, handlers);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOrCreateDomainCertificate(args, defaults, handlers) {
 | 
			
		||||
  if (args.duplicate) {
 | 
			
		||||
    // we're forcing a refresh via 'dupliate: true'
 | 
			
		||||
    return getCertificateAsync(args, defaults, handlers);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return fetchFromConfigLiveDir(args).then(function (certs) {
 | 
			
		||||
    var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
 | 
			
		||||
  function getAcmeUrls(args) {
 | 
			
		||||
    var now = Date.now();
 | 
			
		||||
 | 
			
		||||
    if (!certs || (Date.now() - certs.issuedAt) > halfLife) {
 | 
			
		||||
      // There is no cert available
 | 
			
		||||
      // Or the cert is more than half-expired
 | 
			
		||||
    // TODO check response header on request for cache time
 | 
			
		||||
    if ((now - ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
 | 
			
		||||
      return PromiseA.resolve(ipc.acmeUrls);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
 | 
			
		||||
      ipc.acmeUrlsUpdatedAt = Date.now();
 | 
			
		||||
      ipc.acmeUrls = data;
 | 
			
		||||
 | 
			
		||||
      return ipc.acmeUrls;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getCertificateAsync(args, defaults, handlers) {
 | 
			
		||||
    function log() {
 | 
			
		||||
      if (args.debug || defaults.debug) {
 | 
			
		||||
        console.log.apply(console, arguments);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var account = args.account;
 | 
			
		||||
    var promise;
 | 
			
		||||
    var keypairOpts = { public: true, pem: true };
 | 
			
		||||
 | 
			
		||||
    promise = backend.getPrivatePem(args).then(function (pem) {
 | 
			
		||||
      return RSA.import({ privateKeyPem: pem });
 | 
			
		||||
    }, function (/*err*/) {
 | 
			
		||||
      return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
 | 
			
		||||
        keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
 | 
			
		||||
        keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
 | 
			
		||||
        return backend.setPrivatePem(args, keypair);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return promise.then(function (domainKeypair) {
 | 
			
		||||
      log("[le/core.js] get certificate");
 | 
			
		||||
 | 
			
		||||
      args.domainKeypair = domainKeypair;
 | 
			
		||||
      //args.registration = domainKey;
 | 
			
		||||
 | 
			
		||||
      return LeCore.getCertificateAsync({
 | 
			
		||||
        debug: args.debug
 | 
			
		||||
 | 
			
		||||
      , newAuthzUrl: args._acmeUrls.newAuthz
 | 
			
		||||
      , newCertUrl: args._acmeUrls.newCert
 | 
			
		||||
 | 
			
		||||
      , accountKeypair: RSA.import(account.keypair)
 | 
			
		||||
      , domainKeypair: domainKeypair
 | 
			
		||||
      , domains: args.domains
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // IMPORTANT
 | 
			
		||||
        //
 | 
			
		||||
        // setChallenge and removeChallenge are handed defaults
 | 
			
		||||
        // instead of args because getChallenge does not have
 | 
			
		||||
        // access to args
 | 
			
		||||
        // (args is per-request, defaults is per instance)
 | 
			
		||||
        //
 | 
			
		||||
      , setChallenge: function (domain, key, value, done) {
 | 
			
		||||
          var copy = handlers.merge({ domains: [domain] }, defaults);
 | 
			
		||||
          handlers.tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
          args.domains = [domain];
 | 
			
		||||
          //args.domains = args.domains || [domain];
 | 
			
		||||
          if (4 === handlers.setChallenge.length) {
 | 
			
		||||
            console.warn('[WARNING] deprecated use. Define setChallenge as function (opts, domain, key, val, cb) { }');
 | 
			
		||||
            handlers.setChallenge(copy, key, value, done);
 | 
			
		||||
          }
 | 
			
		||||
          else if (5 === handlers.setChallenge.length) {
 | 
			
		||||
            handlers.setChallenge(copy, domain, key, value, done);
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            done(new Error("handlers.setChallenge receives the wrong number of arguments"));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      , removeChallenge: function (domain, key, done) {
 | 
			
		||||
          var copy = handlers.merge({ domains: [domain] }, defaults);
 | 
			
		||||
          handlers.tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
          if (3 === handlers.removeChallenge.length) {
 | 
			
		||||
            handlers.removeChallenge(copy, key, done);
 | 
			
		||||
          }
 | 
			
		||||
          else if (4 === handlers.removeChallenge.length) {
 | 
			
		||||
            handlers.removeChallenge(copy, domain, key, done);
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            done(new Error("handlers.removeChallenge receives the wrong number of arguments"));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }).then(function (results) {
 | 
			
		||||
      // { cert, chain, fullchain, privkey }
 | 
			
		||||
      args.pems = results;
 | 
			
		||||
      return backend.setRegistration(args, defaults, handlers);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getOrCreateDomainCertificate(args, defaults, handlers) {
 | 
			
		||||
    if (args.duplicate) {
 | 
			
		||||
      // we're forcing a refresh via 'dupliate: true'
 | 
			
		||||
      return getCertificateAsync(args, defaults, handlers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.reject(new Error(
 | 
			
		||||
        "[ERROR] Certificate issued at '"
 | 
			
		||||
      + new Date(certs.issuedAt).toISOString() + "' and expires at '"
 | 
			
		||||
      + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '"
 | 
			
		||||
      + new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force."
 | 
			
		||||
    ));
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
    return backend.getRegistration(args).then(function (certs) {
 | 
			
		||||
      var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
 | 
			
		||||
 | 
			
		||||
// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
 | 
			
		||||
function getOrCreateAcmeAccount(args, defaults, handlers) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
      if (!certs || (Date.now() - certs.issuedAt) > halfLife) {
 | 
			
		||||
        // There is no cert available
 | 
			
		||||
        // Or the cert is more than half-expired
 | 
			
		||||
        return getCertificateAsync(args, defaults, handlers);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return PromiseA.reject(new Error(
 | 
			
		||||
          "[ERROR] Certificate issued at '"
 | 
			
		||||
        + new Date(certs.issuedAt).toISOString() + "' and expires at '"
 | 
			
		||||
        + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '"
 | 
			
		||||
        + new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force."
 | 
			
		||||
      ));
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
 | 
			
		||||
  return pyconf.readFileAsync(args.renewalPath).then(function (renewal) {
 | 
			
		||||
    var accountId = renewal.account;
 | 
			
		||||
    renewal = renewal.account;
 | 
			
		||||
 | 
			
		||||
    return accountId;
 | 
			
		||||
  }, function (err) {
 | 
			
		||||
    if ("ENOENT" === err.code) {
 | 
			
		||||
      log("[le/core.js] try email");
 | 
			
		||||
      return Accounts.getAccountIdByEmail(args, handlers);
 | 
			
		||||
  // returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
 | 
			
		||||
  function getOrCreateAcmeAccount(args, defaults, handlers) {
 | 
			
		||||
    function log() {
 | 
			
		||||
      if (args.debug) {
 | 
			
		||||
        console.log.apply(console, arguments);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.reject(err);
 | 
			
		||||
  }).then(function (accountId) {
 | 
			
		||||
    return backend.getAccountId(args).then(function (accountId) {
 | 
			
		||||
 | 
			
		||||
    // Note: the ACME urls are always fetched fresh on purpose
 | 
			
		||||
    return getAcmeUrls(args).then(function (urls) {
 | 
			
		||||
      args._acmeUrls = urls;
 | 
			
		||||
      // Note: the ACME urls are always fetched fresh on purpose
 | 
			
		||||
      return getAcmeUrls(args).then(function (urls) {
 | 
			
		||||
        args._acmeUrls = urls;
 | 
			
		||||
 | 
			
		||||
      if (accountId) {
 | 
			
		||||
        log('[le/core.js] use account');
 | 
			
		||||
        if (accountId) {
 | 
			
		||||
          log('[le/core.js] use account');
 | 
			
		||||
 | 
			
		||||
        args.accountId = accountId;
 | 
			
		||||
        return Accounts.getAccount(args, handlers);
 | 
			
		||||
      } else {
 | 
			
		||||
        log('[le/core.js] create account');
 | 
			
		||||
        return Accounts.createAccount(args, handlers);
 | 
			
		||||
          args.accountId = accountId;
 | 
			
		||||
          return Accounts.getAccount(args, handlers);
 | 
			
		||||
        } else {
 | 
			
		||||
          log('[le/core.js] create account');
 | 
			
		||||
          return Accounts.createAccount(args, handlers);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }).then(function (account) {
 | 
			
		||||
      /*
 | 
			
		||||
      if (renewal.account !== account) {
 | 
			
		||||
        // the account has become corrupt, re-register
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      */
 | 
			
		||||
      log('[le/core.js] created account');
 | 
			
		||||
      return account;
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function (account) {
 | 
			
		||||
    /*
 | 
			
		||||
    if (renewal.account !== account) {
 | 
			
		||||
      // the account has become corrupt, re-register
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
    log('[le/core.js] created account');
 | 
			
		||||
    return account;
 | 
			
		||||
  });
 | 
			
		||||
/*
 | 
			
		||||
  return fs.readdirAsync(accountsDir, function (nodes) {
 | 
			
		||||
    return PromiseA.all(nodes.map(function (node) {
 | 
			
		||||
      var reMd5 = /[a-f0-9]{32}/i;
 | 
			
		||||
      if (reMd5.test(node)) {
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  });
 | 
			
		||||
*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.create = function (defaults, handlers) {
 | 
			
		||||
  defaults.server = defaults.server || LE.liveServer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var wrapped = {
 | 
			
		||||
    registerAsync: function (args) {
 | 
			
		||||
      var copy;
 | 
			
		||||
      // TODO move these defaults elsewhere?
 | 
			
		||||
      //args.renewalDir = args.renewalDir || ':config/renewal/';
 | 
			
		||||
      args.renewalPath = args.renewalPath || ':config/renewal/:hostname.conf';
 | 
			
		||||
      // Note: the /directory is part of the server url and, as such, bleeds into the pathname
 | 
			
		||||
      // So :config/accounts/:server/directory is *incorrect*, but the following *is* correct:
 | 
			
		||||
      args.accountsDir = args.accountsDir || ':config/accounts/:server';
 | 
			
		||||
      copy = merge(args, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      var url = require('url');
 | 
			
		||||
      var acmeLocation = url.parse(copy.server);
 | 
			
		||||
      var acmeHostpath = path.join(acmeLocation.hostname, acmeLocation.pathname);
 | 
			
		||||
      copy.renewalPath = copy.renewalPath || path.join(copy.configDir, 'renewal', copy.domains[0] + '.conf');
 | 
			
		||||
      copy.accountsDir = copy.accountsDir || path.join(copy.configDir, 'accounts', acmeHostpath);
 | 
			
		||||
   registerAsync: function (args) {
 | 
			
		||||
      var copy = handlers.merge(args, defaults);
 | 
			
		||||
      handlers.tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) {
 | 
			
		||||
        copy.account = account;
 | 
			
		||||
 | 
			
		||||
        return getOrCreateRenewal(copy).then(function (pyobj) {
 | 
			
		||||
        return backend.getOrCreateRenewal(copy).then(function (pyobj) {
 | 
			
		||||
 | 
			
		||||
          copy.pyobj = pyobj;
 | 
			
		||||
          return getOrCreateDomainCertificate(copy, defaults, handlers);
 | 
			
		||||
@ -459,55 +233,19 @@ module.exports.create = function (defaults, handlers) {
 | 
			
		||||
        return PromiseA.reject(err);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , fetchAsync: function (args) {
 | 
			
		||||
      var copy = merge(args, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return fetchFromConfigLiveDir(copy, defaults);
 | 
			
		||||
  , getOrCreateAccount: function (args) {
 | 
			
		||||
      // TODO
 | 
			
		||||
      keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
 | 
			
		||||
      keypair.publicKeyPem = RSA.exportPublicPem(keypair);
 | 
			
		||||
      return createAccount(args, handlers);
 | 
			
		||||
    }
 | 
			
		||||
  , configureAsync: function (hargs) {
 | 
			
		||||
      hargs.renewalPath = hargs.renewalPath || ':config/renewal/:hostname.conf';
 | 
			
		||||
      var copy = merge(hargs, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) {
 | 
			
		||||
        copy.account = account;
 | 
			
		||||
        return getOrCreateRenewal(copy);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , getConfigAsync: function (hargs) {
 | 
			
		||||
      hargs.renewalPath = hargs.renewalPath || ':config/renewal/:hostname.conf';
 | 
			
		||||
      hargs.domains = [];
 | 
			
		||||
 | 
			
		||||
      var copy = merge(hargs, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return readRenewalConfig(copy).then(function (pyobj) {
 | 
			
		||||
        var exists = pyobj.checkpoints >= 0;
 | 
			
		||||
        if (!exists) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return pyobj;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , getConfigsAsync: function (hargs) {
 | 
			
		||||
      hargs.renewalDir = hargs.renewalDir || ':config/renewal/';
 | 
			
		||||
      hargs.renewalPath = hargs.renewalPath || ':config/renewal/:hostname.conf';
 | 
			
		||||
      hargs.domains = [];
 | 
			
		||||
 | 
			
		||||
      var copy = merge(hargs, defaults);
 | 
			
		||||
      tplCopy(copy);
 | 
			
		||||
 | 
			
		||||
      return fs.readdirAsync(copy.renewalDir).then(function (nodes) {
 | 
			
		||||
        nodes = nodes.filter(function (node) {
 | 
			
		||||
          return /^[a-z0-9]+.*\.conf$/.test(node);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return PromiseA.all(nodes.map(function (node) {
 | 
			
		||||
          copy.domains = [node.replace(/\.conf$/, '')];
 | 
			
		||||
          return wrapped.getConfigAsync(copy);
 | 
			
		||||
        }));
 | 
			
		||||
        return backend.getOrCreateRenewal(copy);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,11 @@
 | 
			
		||||
var fs = require('fs');
 | 
			
		||||
var path = require('path');
 | 
			
		||||
 | 
			
		||||
module.exports.agreeToTerms = function (args, agree) {
 | 
			
		||||
  agree(null, args.agreeTos);
 | 
			
		||||
module.exports.agreeToTerms = function (args, agreeCb) {
 | 
			
		||||
  agreeCb(null, args.agreeTos);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.setChallenge = function (args, challengePath, keyAuthorization, done) {
 | 
			
		||||
module.exports.setChallenge = function (args, domain, challengePath, keyAuthorization, done) {
 | 
			
		||||
  //var hostname = args.domains[0];
 | 
			
		||||
  var mkdirp = require('mkdirp');
 | 
			
		||||
 | 
			
		||||
@ -26,14 +26,14 @@ module.exports.setChallenge = function (args, challengePath, keyAuthorization, d
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.getChallenge = function (args, key, done) {
 | 
			
		||||
module.exports.getChallenge = function (args, domain, key, done) {
 | 
			
		||||
  //var hostname = args.domains[0];
 | 
			
		||||
 | 
			
		||||
  //console.log("getting the challenge", args, key);
 | 
			
		||||
  fs.readFile(path.join(args.webrootPath, key), 'utf8', done);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.removeChallenge = function (args, key, done) {
 | 
			
		||||
module.exports.removeChallenge = function (args, domain, key, done) {
 | 
			
		||||
  //var hostname = args.domains[0];
 | 
			
		||||
 | 
			
		||||
  fs.unlink(path.join(args.webrootPath, key), done);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										517
									
								
								lib/pycompat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										517
									
								
								lib/pycompat.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,517 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var fs = PromiseA.promisifyAll(require('fs'));
 | 
			
		||||
var sfs = require('safe-replace');
 | 
			
		||||
 | 
			
		||||
var fetchFromConfigLiveDir = function (args) {
 | 
			
		||||
  // TODO NO HARD-CODED DEFAULTS
 | 
			
		||||
  if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) {
 | 
			
		||||
    console.warn("missing one or more of args.privkeyPath, args.fullchainPath, args.certPath, args.chainPath");
 | 
			
		||||
    console.warn("hard-coded conventional pathnames were for debugging and are not a stable part of the API");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //, fs.readFileAsync(fullchainPath, 'ascii')
 | 
			
		||||
  // note: if this ^^ gets added back in, the arrays below must change
 | 
			
		||||
  return PromiseA.all([
 | 
			
		||||
    fs.readFileAsync(args.privkeyPath, 'ascii')   // 0
 | 
			
		||||
  , fs.readFileAsync(args.certPath, 'ascii')      // 1
 | 
			
		||||
  , fs.readFileAsync(args.chainPath, 'ascii')     // 2
 | 
			
		||||
 | 
			
		||||
    // stat the file, not the link
 | 
			
		||||
  , fs.statAsync(args.certPath)                   // 3
 | 
			
		||||
  ]).then(function (arr) {
 | 
			
		||||
    var cert = arr[1];
 | 
			
		||||
    var getCertInfo = require('./cert-info').getCertInfo;
 | 
			
		||||
 | 
			
		||||
    // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | 
			
		||||
    var certInfo = getCertInfo(cert);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      key: arr[0]                           // privkey.pem
 | 
			
		||||
    , privkey: arr[0]                       // privkey.pem
 | 
			
		||||
 | 
			
		||||
    , fullchain: arr[1] + '\n' + arr[2]     // fullchain.pem
 | 
			
		||||
    , cert: cert                            // cert.pem
 | 
			
		||||
 | 
			
		||||
    , chain: arr[2]                         // chain.pem
 | 
			
		||||
    , ca: arr[2]                            // chain.pem
 | 
			
		||||
 | 
			
		||||
    , privkeyPath: args.privkeyPath
 | 
			
		||||
    , fullchainPath: args.fullchainPath
 | 
			
		||||
    , certPath: args.certPath
 | 
			
		||||
    , chainPath: args.chainPath
 | 
			
		||||
 | 
			
		||||
    //, issuedAt: arr[3].mtime.valueOf()
 | 
			
		||||
    , issuedAt: Date(certInfo.notBefore.value).valueOf() // Date.now()
 | 
			
		||||
    , expiresAt: Date(certInfo.notAfter.value).valueOf()
 | 
			
		||||
    , lifetime: args.lifetime
 | 
			
		||||
    };
 | 
			
		||||
  }, function (err) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.error("[letsencrypt/lib/common.js] fetchFromDisk");
 | 
			
		||||
      console.error(err.stack);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getAccount(args) {
 | 
			
		||||
  var accountId = args.accountId;
 | 
			
		||||
  var accountDir = path.join(args.accountsDir, accountId);
 | 
			
		||||
  var files = {};
 | 
			
		||||
  var configs = [ 'meta.json', 'private_key.json', 'regr.json' ];
 | 
			
		||||
 | 
			
		||||
  return PromiseA.all(configs.map(function (filename) {
 | 
			
		||||
    var keyname = filename.slice(0, -5);
 | 
			
		||||
 | 
			
		||||
    return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) {
 | 
			
		||||
      var data;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        data = JSON.parse(text);
 | 
			
		||||
      } catch(e) {
 | 
			
		||||
        files[keyname] = { error: e };
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      files[keyname] = data;
 | 
			
		||||
    }, function (err) {
 | 
			
		||||
      files[keyname] = { error: err };
 | 
			
		||||
    });
 | 
			
		||||
  })).then(function () {
 | 
			
		||||
    var err;
 | 
			
		||||
 | 
			
		||||
    if (!Object.keys(files).every(function (key) {
 | 
			
		||||
      return !files[key].error;
 | 
			
		||||
    }) || !files.private_key || !files.private_key.n) {
 | 
			
		||||
      err = new Error("Account '" + accountId + "' was corrupt. No big deal (I think?). Creating a new one...");
 | 
			
		||||
      err.code = 'E_ACCOUNT_CORRUPT';
 | 
			
		||||
      err.data = files;
 | 
			
		||||
      return PromiseA.reject(err);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //files.private_key;
 | 
			
		||||
    //files.regr;
 | 
			
		||||
    //files.meta;
 | 
			
		||||
    files.accountId = accountId;                  // preserve current account id
 | 
			
		||||
    files.id = accountId;
 | 
			
		||||
    files.keypair = { privateKeyJwk: files.private_key };
 | 
			
		||||
 | 
			
		||||
    return files;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAccountIdByEmail(args) {
 | 
			
		||||
  // If we read 10,000 account directories looking for
 | 
			
		||||
  // just one email address, that could get crazy.
 | 
			
		||||
  // We should have a folder per email and list
 | 
			
		||||
  // each account as a file in the folder
 | 
			
		||||
  // TODO
 | 
			
		||||
  var email = args.email;
 | 
			
		||||
  if ('string' !== typeof email) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log("[LE] No email given");
 | 
			
		||||
    }
 | 
			
		||||
    return PromiseA.resolve(null);
 | 
			
		||||
  }
 | 
			
		||||
  return fs.readdirAsync(args.accountsDir).then(function (nodes) {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log("[LE] arg.accountsDir success");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.all(nodes.map(function (node) {
 | 
			
		||||
      return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) {
 | 
			
		||||
        var regr = JSON.parse(text);
 | 
			
		||||
        regr.__accountId = node;
 | 
			
		||||
 | 
			
		||||
        return regr;
 | 
			
		||||
      });
 | 
			
		||||
    })).then(function (regrs) {
 | 
			
		||||
      var accountId;
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
      if (args.debug) {
 | 
			
		||||
        console.log('read many regrs');
 | 
			
		||||
        console.log('regrs', regrs);
 | 
			
		||||
      }
 | 
			
		||||
      */
 | 
			
		||||
 | 
			
		||||
      regrs.some(function (regr) {
 | 
			
		||||
        return regr.body.contact.some(function (contact) {
 | 
			
		||||
          var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase();
 | 
			
		||||
          if (match) {
 | 
			
		||||
            accountId = regr.__accountId;
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!accountId) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return accountId;
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function (accountId) {
 | 
			
		||||
    return accountId;
 | 
			
		||||
  }, function (err) {
 | 
			
		||||
    if ('ENOENT' === err.code) {
 | 
			
		||||
      // ignore error
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PromiseA.reject(err);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function readRenewalConfig(args) {
 | 
			
		||||
  var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
 | 
			
		||||
  return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) {
 | 
			
		||||
    return pyobj;
 | 
			
		||||
  }, function () {
 | 
			
		||||
    return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) {
 | 
			
		||||
      return pyobj;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function writeRenewalConfig(args) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var pyobj = args.pyobj;
 | 
			
		||||
  pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
 | 
			
		||||
 | 
			
		||||
  var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
 | 
			
		||||
  var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
 | 
			
		||||
  var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
 | 
			
		||||
  var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
 | 
			
		||||
  var privkeyPath = args.privkeyPath || pyobj.privkey
 | 
			
		||||
    //|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath
 | 
			
		||||
    || path.join(liveDir, 'privkey.pem');
 | 
			
		||||
 | 
			
		||||
  log('[le/core.js] privkeyPath', privkeyPath);
 | 
			
		||||
 | 
			
		||||
  var updates = {
 | 
			
		||||
    account: args.account.id
 | 
			
		||||
  , configDir: args.configDir
 | 
			
		||||
  , domains: args.domains
 | 
			
		||||
 | 
			
		||||
  , email: args.email
 | 
			
		||||
  , tos: args.agreeTos && true
 | 
			
		||||
    // yes, it's an array. weird, right?
 | 
			
		||||
  , webrootPath: args.webrootPath && [args.webrootPath] || []
 | 
			
		||||
  , server: args.server || args.acmeDiscoveryUrl
 | 
			
		||||
 | 
			
		||||
  , privkey: privkeyPath
 | 
			
		||||
  , fullchain: fullchainPath
 | 
			
		||||
  , cert: certPath
 | 
			
		||||
  , chain: chainPath
 | 
			
		||||
 | 
			
		||||
  , http01Port: args.http01Port
 | 
			
		||||
  , keyPath: args.domainPrivateKeyPath || args.privkeyPath
 | 
			
		||||
  , rsaKeySize: args.rsaKeySize
 | 
			
		||||
  , checkpoints: pyobj.checkpoints
 | 
			
		||||
    /* // TODO XXX what's the deal with these? they don't make sense
 | 
			
		||||
    // are they just old junk? or do they have a meaning that I don't know about?
 | 
			
		||||
  , fullchainPath: path.join(args.configDir, 'chain.pem')
 | 
			
		||||
  , certPath: path.join(args.configDir, 'cert.pem')
 | 
			
		||||
  , chainPath: path.join(args.configDir, 'chain.pem')
 | 
			
		||||
    */ // TODO XXX end
 | 
			
		||||
  , workDir: args.workDir
 | 
			
		||||
  , logsDir: args.logsDir
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // final section is completely dynamic
 | 
			
		||||
  // :hostname = :webroot_path
 | 
			
		||||
  args.domains.forEach(function (hostname) {
 | 
			
		||||
    updates[hostname] = args.webrootPath;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // must write back to the original pyobject or
 | 
			
		||||
  // annotations will be lost
 | 
			
		||||
  Object.keys(updates).forEach(function (key) {
 | 
			
		||||
    pyobj[key] = updates[key];
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return mkdirpAsync(path.dirname(args.renewalPath)).then(function () {
 | 
			
		||||
    return pyconf.writeFileAsync(args.renewalPath, pyobj);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    // NOTE
 | 
			
		||||
    // writing twice seems to causes a bug,
 | 
			
		||||
    // so instead we re-read the file from the disk
 | 
			
		||||
    return pyconf.readFileAsync(args.renewalPath);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOrCreateRenewal(args) {
 | 
			
		||||
  return readRenewalConfig(args).then(function (pyobj) {
 | 
			
		||||
    var minver = pyobj.checkpoints >= 0;
 | 
			
		||||
 | 
			
		||||
    args.pyobj = pyobj;
 | 
			
		||||
 | 
			
		||||
    if (!minver) {
 | 
			
		||||
      args.checkpoints = 0;
 | 
			
		||||
      pyobj.checkpoints = 0;
 | 
			
		||||
      return writeRenewalConfig(args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // args.account.id = pyobj.account
 | 
			
		||||
    // args.configDir = args.configDir || pyobj.configDir;
 | 
			
		||||
 | 
			
		||||
    args.checkpoints = pyobj.checkpoints;
 | 
			
		||||
 | 
			
		||||
    args.agreeTos = (args.agreeTos || pyobj.tos) && true;
 | 
			
		||||
    args.email = args.email || pyobj.email;
 | 
			
		||||
    args.domains = args.domains || pyobj.domains;
 | 
			
		||||
 | 
			
		||||
    // yes, it's an array. weird, right?
 | 
			
		||||
    args.webrootPath = args.webrootPath || pyobj.webrootPath[0];
 | 
			
		||||
    args.server = args.server || args.acmeDiscoveryUrl || pyobj.server;
 | 
			
		||||
 | 
			
		||||
    args.certPath = args.certPath || pyobj.cert;
 | 
			
		||||
    args.privkeyPath = args.privkeyPath || pyobj.privkey;
 | 
			
		||||
    args.chainPath = args.chainPath || pyobj.chain;
 | 
			
		||||
    args.fullchainPath = args.fullchainPath || pyobj.fullchain;
 | 
			
		||||
 | 
			
		||||
  //, workDir: args.workDir
 | 
			
		||||
  //, logsDir: args.logsDir
 | 
			
		||||
    args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize;
 | 
			
		||||
    args.http01Port = args.http01Port || pyobj.http01Port;
 | 
			
		||||
    args.domainKeyPath = args.domainPrivateKeyPath || args.domainKeyPath || args.keyPath || pyobj.keyPath;
 | 
			
		||||
 | 
			
		||||
    return writeRenewalConfig(args);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function writeCertificateAsync(args) {
 | 
			
		||||
  function log() {
 | 
			
		||||
    if (args.debug) {
 | 
			
		||||
      console.log.apply(console, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  log("[le/core.js] got certificate!");
 | 
			
		||||
 | 
			
		||||
  var obj = args.pyobj;
 | 
			
		||||
  var pems = args.pems;
 | 
			
		||||
 | 
			
		||||
  pems.fullchain = pems.cert + '\n' + (pems.chain || pems.ca);
 | 
			
		||||
  obj.checkpoints = parseInt(obj.checkpoints, 10) || 0;
 | 
			
		||||
 | 
			
		||||
  var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var certPath = args.certPath || obj.cert || path.join(liveDir, 'cert.pem');
 | 
			
		||||
  var fullchainPath = args.fullchainPath || obj.fullchain || path.join(liveDir, 'fullchain.pem');
 | 
			
		||||
  var chainPath = args.chainPath || obj.chain || path.join(liveDir, 'chain.pem');
 | 
			
		||||
  var privkeyPath = args.privkeyPath || obj.privkey
 | 
			
		||||
    //|| args.domainPrivateKeyPath || args.domainKeyPath || obj.keyPath
 | 
			
		||||
    || path.join(liveDir, 'privkey.pem');
 | 
			
		||||
 | 
			
		||||
  log('[le/core.js] privkeyPath', privkeyPath);
 | 
			
		||||
 | 
			
		||||
  var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]);
 | 
			
		||||
 | 
			
		||||
  var checkpoints = obj.checkpoints.toString();
 | 
			
		||||
  var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem');
 | 
			
		||||
  var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem');
 | 
			
		||||
  var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem');
 | 
			
		||||
  var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem');
 | 
			
		||||
 | 
			
		||||
  return mkdirpAsync(archiveDir).then(function () {
 | 
			
		||||
    return PromiseA.all([
 | 
			
		||||
      sfs.writeFileAsync(certArchive, pems.cert, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(chainArchive, (pems.chain || pems.ca), 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(fullchainArchive, pems.fullchain, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(
 | 
			
		||||
        privkeyArchive
 | 
			
		||||
        // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
      , 'ascii'
 | 
			
		||||
      )
 | 
			
		||||
    ]);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return mkdirpAsync(liveDir);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return PromiseA.all([
 | 
			
		||||
      sfs.writeFileAsync(certPath, pems.cert, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(chainPath, (pems.chain || pems.ca), 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(fullchainPath, pems.fullchain, 'ascii')
 | 
			
		||||
    , sfs.writeFileAsync(
 | 
			
		||||
        privkeyPath
 | 
			
		||||
        // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
      , 'ascii'
 | 
			
		||||
      )
 | 
			
		||||
    ]);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    obj.checkpoints += 1;
 | 
			
		||||
    args.checkpoints += 1;
 | 
			
		||||
 | 
			
		||||
    return writeRenewalConfig(args);
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    var getCertInfo = require('./cert-info').getCertInfo;
 | 
			
		||||
 | 
			
		||||
    // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | 
			
		||||
    var certInfo = getCertInfo(pems.cert);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      certPath: certPath
 | 
			
		||||
    , chainPath: chainPath
 | 
			
		||||
    , fullchainPath: fullchainPath
 | 
			
		||||
    , privkeyPath: privkeyPath
 | 
			
		||||
 | 
			
		||||
      // TODO nix keypair
 | 
			
		||||
    , keypair: args.domainKeypair
 | 
			
		||||
 | 
			
		||||
      // TODO nix args.key, args.domainPrivateKeyPem ??
 | 
			
		||||
      // some ambiguity here...
 | 
			
		||||
    , privkey: (pems.privkey || pems.key) //|| RSA.exportPrivatePem(args.domainKeypair)
 | 
			
		||||
    , fullchain: pems.fullchain || (pems.cert + '\n' + pems.chain)
 | 
			
		||||
    , chain:  (pems.chain || pems.ca)
 | 
			
		||||
      // especially this one... might be cert only, might be fullchain
 | 
			
		||||
    , cert: pems.cert
 | 
			
		||||
 | 
			
		||||
    , issuedAt: Date(certInfo.notBefore.value).valueOf() // Date.now()
 | 
			
		||||
    , expiresAt: Date(certInfo.notAfter.value).valueOf()
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.create = function (/*defaults*/) {
 | 
			
		||||
  function getConfigAsync(copy) {
 | 
			
		||||
    copy.domains = [];
 | 
			
		||||
 | 
			
		||||
    return readRenewalConfig(copy).then(function (pyobj) {
 | 
			
		||||
      var exists = pyobj.checkpoints >= 0;
 | 
			
		||||
      if (!exists) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return pyobj;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    getDefaults: function () {
 | 
			
		||||
LE.tplConfigDir = require('./lib/common').tplConfigDir;
 | 
			
		||||
  // replaces strings of workDir, certPath, etc
 | 
			
		||||
  // if they have :config/etc/live or :conf/etc/archive
 | 
			
		||||
  // to instead have the path of the configDir
 | 
			
		||||
  LE.tplConfigDir(defaults.configDir, defaults);
 | 
			
		||||
      return {
 | 
			
		||||
        configDir: require('homedir')() + '/letsencrypt/etc'    // /etc/letsencrypt/
 | 
			
		||||
      , logsDir: ':config/log'                                  // /var/log/letsencrypt/
 | 
			
		||||
      , workDir: leCore.workDir // /var/lib/letsencrypt/
 | 
			
		||||
      , accountsDir: ':config/accounts/:server'
 | 
			
		||||
      , renewalPath: ':config/renewal/:hostname.conf'
 | 
			
		||||
      , renewalDir: ':config/renewal/'
 | 
			
		||||
      , privkeyPath: ':config/live/:hostname/privkey.pem'
 | 
			
		||||
      , fullchainPath: ':config/live/:hostname/fullchain.pem'
 | 
			
		||||
      , certPath: ':config/live/:hostname/cert.pem'
 | 
			
		||||
      , chainPath: ':config/live/:hostname/chain.pem'
 | 
			
		||||
      , renewalPath: ':config/renewal/:hostname.conf'
 | 
			
		||||
      , accountsDir: ':config/accounts/:server'
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  , getPrivatePemAsync: function (args) {
 | 
			
		||||
      return fs.readFileAsync(args.domainKeyPath, 'ascii');
 | 
			
		||||
    }
 | 
			
		||||
  , setPrivatePemAsync: function (args, keypair) {
 | 
			
		||||
      return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () {
 | 
			
		||||
        return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () {
 | 
			
		||||
          return keypair;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , setRegistrationAsync: function (args) {
 | 
			
		||||
      return writeCertificateAsync(args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  , getRegistrationAsync: function (args) {
 | 
			
		||||
      return fetchFromConfigLiveDir(args);
 | 
			
		||||
    }
 | 
			
		||||
  , getOrCreateRenewalAsync: function (args) {
 | 
			
		||||
      return getOrCreateRenewal(args);
 | 
			
		||||
    }
 | 
			
		||||
  , getConfigAsync: getConfigAsync
 | 
			
		||||
  , getConfigsAsync: function (copy) {
 | 
			
		||||
      copy.domains = [];
 | 
			
		||||
 | 
			
		||||
      return fs.readdirAsync(copy.renewalDir).then(function (nodes) {
 | 
			
		||||
        nodes = nodes.filter(function (node) {
 | 
			
		||||
          return /^[a-z0-9]+.*\.conf$/.test(node);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return PromiseA.all(nodes.map(function (node) {
 | 
			
		||||
          copy.domains = [node.replace(/\.conf$/, '')];
 | 
			
		||||
          return getConfigAsync(copy);
 | 
			
		||||
        }));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , fetchAsync: function (args) {
 | 
			
		||||
      return fetchFromConfigLiveDir(args);
 | 
			
		||||
    }
 | 
			
		||||
  , getAccountIdByEmailAsync: getAccountIdByEmail
 | 
			
		||||
  , getAccountAsync: getAccount
 | 
			
		||||
  , setAccountAsync: function (args, account) {
 | 
			
		||||
      var isoDate = new Date().toISOString();
 | 
			
		||||
      var os = require("os");
 | 
			
		||||
      var localname = os.hostname();
 | 
			
		||||
      var accountDir = path.join(args.accountsDir, account.accountId);
 | 
			
		||||
 | 
			
		||||
      account.meta = account.meta || {
 | 
			
		||||
        creation_host: localname
 | 
			
		||||
      , creation_dt: isoDate
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return mkdirpAsync(accountDir).then(function () {
 | 
			
		||||
        var RSA = require('rsa-compat').RSA;
 | 
			
		||||
 | 
			
		||||
        // TODO abstract file writing
 | 
			
		||||
        return PromiseA.all([
 | 
			
		||||
          // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"}
 | 
			
		||||
          fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(account.meta), 'utf8')
 | 
			
		||||
          // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" }
 | 
			
		||||
        , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(account.keypair)), 'utf8')
 | 
			
		||||
          // regr.json:
 | 
			
		||||
          /*
 | 
			
		||||
          { body: { contact: [ 'mailto:coolaj86@gmail.com' ],
 | 
			
		||||
           agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf',
 | 
			
		||||
           key: { e: 'AQAB', kty: 'RSA', n: '...' } },
 | 
			
		||||
            uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272',
 | 
			
		||||
            new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz',
 | 
			
		||||
            terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' }
 | 
			
		||||
           */
 | 
			
		||||
        , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(account.regr), 'utf8')
 | 
			
		||||
        ]);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  , getAccountIdAsync: function (args) {
 | 
			
		||||
      var pyconf = PromiseA.promisifyAll(require('pyconf'));
 | 
			
		||||
 | 
			
		||||
      return pyconf.readFileAsync(args.renewalPath).then(function (renewal) {
 | 
			
		||||
        var accountId = renewal.account;
 | 
			
		||||
        renewal = renewal.account;
 | 
			
		||||
 | 
			
		||||
        return accountId;
 | 
			
		||||
      }, function (err) {
 | 
			
		||||
        if ("ENOENT" === err.code) {
 | 
			
		||||
          return getAccountIdByEmail(args);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return PromiseA.reject(err);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -30,8 +30,6 @@
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://github.com/Daplie/node-letsencrypt#readme",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "express": "^4.13.3",
 | 
			
		||||
    "localhost.daplie.com-certificates": "^1.1.2"
 | 
			
		||||
  },
 | 
			
		||||
  "optionalDependencies": {},
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user