2015-12-15 11:37:39 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var PromiseA = require('bluebird');
|
2015-12-16 10:07:00 +00:00
|
|
|
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
|
2015-12-15 11:37:39 +00:00
|
|
|
var path = require('path');
|
2015-12-16 10:07:00 +00:00
|
|
|
var sfs = require('safe-replace');
|
2015-12-15 15:21:27 +00:00
|
|
|
var LE = require('../');
|
2015-12-16 09:11:31 +00:00
|
|
|
var LeCore = PromiseA.promisifyAll(require('letiny-core'));
|
2015-12-16 09:25:39 +00:00
|
|
|
var leCrypto = PromiseA.promisifyAll(LeCore.leCrypto);
|
2015-12-17 05:44:41 +00:00
|
|
|
var Accounts = require('./accounts');
|
2015-12-16 09:11:31 +00:00
|
|
|
|
2015-12-17 08:46:40 +00:00
|
|
|
var merge = require('./common').merge;
|
|
|
|
var tplHostname = require('./common').tplHostname;
|
2015-12-15 15:21:27 +00:00
|
|
|
var fetchFromConfigLiveDir = require('./common').fetchFromDisk;
|
2015-12-15 11:37:39 +00:00
|
|
|
|
|
|
|
var ipc = {}; // in-process cache
|
|
|
|
|
|
|
|
function getAcmeUrls(args) {
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
|
|
// TODO check response header on request for cache time
|
|
|
|
if ((now - ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
|
|
|
|
return PromiseA.resolve(ipc.acmeUrls);
|
|
|
|
}
|
|
|
|
|
2015-12-16 09:11:31 +00:00
|
|
|
return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
|
2015-12-15 11:37:39 +00:00
|
|
|
ipc.acmeUrlsUpdatedAt = Date.now();
|
2015-12-16 10:07:00 +00:00
|
|
|
ipc.acmeUrls = data;
|
2015-12-15 11:37:39 +00:00
|
|
|
|
|
|
|
return ipc.acmeUrls;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-12-15 15:21:27 +00:00
|
|
|
function getCertificateAsync(account, args, defaults, handlers) {
|
2015-12-16 09:25:39 +00:00
|
|
|
return leCrypto.generateRsaKeypairAsync(args.rsaBitLength, args.rsaExponent).then(function (domain) {
|
2015-12-16 10:07:00 +00:00
|
|
|
return LeCore.getCertificateAsync({
|
|
|
|
newAuthzUrl: args._acmeUrls.newAuthz
|
|
|
|
, newCertUrl: args._acmeUrls.newCert
|
2015-12-16 09:11:31 +00:00
|
|
|
|
2015-12-15 15:21:27 +00:00
|
|
|
, accountPrivateKeyPem: account.privateKeyPem
|
|
|
|
, domainPrivateKeyPem: domain.privateKeyPem
|
2015-12-16 09:11:31 +00:00
|
|
|
, domains: args.domains
|
|
|
|
|
2015-12-17 08:46:40 +00:00
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
//
|
2015-12-15 15:21:27 +00:00
|
|
|
, setChallenge: function (domain, key, value, done) {
|
2015-12-17 08:46:40 +00:00
|
|
|
var copy = merge(defaults, { domains: [domain] });
|
|
|
|
tplHostname(domain, copy);
|
|
|
|
|
2015-12-15 15:21:27 +00:00
|
|
|
args.domains = [domain];
|
2015-12-17 08:46:40 +00:00
|
|
|
args.webrootPath = args.webrootPath;
|
2015-12-17 04:44:28 +00:00
|
|
|
if (4 === handlers.setChallenge.length) {
|
2015-12-17 08:46:40 +00:00
|
|
|
handlers.setChallenge(copy, key, value, done);
|
2015-12-17 04:44:28 +00:00
|
|
|
}
|
|
|
|
else if (5 === handlers.setChallenge.length) {
|
2015-12-17 08:46:40 +00:00
|
|
|
handlers.setChallenge(copy, domain, key, value, done);
|
2015-12-17 04:44:28 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
done(new Error("handlers.setChallenge receives the wrong number of arguments"));
|
|
|
|
}
|
2015-12-15 15:21:27 +00:00
|
|
|
}
|
|
|
|
, removeChallenge: function (domain, key, done) {
|
2015-12-17 08:46:40 +00:00
|
|
|
var copy = merge(defaults, { domains: [domain] });
|
|
|
|
tplHostname(domain, copy);
|
|
|
|
|
2015-12-17 04:44:28 +00:00
|
|
|
if (3 === handlers.removeChallenge.length) {
|
2015-12-17 08:46:40 +00:00
|
|
|
handlers.removeChallenge(copy, key, done);
|
2015-12-17 04:44:28 +00:00
|
|
|
}
|
|
|
|
else if (4 === handlers.removeChallenge.length) {
|
2015-12-17 08:46:40 +00:00
|
|
|
handlers.removeChallenge(copy, domain, key, done);
|
2015-12-17 04:44:28 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
done(new Error("handlers.removeChallenge receives the wrong number of arguments"));
|
|
|
|
}
|
2015-12-15 15:21:27 +00:00
|
|
|
}
|
|
|
|
}).then(function (result) {
|
2015-12-16 09:11:31 +00:00
|
|
|
var liveDir = path.join(args.configDir, 'live', args.domains[0]);
|
|
|
|
var certPath = path.join(liveDir, 'cert.pem');
|
|
|
|
var fullchainPath = path.join(liveDir, 'fullchain.pem');
|
|
|
|
var chainPath = path.join(liveDir, 'chain.pem');
|
|
|
|
var privkeyPath = path.join(liveDir, 'privkey.pem');
|
|
|
|
|
|
|
|
result.fullchain = result.cert + '\n' + result.ca;
|
|
|
|
|
|
|
|
// TODO write to archive first, then write to live
|
2015-12-17 08:46:40 +00:00
|
|
|
|
|
|
|
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
// TODO read renewal.conf.default, write renewal.conf
|
|
|
|
// var pyconf = PromiseA.promisifyAll(require('pyconf'));
|
|
|
|
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
|
2015-12-16 09:11:31 +00:00
|
|
|
return mkdirpAsync(liveDir).then(function () {
|
2015-12-16 10:07:00 +00:00
|
|
|
return PromiseA.all([
|
2015-12-16 09:11:31 +00:00
|
|
|
sfs.writeFileAsync(certPath, result.cert, 'ascii')
|
2015-12-17 08:46:40 +00:00
|
|
|
, sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii')
|
2015-12-16 09:11:31 +00:00
|
|
|
, sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii')
|
2015-12-17 08:46:40 +00:00
|
|
|
, sfs.writeFileAsync(privkeyPath, result.key || result.privkey, 'ascii')
|
2015-12-16 12:57:53 +00:00
|
|
|
]).then(function () {
|
|
|
|
// TODO format result licesy
|
|
|
|
//console.log(liveDir);
|
|
|
|
//console.log(result);
|
|
|
|
return {
|
|
|
|
certPath: certPath
|
|
|
|
, chainPath: chainPath
|
|
|
|
, fullchainPath: fullchainPath
|
|
|
|
, privkeyPath: privkeyPath
|
2015-12-17 08:46:40 +00:00
|
|
|
|
|
|
|
// some ambiguity here...
|
|
|
|
, privkey: result.key || result.privkey
|
|
|
|
, fullchain: result.fullchain || result.cert
|
|
|
|
, chain: result.ca || result.chain
|
|
|
|
// especially this one... might be cert only, might be fullchain
|
|
|
|
, cert: result.cert
|
|
|
|
|
|
|
|
, issuedAt: Date.now()
|
|
|
|
, lifetime: defaults.lifetime || handlers.lifetime
|
2015-12-16 12:57:53 +00:00
|
|
|
};
|
|
|
|
});
|
2015-12-16 09:11:31 +00:00
|
|
|
});
|
2015-12-15 15:21:27 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2015-12-15 11:37:39 +00:00
|
|
|
|
2015-12-15 15:21:27 +00:00
|
|
|
function registerWithAcme(args, defaults, handlers) {
|
|
|
|
var pyconf = PromiseA.promisifyAll(require('pyconf'));
|
2015-12-17 08:46:40 +00:00
|
|
|
var server = args.server;
|
2015-12-15 15:21:27 +00:00
|
|
|
var acmeHostname = require('url').parse(server).hostname;
|
2015-12-17 08:46:40 +00:00
|
|
|
var configDir = args.configDir;
|
2015-12-15 15:21:27 +00:00
|
|
|
|
|
|
|
args.server = server;
|
|
|
|
args.renewalDir = args.renewalDir || path.join(configDir, 'renewal', args.domains[0] + '.conf');
|
|
|
|
args.accountsDir = args.accountsDir || path.join(configDir, 'accounts', acmeHostname, 'directory');
|
|
|
|
|
|
|
|
return pyconf.readFileAsync(args.renewalDir).then(function (renewal) {
|
|
|
|
var accountId = renewal.account;
|
|
|
|
renewal = renewal.account;
|
|
|
|
|
|
|
|
return accountId;
|
|
|
|
}, function (err) {
|
2015-12-16 10:07:00 +00:00
|
|
|
if ("ENOENT" === err.code) {
|
2015-12-17 05:44:41 +00:00
|
|
|
return Accounts.getAccountByEmail(args, handlers);
|
2015-12-15 15:21:27 +00:00
|
|
|
}
|
2015-12-15 11:37:39 +00:00
|
|
|
|
2015-12-15 15:21:27 +00:00
|
|
|
return PromiseA.reject(err);
|
|
|
|
}).then(function (accountId) {
|
|
|
|
// Note: the ACME urls are always fetched fresh on purpose
|
|
|
|
return getAcmeUrls(args).then(function (urls) {
|
|
|
|
args._acmeUrls = urls;
|
|
|
|
|
|
|
|
if (accountId) {
|
2015-12-17 05:44:41 +00:00
|
|
|
return Accounts.getAccount(accountId, args, handlers);
|
2015-12-15 15:21:27 +00:00
|
|
|
} else {
|
2015-12-17 05:44:41 +00:00
|
|
|
return Accounts.createAccount(args, handlers);
|
2015-12-15 15:21:27 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}).then(function (account) {
|
|
|
|
/*
|
|
|
|
if (renewal.account !== account) {
|
|
|
|
// the account has become corrupt, re-register
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2015-12-16 12:57:53 +00:00
|
|
|
//console.log(account);
|
2015-12-17 08:46:40 +00:00
|
|
|
return fetchFromConfigLiveDir(args).then(function (certs) {
|
2015-12-15 15:21:27 +00:00
|
|
|
// if nothing, register and save
|
|
|
|
// if something, check date (don't register unless 30+ days)
|
|
|
|
// if good, don't bother registering
|
|
|
|
// (but if we get to the point that we're actually calling
|
|
|
|
// this function, that shouldn't be the case, right?)
|
2015-12-16 12:57:53 +00:00
|
|
|
//console.log(certs);
|
2015-12-15 15:21:27 +00:00
|
|
|
if (!certs) {
|
|
|
|
// no certs, seems like a good time to get some
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers);
|
|
|
|
}
|
|
|
|
else if (certs.issuedAt > (27 * 24 * 60 * 60 * 1000)) {
|
|
|
|
// cert is at least 27 days old we can renew that
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers);
|
|
|
|
}
|
2015-12-16 12:57:53 +00:00
|
|
|
else if (args.duplicate) {
|
2015-12-15 15:21:27 +00:00
|
|
|
// YOLO! I be gettin' fresh certs 'erday! Yo!
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers);
|
|
|
|
}
|
|
|
|
else {
|
2015-12-16 12:57:53 +00:00
|
|
|
console.warn('[WARN] Ignoring renewal attempt for certificate less than 27 days old. Use args.duplicate to force.');
|
2015-12-15 15:21:27 +00:00
|
|
|
// We're happy with what we have
|
|
|
|
return certs;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
cert = /home/aj/node-letsencrypt/tests/letsencrypt.config/live/lds.io/cert.pem
|
|
|
|
privkey = /home/aj/node-letsencrypt/tests/letsencrypt.config/live/lds.io/privkey.pem
|
|
|
|
chain = /home/aj/node-letsencrypt/tests/letsencrypt.config/live/lds.io/chain.pem
|
|
|
|
fullchain = /home/aj/node-letsencrypt/tests/letsencrypt.config/live/lds.io/fullchain.pem
|
|
|
|
|
|
|
|
# Options and defaults used in the renewal process
|
|
|
|
[renewalparams]
|
|
|
|
apache_enmod = a2enmod
|
|
|
|
no_verify_ssl = False
|
|
|
|
ifaces = None
|
|
|
|
apache_dismod = a2dismod
|
|
|
|
register_unsafely_without_email = False
|
|
|
|
uir = None
|
|
|
|
installer = none
|
|
|
|
config_dir = /home/aj/node-letsencrypt/tests/letsencrypt.config
|
|
|
|
text_mode = True
|
|
|
|
func = <function obtain_cert at 0x7f46af0f02a8>
|
|
|
|
prepare = False
|
|
|
|
work_dir = /home/aj/node-letsencrypt/tests/letsencrypt.work
|
|
|
|
tos = True
|
|
|
|
init = False
|
|
|
|
http01_port = 80
|
|
|
|
duplicate = False
|
|
|
|
key_path = None
|
|
|
|
nginx = False
|
|
|
|
fullchain_path = /home/aj/node-letsencrypt/chain.pem
|
|
|
|
email = coolaj86@gmail.com
|
|
|
|
csr = None
|
|
|
|
agree_dev_preview = None
|
|
|
|
redirect = None
|
|
|
|
verbose_count = -3
|
|
|
|
config_file = None
|
|
|
|
renew_by_default = True
|
|
|
|
hsts = False
|
|
|
|
authenticator = webroot
|
|
|
|
domains = lds.io,
|
|
|
|
rsa_key_size = 2048
|
|
|
|
checkpoints = 1
|
|
|
|
manual_test_mode = False
|
|
|
|
apache = False
|
|
|
|
cert_path = /home/aj/node-letsencrypt/cert.pem
|
|
|
|
webroot_path = /home/aj/node-letsencrypt/examples/../tests/acme-challenge,
|
|
|
|
strict_permissions = False
|
|
|
|
apache_server_root = /etc/apache2
|
|
|
|
account = 1c41c64dfaf10d511db8aef0cc33b27f
|
|
|
|
manual_public_ip_logging_ok = False
|
|
|
|
chain_path = /home/aj/node-letsencrypt/chain.pem
|
|
|
|
standalone = False
|
|
|
|
manual = False
|
|
|
|
server = https://acme-staging.api.letsencrypt.org/directory
|
|
|
|
standalone_supported_challenges = "http-01,tls-sni-01"
|
|
|
|
webroot = True
|
|
|
|
apache_init_script = None
|
|
|
|
user_agent = None
|
|
|
|
apache_ctl = apache2ctl
|
|
|
|
apache_le_vhost_ext = -le-ssl.conf
|
|
|
|
debug = False
|
|
|
|
tls_sni_01_port = 443
|
|
|
|
logs_dir = /home/aj/node-letsencrypt/tests/letsencrypt.logs
|
|
|
|
configurator = None
|
|
|
|
[[webroot_map]]
|
|
|
|
lds.io = /home/aj/node-letsencrypt/examples/../tests/acme-challenge
|
|
|
|
*/
|
|
|
|
});
|
2015-12-15 11:37:39 +00:00
|
|
|
/*
|
2015-12-15 15:21:27 +00:00
|
|
|
return fs.readdirAsync(accountsDir, function (nodes) {
|
|
|
|
return PromiseA.all(nodes.map(function (node) {
|
|
|
|
var reMd5 = /[a-f0-9]{32}/i;
|
|
|
|
if (reMd5.test(node)) {
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
});
|
2015-12-15 11:37:39 +00:00
|
|
|
*/
|
2015-12-15 15:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.create = function (defaults, handlers) {
|
|
|
|
defaults.server = defaults.server || LE.liveServer;
|
|
|
|
|
|
|
|
var wrapped = {
|
|
|
|
registerAsync: function (args) {
|
2015-12-17 08:46:40 +00:00
|
|
|
var copy = merge(args, defaults);
|
|
|
|
tplHostname(args.domains[0], copy);
|
|
|
|
|
|
|
|
if (args.debug) {
|
|
|
|
console.log('[LE DEBUG] reg domains', args.domains);
|
|
|
|
}
|
|
|
|
return registerWithAcme(copy, defaults, handlers);
|
2015-12-15 11:37:39 +00:00
|
|
|
}
|
|
|
|
, fetchAsync: function (args) {
|
2015-12-17 08:46:40 +00:00
|
|
|
var copy = merge(args, defaults);
|
|
|
|
tplHostname(args.domains[0], copy);
|
|
|
|
|
|
|
|
if (args.debug) {
|
|
|
|
console.log('[LE DEBUG] fetch domains', copy);
|
|
|
|
}
|
|
|
|
return fetchFromConfigLiveDir(copy, defaults);
|
2015-12-15 11:37:39 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return wrapped;
|
|
|
|
};
|