Browse Source

partial refactor

greenlock
AJ ONeal 8 years ago
parent
commit
fd02f44c13
  1. 7
      examples/README.md
  2. 64
      examples/simple.js
  3. 71
      index.js
  4. 195
      lib/accounts.js
  5. 75
      lib/common.js
  6. 420
      lib/core.js
  7. 10
      lib/default-handlers.js
  8. 517
      lib/pycompat.js
  9. 2
      package.json

7
examples/README.md

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

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

71
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

@ -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;

75
lib/common.js

@ -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 = {};
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;
});
};

420
lib/core.js

@ -1,22 +1,60 @@
'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 ipc = {}; // in-process cache
var merge = require('./common').merge;
var tplCopy = require('./common').tplCopy;
var fetchFromConfigLiveDir = require('./common').fetchFromDisk;
module.exports.create = function (defaults, handlers, backend) {
defaults.server = defaults.server || LE.liveServer;
handlers.merge = require('./common').merge;
handlers.tplCopy = require('./common').tplCopy;
var PromiseA = require('bluebird');
var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA);
var LeCore = PromiseA.promisifyAll(require('letiny-core'));
var crypto = require('crypto');
function createAccount(args, handlers) {
// 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: 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');
var accountId = keypair.publicKeyMd5;
var regr = { body: body };
var account = {};
var ipc = {}; // in-process cache
args.accountId = accountId;
account.keypair = keypair;
account.regr = regr;
account.accountId = accountId;
account.id = accountId;
args.account = account;
return backend.setAccountAsync(args, account).then(function () {
return account;
});
});
});
}
function getAcmeUrls(args) {
function getAcmeUrls(args) {
var now = Date.now();
// TODO check response header on request for cache time
@ -30,231 +68,9 @@ function getAcmeUrls(args) {
return ipc.acmeUrls;
});
}
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, 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 getCertificateAsync(args, defaults, handlers) {
function log() {
if (args.debug || defaults.debug) {
console.log.apply(console, arguments);
@ -265,17 +81,13 @@ function getCertificateAsync(args, defaults, handlers) {
var promise;
var keypairOpts = { public: true, pem: true };
log('[le/core.js] domainKeyPath:', args.domainKeyPath);
promise = fs.readFileAsync(args.domainKeyPath, 'ascii').then(function (pem) {
promise = backend.getPrivatePem(args).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;
});
});
keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
return backend.setPrivatePem(args, keypair);
});
});
@ -304,12 +116,13 @@ function getCertificateAsync(args, defaults, handlers) {
// (args is per-request, defaults is per instance)
//
, setChallenge: function (domain, key, value, done) {
var copy = merge(defaults, { domains: [domain] });
tplCopy(copy);
var copy = handlers.merge({ domains: [domain] }, defaults);
handlers.tplCopy(copy);
args.domains = [domain];
args.webrootPath = args.webrootPath;
//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) {
@ -320,8 +133,8 @@ function getCertificateAsync(args, defaults, handlers) {
}
}
, removeChallenge: function (domain, key, done) {
var copy = merge(defaults, { domains: [domain] });
tplCopy(copy);
var copy = handlers.merge({ domains: [domain] }, defaults);
handlers.tplCopy(copy);
if (3 === handlers.removeChallenge.length) {
handlers.removeChallenge(copy, key, done);
@ -337,17 +150,17 @@ function getCertificateAsync(args, defaults, handlers) {
}).then(function (results) {
// { cert, chain, fullchain, privkey }
args.pems = results;
return writeCertificateAsync(args, defaults, handlers);
return backend.setRegistration(args, defaults, handlers);
});
}
}
function getOrCreateDomainCertificate(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) {
return backend.getRegistration(args).then(function (certs) {
var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
if (!certs || (Date.now() - certs.issuedAt) > halfLife) {
@ -363,31 +176,17 @@ function getOrCreateDomainCertificate(args, defaults, handlers) {
+ new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force."
));
});
}
}
// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
function getOrCreateAcmeAccount(args, defaults, 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);
}
}
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);
}
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) {
@ -413,42 +212,17 @@ function getOrCreateAcmeAccount(args, defaults, handlers) {
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);
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);
});
}
};

10
lib/default-handlers.js

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

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

2
package.json

@ -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…
Cancel
Save