615 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			615 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| /*global Promise*/
 | |
| require('./compat.js');
 | |
| 
 | |
| var util = require('util');
 | |
| function promisifyAll(obj) {
 | |
|   var aobj = {};
 | |
|   Object.keys(obj).forEach(function (key) {
 | |
|     if ('function' === typeof obj[key]) {
 | |
|       aobj[key] = obj[key];
 | |
|       aobj[key + 'Async'] = util.promisify(obj[key]);
 | |
|     }
 | |
|   });
 | |
|   return aobj;
 | |
| }
 | |
| 
 | |
| function _log(debug) {
 | |
|   if (debug) {
 | |
|     var args = Array.prototype.slice.call(arguments);
 | |
|     args.shift();
 | |
|     args.unshift("[greenlock/lib/core.js]");
 | |
|     console.log.apply(console, args);
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports.create = function (gl) {
 | |
|   var utils = require('./utils');
 | |
|   var RSA = promisifyAll(require('rsa-compat').RSA);
 | |
|   var log = gl.log || _log; // allow custom log
 | |
|   var pendingRegistrations = {};
 | |
| 
 | |
|   var core = {
 | |
|     //
 | |
|     // Helpers
 | |
|     //
 | |
|     getAcmeUrlsAsync: function (args) {
 | |
|       var now = Date.now();
 | |
| 
 | |
|       // TODO check response header on request for cache time
 | |
|       if ((now - gl._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
 | |
|         return Promise.resolve(gl._ipc.acmeUrls);
 | |
|       }
 | |
| 
 | |
|       // TODO acme-v2/nocompat
 | |
|       return gl.acme.getAcmeUrlsAsync(args.server).then(function (data) {
 | |
|         gl._ipc.acmeUrlsUpdatedAt = Date.now();
 | |
|         gl._ipc.acmeUrls = data;
 | |
| 
 | |
|         return gl._ipc.acmeUrls;
 | |
|       });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //
 | |
|     // The Main Enchilada
 | |
|     //
 | |
| 
 | |
|     //
 | |
|     // Accounts
 | |
|     //
 | |
|   , accounts: {
 | |
|       // Accounts
 | |
|       registerAsync: function (args) {
 | |
|         var err;
 | |
|         var copy = utils.merge(args, gl);
 | |
|         var disagreeTos;
 | |
|         args = utils.tplCopy(copy);
 | |
|         if (!args.account) { args.account = {}; }
 | |
|         if ('object' === typeof args.account && !args.account.id) { args.account.id = args.accountId || args.email || ''; }
 | |
| 
 | |
|         disagreeTos = (!args.agreeTos && 'undefined' !== typeof args.agreeTos);
 | |
|         if (!args.email || disagreeTos || (parseInt(args.rsaKeySize, 10) < 2048)) {
 | |
|           err = new Error(
 | |
|             "In order to register an account both 'email' and 'agreeTos' must be present"
 | |
|               + " and 'rsaKeySize' must be 2048 or greater."
 | |
|           );
 | |
|           err.code = 'E_ARGS';
 | |
|           return Promise.reject(err);
 | |
|         }
 | |
| 
 | |
|         return utils.testEmail(args.email).then(function () {
 | |
|           if (args.account && args.account.privkey && (args.account.privkey.jwk || args.account.privkey.pem)) {
 | |
|             // TODO import jwk or pem and return it here
 | |
|             console.warn("TODO: implement accounts.checkKeypairAsync skipping");
 | |
|           }
 | |
|           var accountKeypair;
 | |
|           var newAccountKeypair = true;
 | |
|           var promise = gl.store.accounts.checkKeypairAsync(args).then(function (keypair) {
 | |
|             if (keypair) {
 | |
|               // TODO keypairs
 | |
|               newAccountKeypair = false;
 | |
|               accountKeypair = RSA.import(keypair);
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             if (args.accountKeypair) {
 | |
|               // TODO keypairs
 | |
|               accountKeypair = RSA.import(args.accountKeypair);
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             var keypairOpts = { bitlen: args.rsaKeySize, exp: 65537, public: true, pem: true };
 | |
|             // TODO keypairs
 | |
|             return (args.generateKeypair||RSA.generateKeypairAsync)(keypairOpts).then(function (keypair) {
 | |
|               keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
 | |
|               keypair.publicKeyPem = RSA.exportPublicPem(keypair);
 | |
|               keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
 | |
|               accountKeypair = keypair;
 | |
|             });
 | |
|           }).then(function () {
 | |
|             return accountKeypair;
 | |
|          });
 | |
| 
 | |
|           return promise.then(function (keypair) {
 | |
|             // Note: the ACME urls are always fetched fresh on purpose
 | |
|             // TODO acme-v2/nocompat
 | |
|             return core.getAcmeUrlsAsync(args).then(function (urls) {
 | |
|               args._acmeUrls = urls;
 | |
| 
 | |
|               // TODO acme-v2/nocompat
 | |
|               return gl.acme.registerNewAccountAsync({
 | |
|                 email: args.email
 | |
|               , newRegUrl: args._acmeUrls.newReg
 | |
|               , newAuthzUrl: args._acmeUrls.newAuthz
 | |
|               , agreeToTerms: function (tosUrl, agreeCb) {
 | |
|                   if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === gl.agreeToTerms) {
 | |
|                     agreeCb(null, tosUrl);
 | |
|                     return;
 | |
|                   }
 | |
| 
 | |
|                   // args.email = email;      // already there
 | |
|                   // args.domains = domains   // already there
 | |
|                   args.tosUrl = tosUrl;
 | |
|                   gl.agreeToTerms(args, agreeCb);
 | |
|                 }
 | |
|               , accountKeypair: keypair
 | |
| 
 | |
|               , debug: gl.debug || args.debug
 | |
|               }).then(function (receipt) {
 | |
|                 var reg = {
 | |
|                   keypair: keypair
 | |
|                 , receipt: receipt
 | |
|                 , kid: receipt && receipt.key && (receipt.key.kid || receipt.kid)
 | |
|                 , email: args.email
 | |
|                 , newRegUrl: args._acmeUrls.newReg
 | |
|                 , newAuthzUrl: args._acmeUrls.newAuthz
 | |
|                 };
 | |
| 
 | |
|                 var accountKeypairPromise;
 | |
|                 args.keypair = keypair;
 | |
|                 args.receipt = receipt;
 | |
|                 if (newAccountKeypair) {
 | |
|                   accountKeypairPromise = gl.store.accounts.setKeypairAsync(args, keypair);
 | |
|                 }
 | |
|                 return Promise.resolve(accountKeypairPromise).then(function () {
 | |
|                 // TODO move templating of arguments to right here?
 | |
|                 if (!gl.store.accounts.setAsync) { return Promise.resolve({ keypair: keypair }); }
 | |
|                   return gl.store.accounts.setAsync(args, reg).then(function (account) {
 | |
|                    if (account && 'object' !== typeof account) {
 | |
|                       throw new Error("store.accounts.setAsync should either return 'null' or an object with at least a string 'id'");
 | |
|                    }
 | |
|                     if (!account) { account = {}; }
 | |
|                     account.keypair = keypair;
 | |
|                     return account;
 | |
|                   });
 | |
|                });
 | |
|               });
 | |
|             });
 | |
|           });
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       // Accounts
 | |
|       // (only used for keypair)
 | |
|     , getAsync: function (args) {
 | |
|         var accountPromise = null;
 | |
|         if (gl.store.accounts.checkAsync) {
 | |
|           accountPromise = core.accounts.checkAsync(args);
 | |
|         }
 | |
|         return Promise.resolve(accountPromise).then(function (account) {
 | |
|           if (!account) { return core.accounts.registerAsync(args); }
 | |
|           if (account.keypair) { return account; }
 | |
| 
 | |
|           if (!args.account) { args.account = {}; }
 | |
|           if ('object' === typeof args.account && !args.account.id) { args.account.id = args.accountId || args.email || ''; }
 | |
|           var copy = utils.merge(args, gl);
 | |
|           args = utils.tplCopy(copy);
 | |
|           return gl.store.accounts.checkKeypairAsync(args).then(function (keypair) {
 | |
|             if (keypair) { return { keypair: keypair }; }
 | |
|             return core.accounts.registerAsync(args);
 | |
|           });
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       // Accounts
 | |
|     , checkAsync: function (args) {
 | |
|         var requiredArgs = ['accountId', 'email', 'domains', 'domain'];
 | |
|         if (!(args.account && (args.account.id || args.account.kid))
 | |
|           && !requiredArgs.some(function (key) { return -1 !== Object.keys(args).indexOf(key); })) {
 | |
|           return Promise.reject(new Error(
 | |
|             "In order to register or retrieve an account one of '" + requiredArgs.join("', '") + "' must be present"
 | |
|           ));
 | |
|         }
 | |
| 
 | |
|         var copy = utils.merge(args, gl);
 | |
|         args = utils.tplCopy(copy);
 | |
|         if (!args.account) { args.account = {}; }
 | |
|         if ('object' === typeof args.account && !args.account.id) { args.account.id = args.accountId || args.email || ''; }
 | |
| 
 | |
|         // we can re-register the same account until we're blue in the face and it's all the same
 | |
|         // of course, we can also skip the lookup if we do store the account, but whatever
 | |
|         if (!gl.store.accounts.checkAsync) { return Promise.resolve(null); }
 | |
|         return gl.store.accounts.checkAsync(args).then(function (account) {
 | |
| 
 | |
|           if (!account) {
 | |
|             return null;
 | |
|           }
 | |
| 
 | |
|           args.account = account;
 | |
|           args.accountId = account.id;
 | |
| 
 | |
|           return account;
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   , certificates: {
 | |
|       // Certificates
 | |
|       registerAsync: function (args) {
 | |
|         var err;
 | |
|         var challengeDefaults = gl['_challengeOpts_' + (args.challengeType || gl.challengeType)] || {};
 | |
|         var copy = utils.merge(args, challengeDefaults || {});
 | |
|         copy = utils.merge(copy, gl);
 | |
|         if (!copy.subject) { copy.subject = copy.domains[0]; }
 | |
|         if (!copy.domain) { copy.domain = copy.domains[0]; }
 | |
|         args = utils.tplCopy(copy);
 | |
| 
 | |
|         if (!Array.isArray(args.domains)) {
 | |
|           return Promise.reject(new Error('args.domains should be an array of domains'));
 | |
|         }
 | |
|         //if (-1 === args.domains.indexOf(args.subject)) // TODO relax the constraint once acme-v2 handles subject?
 | |
|         if (args.subject !== args.domains[0]) {
 | |
|           console.warn("The certificate's subject (primary domain) should be first in the list of opts.domains");
 | |
|           console.warn('\topts.subject: (set by you approveDomains(), falling back to opts.domain) ' + args.subject);
 | |
|           console.warn('\topts.domain: (set by SNICallback()) ' + args.domain);
 | |
|           console.warn('\topts.domains: (set by you in approveDomains()) ' + args.domains.join(','));
 | |
|           console.warn("Updating your code will prevent weird, random, hard-to-repro bugs during renewals");
 | |
|           console.warn("(also this will be required in the next major version of greenlock)");
 | |
|           //return Promise.reject(new Error('certificate subject (primary domain) must be the first in opts.domains'));
 | |
|         }
 | |
|         if (!(args.domains.length && args.domains.every(utils.isValidDomain))) {
 | |
|           // NOTE: this library can't assume to handle the http loopback
 | |
|           // (or dns-01 validation may be used)
 | |
|           // so we do not check dns records or attempt a loopback here
 | |
|           err = new Error("invalid domain name(s): '(" + args.subject + ') ' + args.domains.join(',') + "'");
 | |
|           err.code = "INVALID_DOMAIN";
 | |
|           return Promise.reject(err);
 | |
|         }
 | |
| 
 | |
|         // If a previous request to (re)register a certificate is already underway we need
 | |
|         // to return the same promise created before rather than registering things twice.
 | |
|         // I'm not 100% sure how to properly handle the case where someone registers domain
 | |
|         // lists with some but not all elements common, nor am I sure that's even a case that
 | |
|         // is allowed to happen anyway. But for now we act like the list is completely the
 | |
|         // same if any elements are the same.
 | |
|         var promise;
 | |
|         args.domains.some(function (name) {
 | |
|           if (pendingRegistrations.hasOwnProperty(name)) {
 | |
|             promise = pendingRegistrations[name];
 | |
|             return true;
 | |
|           }
 | |
|         });
 | |
|         if (promise) {
 | |
|           return promise;
 | |
|         }
 | |
| 
 | |
|         promise = core.certificates._runRegistration(args);
 | |
| 
 | |
|         // Now that the registration is actually underway we need to make sure any subsequent
 | |
|         // registration attempts return the same promise until it is completed (but not after
 | |
|         // it is completed).
 | |
|         args.domains.forEach(function (name) {
 | |
|           pendingRegistrations[name] = promise;
 | |
|         });
 | |
|         function clearPending() {
 | |
|           args.domains.forEach(function (name) {
 | |
|             delete pendingRegistrations[name];
 | |
|           });
 | |
|         }
 | |
|         promise.then(clearPending, clearPending);
 | |
| 
 | |
|         return promise;
 | |
|       }
 | |
|     , _runRegistration: function (args) {
 | |
|         // TODO renewal cb
 | |
|         // accountId and or email
 | |
|         return core.accounts.getAsync(args).then(function (account) {
 | |
|           args.account = account;
 | |
| 
 | |
| 
 | |
|           if (args.certificate && args.certificate.privkey && (args.certificate.privkey.jwk || args.certificate.privkey.pem)) {
 | |
|             // TODO import jwk or pem and return it here
 | |
|             console.warn("TODO: implement certificates.checkKeypairAsync skipping");
 | |
|           }
 | |
|           var domainKeypair;
 | |
|           var newDomainKeypair = true;
 | |
|           // This has been done in the getAsync already, so we skip it here
 | |
|           // if approveDomains doesn't set subject, we set it here
 | |
|           //args.subject = args.subject || args.domains[0];
 | |
|           var promise = gl.store.certificates.checkKeypairAsync(args).then(function (keypair) {
 | |
|             if (keypair) {
 | |
|               domainKeypair = RSA.import(keypair);
 | |
|               newDomainKeypair = false;
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             if (args.domainKeypair) {
 | |
|               domainKeypair = RSA.import(args.domainKeypair);
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             var keypairOpts = { bitlen: args.rsaKeySize, exp: 65537, public: true, pem: true };
 | |
|             return (args.generateKeypair||RSA.generateKeypairAsync)(keypairOpts).then(function (keypair) {
 | |
|               keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
 | |
|               keypair.publicKeyPem = RSA.exportPublicPem(keypair);
 | |
|               keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
 | |
|               domainKeypair = keypair;
 | |
|             });
 | |
|           }).then(function () {
 | |
|             return domainKeypair;
 | |
|           });
 | |
| 
 | |
|           return promise.then(function (domainKeypair) {
 | |
|             args.domainKeypair = domainKeypair;
 | |
|             //args.registration = domainKey;
 | |
| 
 | |
|             // Note: the ACME urls are always fetched fresh on purpose
 | |
|             // TODO is this the right place for this?
 | |
|             return core.getAcmeUrlsAsync(args).then(function (urls) {
 | |
|               args._acmeUrls = urls;
 | |
| 
 | |
|               var certReq = {
 | |
|                 debug: args.debug || gl.debug
 | |
| 
 | |
|               , newAuthzUrl: args._acmeUrls.newAuthz
 | |
|               , newCertUrl: args._acmeUrls.newCert
 | |
| 
 | |
|               , accountKeypair: RSA.import(account.keypair)
 | |
|               , domainKeypair: domainKeypair
 | |
|               , subject: args.subject // TODO handle this in acme-v2
 | |
|               , domains: args.domains
 | |
|               , challengeTypes: Object.keys(args.challenges)
 | |
|               };
 | |
| 
 | |
|               //
 | |
|               // 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)
 | |
|               //
 | |
|               // Each of these fires individually for each domain,
 | |
|               // even though the certificate on the whole may have many domains
 | |
|               //
 | |
|               certReq.setChallenge = function (challenge, done) {
 | |
|                 log(args.debug, "setChallenge called for '" + challenge.altname + "'");
 | |
|                 // NOTE: First arg takes precedence
 | |
|                 var copy = utils.merge({ domains: [challenge.altname] }, args);
 | |
|                 copy = utils.merge(copy, gl);
 | |
|                 utils.tplCopy(copy);
 | |
|                 copy.challenge = challenge;
 | |
| 
 | |
|                 if (1 === copy.challenges[challenge.type].set.length) {
 | |
|                   copy.challenges[challenge.type].set(copy).then(function (result) {
 | |
|                     done(null, result);
 | |
|                   }).catch(done);
 | |
|                 } else if (2 === copy.challenges[challenge.type].set.length) {
 | |
|                   copy.challenges[challenge.type].set(copy, done);
 | |
|                 } else {
 | |
|                   Object.keys(challenge).forEach(function (key) {
 | |
|                     done[key] = challenge[key];
 | |
|                   });
 | |
|                   // regression bugfix for le-challenge-cloudflare
 | |
|                   // (_acme-challege => _greenlock-dryrun-XXXX)
 | |
|                   copy.acmePrefix = (challenge.dnsHost||'').replace(/\.*/, '') || copy.acmePrefix;
 | |
|                   copy.challenges[challenge.type].set(copy, challenge.altname, challenge.token, challenge.keyAuthorization, done);
 | |
|                 }
 | |
|               };
 | |
|               certReq.removeChallenge = function (challenge, done) {
 | |
|                 log(args.debug, "removeChallenge called for '" + challenge.altname + "'");
 | |
|                 var copy = utils.merge({ domains: [challenge.altname] }, args);
 | |
|                 copy = utils.merge(copy, gl);
 | |
|                 utils.tplCopy(copy);
 | |
|                 copy.challenge = challenge;
 | |
| 
 | |
|                 if (1 === copy.challenges[challenge.type].remove.length) {
 | |
|                   copy.challenges[challenge.type].remove(copy).then(function (result) {
 | |
|                     done(null, result);
 | |
|                   }).catch(done);
 | |
|                 } else if (2 === copy.challenges[challenge.type].remove.length) {
 | |
|                   copy.challenges[challenge.type].remove(copy, done);
 | |
|                 } else {
 | |
|                   Object.keys(challenge).forEach(function (key) {
 | |
|                     done[key] = challenge[key];
 | |
|                   });
 | |
|                   copy.challenges[challenge.type].remove(copy, challenge.altname, challenge.token, done);
 | |
|                 }
 | |
|               };
 | |
|               certReq.init = function (deps) {
 | |
|                 var copy = utils.merge(deps, args);
 | |
|                 copy = utils.merge(copy, gl);
 | |
|                 utils.tplCopy(copy);
 | |
| 
 | |
|                 Object.keys(copy.challenges).forEach(function (key) {
 | |
|                     if ('function' === typeof copy.challenges[key].init) {
 | |
|                         copy.challenges[key].init(copy);
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 return null;
 | |
|               };
 | |
|               certReq.getZones = function (challenge) {
 | |
|                 var copy = utils.merge({ dnsHosts: args.domains.map(function (x) { return 'xxxx.' + x; }) }, args);
 | |
|                 copy = utils.merge(copy, gl);
 | |
|                 utils.tplCopy(copy);
 | |
|                 copy.challenge = challenge;
 | |
| 
 | |
|                 if (!copy.challenges[challenge.type] || ('function' !== typeof copy.challenges[challenge.type].zones)) {
 | |
|                   // may not be available, that's fine.
 | |
|                   return Promise.resolve([]);
 | |
|                 }
 | |
| 
 | |
|                 return copy.challenges[challenge.type].zones(copy);
 | |
|               };
 | |
| 
 | |
|               log(args.debug, 'calling greenlock.acme.getCertificateAsync', certReq.subject, certReq.domains);
 | |
| 
 | |
|               // TODO acme-v2/nocompat
 | |
|               return gl.acme.getCertificateAsync(certReq).then(utils.attachCertInfo);
 | |
|             });
 | |
|           }).then(function (results) {
 | |
|             //var requested = {};
 | |
|             //var issued = {};
 | |
|             // { cert, chain, privkey /*TODO, subject, altnames, issuedAt, expiresAt */ }
 | |
| 
 | |
|             // args.certs.privkey = RSA.exportPrivatePem(options.domainKeypair);
 | |
|             args.certs = results;
 | |
|             // args.pems is deprecated
 | |
|             args.pems = results;
 | |
|             // This has been done in the getAsync already, so we skip it here
 | |
|             // if approveDomains doesn't set subject, we set it here
 | |
|             //args.subject = args.subject || args.domains[0];
 | |
|             var promise;
 | |
|             if (newDomainKeypair) {
 | |
|               args.keypair = domainKeypair;
 | |
|               promise = gl.store.certificates.setKeypairAsync(args, domainKeypair);
 | |
|             }
 | |
|             return Promise.resolve(promise).then(function () {
 | |
|               return gl.store.certificates.setAsync(args).then(function () {
 | |
|                 return results;
 | |
|               });
 | |
|             });
 | |
|           });
 | |
|         });
 | |
|       }
 | |
|       // Certificates
 | |
|     , renewAsync: function (args, certs) {
 | |
|         var renewableAt = core.certificates._getRenewableAt(args, certs);
 | |
|         var err;
 | |
|         //var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
 | |
|         //var renewable = (Date.now() - certs.issuedAt) > halfLife;
 | |
| 
 | |
|         log(args.debug, "(Renew) Expires At", new Date(certs.expiresAt).toISOString());
 | |
|         log(args.debug, "(Renew) Renewable At", new Date(renewableAt).toISOString());
 | |
| 
 | |
|         if (!args.duplicate && Date.now() < renewableAt) {
 | |
|           err = new Error(
 | |
|               "[ERROR] Certificate issued at '"
 | |
|             + new Date(certs.issuedAt).toISOString() + "' and expires at '"
 | |
|             + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until '"
 | |
|             + new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force."
 | |
|           );
 | |
|           err.code = 'E_NOT_RENEWABLE';
 | |
|           return Promise.reject(err);
 | |
|         }
 | |
| 
 | |
|         // Either the cert has entered its renewal period
 | |
|         // or we're forcing a refresh via 'dupliate: true'
 | |
|         log(args.debug, "Renewing!");
 | |
| 
 | |
|         if (!args.domains || !args.domains.length) {
 | |
|           args.domains = args.servernames || [certs.subject].concat(certs.altnames);
 | |
|         }
 | |
| 
 | |
|         return core.certificates.registerAsync(args);
 | |
|       }
 | |
|       // Certificates
 | |
|     , _isRenewable: function (args, certs) {
 | |
|         var renewableAt = core.certificates._getRenewableAt(args, certs);
 | |
| 
 | |
|         log(args.debug, "Check Expires At", new Date(certs.expiresAt).toISOString());
 | |
|         log(args.debug, "Check Renewable At", new Date(renewableAt).toISOString());
 | |
| 
 | |
|         if (args.duplicate || Date.now() >= renewableAt) {
 | |
|           log(args.debug, "certificates are renewable");
 | |
|           return true;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|       }
 | |
|     , _getRenewableAt: function (args, certs) {
 | |
|         return certs.expiresAt - (args.renewWithin || gl.renewWithin);
 | |
|       }
 | |
|     , checkAsync: function (args) {
 | |
|         var copy = utils.merge(args, gl);
 | |
|         // if approveDomains doesn't set subject, we set it here
 | |
|         if (!(copy.domains && copy.domains.length)) { copy.domains = [ copy.subject || copy.domain ].filter(Boolean); }
 | |
|         if (!copy.subject) { copy.subject = copy.domains[0]; }
 | |
|         if (!copy.domain) { copy.domain = copy.domains[0]; }
 | |
|         args = utils.tplCopy(copy);
 | |
| 
 | |
|         // returns pems
 | |
|         return gl.store.certificates.checkAsync(args).then(function (cert) {
 | |
|           if (!cert) { log(args.debug, 'checkAsync failed to find certificates'); return null; }
 | |
| 
 | |
|           cert = utils.attachCertInfo(cert);
 | |
|           if (utils.certHasDomain(cert, args.domain)) {
 | |
|             log(args.debug, 'checkAsync found existing certificates');
 | |
| 
 | |
|             if (cert.privkey) {
 | |
|               return cert;
 | |
|             } else {
 | |
|               return gl.store.certificates.checkKeypairAsync(args).then(function (keypair) {
 | |
|                 cert.privkey = keypair.privateKeyPem || RSA.exportPrivatePem(keypair);
 | |
|                 return cert;
 | |
|               });
 | |
|             }
 | |
|           }
 | |
|           log(args.debug, 'checkAsync found mismatched / incomplete certificates');
 | |
|         });
 | |
|       }
 | |
|       // Certificates
 | |
|     , getAsync: function (args) {
 | |
|         var copy = utils.merge(args, gl);
 | |
|         // if approveDomains doesn't set subject, we set it here
 | |
|         if (!(copy.domains && copy.domains.length)) { copy.domains = [ copy.subject || copy.domain ].filter(Boolean); }
 | |
|         if (!copy.subject) { copy.subject = copy.domains[0]; }
 | |
|         if (!copy.domain) { copy.domain = copy.domains[0]; }
 | |
|         args = utils.tplCopy(copy);
 | |
| 
 | |
|         if (args.certificate && args.certificate.privkey && args.certificate.cert && args.certificate.chain) {
 | |
|           // TODO skip fetching a certificate if it's fetched during approveDomains
 | |
|           console.warn("TODO: implement certificates.checkAsync skipping");
 | |
|         }
 | |
|         return core.certificates.checkAsync(args).then(function (certs) {
 | |
|           if (certs) { certs = utils.attachCertInfo(certs); }
 | |
|           if (!certs || !utils.certHasDomain(certs, args.domain)) {
 | |
|             // There is no cert available
 | |
|             if (false !== args.securityUpdates && !args._communityMemberAdded) {
 | |
|               // We will notify all greenlock users of mandatory and security updates
 | |
|               // We'll keep track of versions and os so we can make sure things work well
 | |
|               // { name, version, email, domains, action, communityMember, telemetry }
 | |
|               require('./community').add({
 | |
|                 name: args._communityPackage
 | |
|               , version: args._communityPackageVersion
 | |
|               , email: args.email
 | |
|               , domains: args.domains || args.servernames
 | |
|               , action: 'reg'
 | |
|               , communityMember: args.communityMember
 | |
|               , telemetry: args.telemetry
 | |
|               });
 | |
|               args._communityMemberAdded = true;
 | |
|             }
 | |
|             return core.certificates.registerAsync(args);
 | |
|           }
 | |
| 
 | |
|           if (core.certificates._isRenewable(args, certs)) {
 | |
|             // it's time to renew the available cert
 | |
|             if (false !== args.securityUpdates && !args._communityMemberAdded) {
 | |
|               // We will notify all greenlock users of mandatory and security updates
 | |
|               // We'll keep track of versions and os so we can make sure things work well
 | |
|               // { name, version, email, domains, action, communityMember, telemetry }
 | |
|               require('./community').add({
 | |
|                 name: args._communityPackage
 | |
|               , version: args._communityPackageVersion
 | |
|               , email: args.email
 | |
|               , domains: args.domains || args.servernames
 | |
|               , action: 'renew'
 | |
|               , communityMember: args.communityMember
 | |
|               , telemetry: args.telemetry
 | |
|               });
 | |
|               args._communityMemberAdded = true;
 | |
|             }
 | |
|             certs.renewing = core.certificates.renewAsync(args, certs);
 | |
|             if (args.waitForRenewal) {
 | |
|               return certs.renewing;
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           // return existing unexpired (although potentially stale) certificates when available
 | |
|           // there will be an additional .renewing property if the certs are being asynchronously renewed
 | |
|           return certs;
 | |
|         }).then(function (results) {
 | |
|           // returns pems
 | |
|           return results;
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   };
 | |
| 
 | |
|   return core;
 | |
| };
 |