From 77f54981c5a2f70c678f1c586fea060dacec108a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 19 Dec 2015 11:49:34 -0800 Subject: [PATCH] close #9 write rewenal config files --- lib/accounts.js | 22 ++++-- lib/core.js | 188 +++++++++++++++++++++++++----------------------- 2 files changed, 114 insertions(+), 96 deletions(-) diff --git a/lib/accounts.js b/lib/accounts.js index b06853e..7942a34 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -14,7 +14,7 @@ function createAccount(args, handlers) { // TODO support ECDSA // arg.rsaBitLength args.rsaExponent return leCrypto.generateRsaKeypairAsync(args.rsaBitLength, args.rsaExponent).then(function (pems) { - /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem, publicKeyMd5 } */ + /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem, publicKeyMd5, publicKeySha256 } */ return LeCore.registerNewAccountAsync({ email: args.email @@ -28,7 +28,13 @@ function createAccount(args, handlers) { , debug: args.debug || handlers.debug }).then(function (body) { - var accountDir = path.join(args.accountsDir, pems.publicKeyMd5); + // TODO XXX use sha256 + var accountId = pems.publicKeyMd5; + var accountDir = path.join(args.accountsDir, accountId); + var regr = { body: body }; + + args.accountId = accountId; + args.accountDir = accountDir; return mkdirpAsync(accountDir).then(function () { @@ -52,8 +58,12 @@ function createAccount(args, handlers) { 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({ body: body }), 'utf8') + , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(regr), 'utf8') ]).then(function () { + pems.meta = accountMeta; + pems.privateKey = pems.privateKeyJwk; + pems.regr = regr; + pems.accountId = accountId; return pems; }); }); @@ -94,11 +104,13 @@ function getAccount(accountId, args, handlers) { } return leCrypto.parseAccountPrivateKeyAsync(files.private_key).then(function (keypair) { - files.accountId = accountId; // md5sum(publicKeyPem) - files.publicKeyMd5 = accountId; // md5sum(publicKeyPem) + files.accountId = accountId; // preserve current account id + files.publicKeySha256 = keypair.publicKeySha256; + files.publicKeyMd5 = keypair.publicKeyMd5; files.publicKeyPem = keypair.publicKeyPem; // ascii PEM: ----BEGIN... files.privateKeyPem = keypair.privateKeyPem; // ascii PEM: ----BEGIN... files.privateKeyJson = keypair.private_key; // json { n: ..., e: ..., iq: ..., etc } + files.privateKeyJwk = keypair.private_key; // json { n: ..., e: ..., iq: ..., etc } return files; }); diff --git a/lib/core.js b/lib/core.js index 1a32d69..3e7ae04 100644 --- a/lib/core.js +++ b/lib/core.js @@ -34,13 +34,13 @@ function getAcmeUrls(args) { function getCertificateAsync(account, args, defaults, handlers) { - return leCrypto.generateRsaKeypairAsync(args.rsaBitLength, args.rsaExponent).then(function (domain) { + return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, args.rsaExponent).then(function (domainKey) { return LeCore.getCertificateAsync({ newAuthzUrl: args._acmeUrls.newAuthz , newCertUrl: args._acmeUrls.newCert , accountPrivateKeyPem: account.privateKeyPem - , domainPrivateKeyPem: domain.privateKeyPem + , domainPrivateKeyPem: domainKey.privateKeyPem , domains: args.domains // @@ -82,31 +82,97 @@ function getCertificateAsync(account, args, defaults, handlers) { } } }).then(function (result) { - 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 + var pyconf = PromiseA.promisifyAll(require('pyconf')); - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // TODO read renewal.conf.default, write renewal.conf - // var pyconf = PromiseA.promisifyAll(require('pyconf')); - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + return pyconf.readFileAsync(args.renewalPath, function (obj) { + return obj; + }, function () { + return pyconf.readFileAsync(path.join(__dirname, 'lib', 'renewal.conf.tpl')).then(function (obj) { + return obj; + }); + }).then(function (obj) { + obj.checkpoint = parseInt(obj.checkpoint, 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.domainPrivateKeyPath || args.domainKeyPath + || obj.privkey || obj.keyPath + || path.join(liveDir, 'privkey.pem'); + + var archiveDir = args.archiveDir || path.join(args.configDir, 'live', args.domains[0]); + + var checkpoint = obj.checkpoint.toString(); + var certArchive = path.join(archiveDir, 'cert' + checkpoint + '.pem'); + var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoint + '.pem'); + var chainArchive = path.join(archiveDir, 'chain'+ checkpoint + '.pem'); + var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoint + '.pem'); + + return mkdirpAsync(archiveDir).then(function () { + return PromiseA.all([ + sfs.writeFileAsync(certArchive, result.cert, 'ascii') + , sfs.writeFileAsync(chainArchive, result.ca || result.chain, 'ascii') + , sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii') + , sfs.writeFileAsync(privkeyArchive, result.key || result.privkey, 'ascii') + ]); + }).then(function () { + return mkdirpAsync(liveDir); + }).then(function () { + return PromiseA.all([ + sfs.writeFileAsync(certPath, result.cert, 'ascii') + , sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') + , sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') + , sfs.writeFileAsync(privkeyPath, result.key || result.privkey, 'ascii') + ]); + }).then(function () { + obj.checkpoint += 1; + + var updates = { + cert: certPath + , privkey: privkeyPath + , chain: chainPath + , fullchain: fullchainPath + , configDir: args.configDir + , workDir: args.workDir + , tos: args.agreeTos && true + , http01Port: args.http01Port + , keyPath: args.domainPrivateKeyPath || args.privkeyPath + , email: args.email + , domains: args.domains + , rsaKeySize: args.rsaKeySize + , checkpoints: obj.checkpoint + // 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 + // yes, it's an array. weird, right? + , webrootPath: args.webrootPath && [args.webrootPath] || [] + , account: account.accountId + , server: args.server || args.acmeDiscoveryUrl + , 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 object or + // annotations will be lost + Object.keys(updates).forEach(function (key) { + obj[key] = updates[key]; + }); + + return pyconf.writeFile(args.renewalPath, obj); + }).then(function () { - return mkdirpAsync(liveDir).then(function () { - return PromiseA.all([ - sfs.writeFileAsync(certPath, result.cert, 'ascii') - , sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') - , sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') - , sfs.writeFileAsync(privkeyPath, result.key || result.privkey, 'ascii') - ]).then(function () { - // TODO format result licesy - //console.log(liveDir); - //console.log(result); return { certPath: certPath , chainPath: chainPath @@ -135,8 +201,7 @@ function registerWithAcme(args, defaults, handlers) { var acmeHostname = require('url').parse(server).hostname; var configDir = args.configDir; - args.server = server; - args.renewalDir = args.renewalDir || path.join(configDir, 'renewal', args.domains[0] + '.conf'); + args.renewalPath = args.renewalPath || 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) { @@ -195,71 +260,6 @@ function registerWithAcme(args, defaults, handlers) { 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 = - 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 - */ }); /* return fs.readdirAsync(accountsDir, function (nodes) { @@ -277,7 +277,13 @@ module.exports.create = function (defaults, handlers) { var wrapped = { registerAsync: function (args) { - var copy = merge(args, defaults); + var copy; + // TODO move these defaults elsewhere? + 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); tplHostname(args.domains[0], copy); if (args.debug) {