diff --git a/lib/default-handlers.js b/lib/default-handlers.js deleted file mode 100644 index 9982539..0000000 --- a/lib/default-handlers.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); - -module.exports.agreeToTerms = function (args, agreeCb) { - agreeCb(null, args.agreeTos); -}; - -module.exports.setChallenge = function (args, domain, challengePath, keyAuthorization, done) { - //var hostname = args.domains[0]; - var mkdirp = require('mkdirp'); - - // TODO should be args.webrootPath - //console.log('args.webrootPath, challengePath'); - //console.log(args.webrootPath, challengePath); - mkdirp(args.webrootPath, function (err) { - if (err) { - done(err); - return; - } - - fs.writeFile(path.join(args.webrootPath, challengePath), keyAuthorization, 'utf8', function (err) { - done(err); - }); - }); -}; - -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, domain, key, done) { - //var hostname = args.domains[0]; - - fs.unlink(path.join(args.webrootPath, key), done); -}; diff --git a/lib/pycompat.js b/lib/pycompat.js deleted file mode 100644 index a19bfd6..0000000 --- a/lib/pycompat.js +++ /dev/null @@ -1,517 +0,0 @@ -'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); - }); - } - }; -}; diff --git a/lib/renewal.conf.tpl b/lib/renewal.conf.tpl deleted file mode 100644 index ad7ae0a..0000000 --- a/lib/renewal.conf.tpl +++ /dev/null @@ -1,68 +0,0 @@ -#cert = :config/live/:hostname/cert.pem -cert = :cert_path -privkey = :privkey_path -chain = :chain_path -fullchain = :fullchain_path - -# 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 = :config -text_mode = True -# junk? -# https://github.com/letsencrypt/letsencrypt/issues/1955 -func = -prepare = False -work_dir = :work_dir -tos = :agree_tos -init = False -http01_port = :http_01_port -duplicate = False -# this is for the domain -key_path = :privkey_path -nginx = False -fullchain_path = :fullchain_path -email = :email -csr = None -agree_dev_preview = None -redirect = None -verbose_count = -3 -config_file = None -renew_by_default = True -hsts = False -authenticator = webroot -domains = :hostnames #comma,delimited,list -rsa_key_size = :rsa_key_size -# starts at 0 and increments at every renewal -checkpoints = -1 -manual_test_mode = False -apache = False -cert_path = :cert_path -webroot_path = :webroot_paths # comma,delimited,list -strict_permissions = False -apache_server_root = /etc/apache2 -# https://github.com/letsencrypt/letsencrypt/issues/1948 -account = :account_id -manual_public_ip_logging_ok = False -chain_path = :chain_path -standalone = False -manual = False -server = :acme_discovery_url -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 = :logs_dir -configurator = None -[[webroot_map]] -# :hostname = :webroot_path diff --git a/tests/pyconf-write.js b/tests/pyconf-write.js deleted file mode 100644 index 98bc3a6..0000000 --- a/tests/pyconf-write.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -var PromiseA = require('bluebird'); -var pyconf = PromiseA.promisifyAll(require('pyconf')); -var mkdirpAsync = PromiseA.promisify(require('mkdirp')); -var path = require('path'); - -pyconf.readFileAsync(path.join(__dirname, 'lib', 'renewal.conf.tpl')).then(function (obj) { - var domains = ['example.com', 'www.example.com']; - var webrootPath = '/tmp/www/example.com'; - - console.log(obj); - - var keys = obj.__keys; - var lines = obj.__lines; - - obj.__keys = null; - obj.__lines = null; - - var updates = { - account: 'ACCOUNT_ID' - - , cert: 'CERT_PATH' - , privkey: 'PRIVATEKEY_PATH' - , configDir: 'CONFIG_DIR' - , tos: true - , http01Port: 80 - , domains: domains - }; - - // final section is completely dynamic - // :hostname = :webroot_path - domains.forEach(function (hostname) { - updates[hostname] = webrootPath; - }); - - // must write back to the original object or - // annotations will be lost - Object.keys(updates).forEach(function (key) { - obj[key] = updates[key]; - }); - - var renewalPath = '/tmp/letsencrypt/renewal/example.com.conf'; - return mkdirpAsync(path.dirname(renewalPath)).then(function () { - console.log(obj); - obj.__keys = keys; - obj.__lines = lines; - return pyconf.writeFileAsync(renewalPath, obj); - }); -});