| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  | // TODO handle www and no-www together somehow?
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 15:05:45 +00:00
										 |  |  | var PromiseA = require('bluebird'); | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  | var crypto = require('crypto'); | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | var tls = require('tls'); | 
					
						
							| 
									
										
										
										
											2015-12-14 21:21:59 -08:00
										 |  |  | var path = require('path'); | 
					
						
							| 
									
										
										
										
											2015-12-12 15:05:45 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | var LE = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  | LE.liveServer = "https://acme-v01.api.letsencrypt.org/directory"; | 
					
						
							|  |  |  | LE.stagingServer = "https://acme-staging.api.letsencrypt.org/directory"; | 
					
						
							| 
									
										
										
										
											2015-12-14 21:21:59 -08:00
										 |  |  | LE.configDir = "/etc/letsencrypt/"; | 
					
						
							|  |  |  | LE.logsDir = "/var/log/letsencrypt/"; | 
					
						
							|  |  |  | LE.workDir = "/var/lib/letsencrypt/"; | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | LE.merge = function merge(defaults, args) { | 
					
						
							|  |  |  |   var copy = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Object.keys(defaults).forEach(function (key) { | 
					
						
							|  |  |  |     copy[key] = defaults[key]; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   Object.keys(args).forEach(function (key) { | 
					
						
							|  |  |  |     copy[key] = args[key]; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return copy; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  | LE.create = function (backend, defaults, handlers) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |   if (!handlers) { handlers = {}; } | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   if (!handlers.lifetime) { handlers.lifetime = 90 * 24 * 60 * 60 * 1000; } | 
					
						
							|  |  |  |   if (!handlers.renewWithin) { handlers.renewWithin = 3 * 24 * 60 * 60 * 1000; } | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |   if (!handlers.memorizeFor) { handlers.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   if (!handlers.sniRegisterCallback) { | 
					
						
							|  |  |  |     handlers.sniRegisterCallback = function (args, cache, cb) { | 
					
						
							|  |  |  |       // TODO when we have ECDSA, just do this automatically
 | 
					
						
							|  |  |  |       cb(null, null); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-15 03:38:21 -08:00
										 |  |  |   if (!handlers.setChallenge) { | 
					
						
							|  |  |  |     if (!defaults.webrootPath) { | 
					
						
							|  |  |  |       // GET /.well-known/acme-challenge/{{challengeKey}} should return {{tokenValue}}
 | 
					
						
							|  |  |  |       throw new Error("handlers.setChallenge or defaults.webrootPath must be set"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 12:01:05 +00:00
										 |  |  |     handlers.setChallenge = require('./lib/default-handlers').setChallenge; | 
					
						
							| 
									
										
										
										
											2015-12-15 03:38:21 -08:00
										 |  |  |   } | 
					
						
							|  |  |  |   if (!handlers.removeChallenge) { | 
					
						
							|  |  |  |     if (!defaults.webrootPath) { | 
					
						
							|  |  |  |       // GET /.well-known/acme-challenge/{{challengeKey}} should return {{tokenValue}}
 | 
					
						
							|  |  |  |       throw new Error("handlers.setChallenge or defaults.webrootPath must be set"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 12:01:46 +00:00
										 |  |  |     handlers.removeChallenge = require('./lib/default-handlers').removeChallenge; | 
					
						
							| 
									
										
										
										
											2015-12-15 03:38:21 -08:00
										 |  |  |   } | 
					
						
							|  |  |  |   if (!handlers.agreeToTerms) { | 
					
						
							|  |  |  |     if (defaults.agreeTos) { | 
					
						
							|  |  |  |       console.warn("[WARN] Agreeing to terms by default is risky business..."); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 13:12:16 +00:00
										 |  |  |     handlers.agreeToTerms = require('./lib/default-handlers').agreeToTerms; | 
					
						
							| 
									
										
										
										
											2015-12-15 03:38:21 -08:00
										 |  |  |   } | 
					
						
							|  |  |  |   if ('function' === typeof backend.create) { | 
					
						
							|  |  |  |     backend = backend.create(defaults, handlers); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     // ignore
 | 
					
						
							|  |  |  |     // this backend was created the v1.0.0 way
 | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   backend = PromiseA.promisifyAll(backend); | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 03:38:21 -08:00
										 |  |  |   var utils = require('./utils'); | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |   //var attempts = {};  // should exist in master process only
 | 
					
						
							|  |  |  |   var ipc = {};       // in-process cache
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |   var le; | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 15:05:45 +00:00
										 |  |  |   // TODO check certs on initial load
 | 
					
						
							|  |  |  |   // TODO expect that certs expire every 90 days
 | 
					
						
							|  |  |  |   // TODO check certs with setInterval?
 | 
					
						
							|  |  |  |   //options.cacheContextsFor = options.cacheContextsFor || (1 * 60 * 60 * 1000);
 | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |   function sniCallback(hostname, cb) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |     var args = LE.merge(defaults, {}); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |     args.domains = [hostname]; | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |     le.fetch(args, function (err, cache) { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         cb(err); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       // vazhdo is Albanian for 'continue'
 | 
					
						
							|  |  |  |       function vazhdo(err, c2) { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           cb(err); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 15:50:00 +00:00
										 |  |  |         cache = c2 || cache; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!cache.context) { | 
					
						
							|  |  |  |           cache.context = tls.createSecureContext({ | 
					
						
							|  |  |  |             key: cache.key    // privkey.pem
 | 
					
						
							|  |  |  |           , cert: cache.cert  // fullchain.pem
 | 
					
						
							|  |  |  |           //, ciphers         // node's defaults are great
 | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-14 21:21:59 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 15:50:00 +00:00
										 |  |  |         cb(null, cache.context); | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-12 15:50:00 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |       if (cache) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |         vazhdo(); | 
					
						
							| 
									
										
										
										
											2015-12-12 15:50:00 +00:00
										 |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       var args = LE.merge(defaults, { domains: [hostname] }); | 
					
						
							|  |  |  |       handlers.sniRegisterCallback(args, cache, vazhdo); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   le = { | 
					
						
							| 
									
										
										
										
											2015-12-15 12:12:15 +00:00
										 |  |  |     backend: backend | 
					
						
							|  |  |  |   , validate: function (hostnames, cb) { | 
					
						
							| 
									
										
										
										
											2015-12-12 15:05:45 +00:00
										 |  |  |       // TODO check dns, etc
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       if ((!hostnames.length && hostnames.every(le.isValidDomain))) { | 
					
						
							|  |  |  |         cb(new Error("node-letsencrypt: invalid hostnames: " + hostnames.join(','))); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 06:00:30 +00:00
										 |  |  |       //
 | 
					
						
							|  |  |  |       // IMPORTANT
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       // Before attempting a dynamic registration you need to validate that
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       //   * these are hostnames that you expected to exist on the system
 | 
					
						
							|  |  |  |       //   * their A records currently point to this ip
 | 
					
						
							|  |  |  |       //   * this system's ip hasn't changed
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       //  If you do not check these things, then someone could attack you
 | 
					
						
							|  |  |  |       //  and cause you, in return, to have your ip be rate-limit blocked
 | 
					
						
							|  |  |  |       //
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       console.warn("[SECURITY WARNING]: node-letsencrypt: validate(hostnames, cb) NOT IMPLEMENTED"); | 
					
						
							|  |  |  |       cb(null, true); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |   , middleware: function () { | 
					
						
							| 
									
										
										
										
											2015-12-12 15:19:11 +00:00
										 |  |  |       //console.log('[DEBUG] webrootPath', defaults.webrootPath);
 | 
					
						
							|  |  |  |       var serveStatic = require('serve-static')(defaults.webrootPath, { dotfiles: 'allow' }); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |       var prefix = '/.well-known/acme-challenge/'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return function (req, res, next) { | 
					
						
							| 
									
										
										
										
											2015-12-12 15:05:45 +00:00
										 |  |  |         if (0 !== req.url.indexOf(prefix)) { | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |           next(); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 15:19:11 +00:00
										 |  |  |         serveStatic(req, res, next); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   , SNICallback: sniCallback | 
					
						
							|  |  |  |   , sniCallback: sniCallback | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   , _registerHelper: function (args, cb) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       var copy = LE.merge(defaults, args); | 
					
						
							|  |  |  |       var err; | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (!utils.isValidDomain(args.domains[0])) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |         err = new Error("invalid domain"); | 
					
						
							|  |  |  |         err.code = "INVALID_DOMAIN"; | 
					
						
							|  |  |  |         cb(err); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       return le.validate(args.domains, function (err) { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           cb(err); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |         console.log("[NLE]: begin registration"); | 
					
						
							|  |  |  |         return backend.registerAsync(copy).then(function () { | 
					
						
							|  |  |  |           console.log("[NLE]: end registration"); | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |           // calls fetch because fetch calls cacheCertInfo
 | 
					
						
							|  |  |  |           return le.fetch(args, cb); | 
					
						
							|  |  |  |         }, cb); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   , _fetchHelper: function (args, cb) { | 
					
						
							|  |  |  |       return backend.fetchAsync(args).then(function (certInfo) { | 
					
						
							|  |  |  |         if (!certInfo) { | 
					
						
							|  |  |  |           cb(null, null); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var now = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // key, cert, issuedAt, lifetime, expiresAt
 | 
					
						
							|  |  |  |         if (!certInfo.expiresAt) { | 
					
						
							|  |  |  |           certInfo.expiresAt = certInfo.issuedAt + (certInfo.lifetime || handlers.lifetime); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!certInfo.lifetime) { | 
					
						
							|  |  |  |           certInfo.lifetime = (certInfo.lifetime || handlers.lifetime); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // a pretty good hard buffer
 | 
					
						
							|  |  |  |         certInfo.expiresAt -= (1 * 24 * 60 * 60 * 100); | 
					
						
							|  |  |  |         certInfo = LE.cacheCertInfo(args, certInfo, ipc, handlers); | 
					
						
							|  |  |  |         if (now > certInfo.bestIfUsedBy && !certInfo.timeout) { | 
					
						
							|  |  |  |           // EXPIRING
 | 
					
						
							|  |  |  |           if (now  > certInfo.expiresAt) { | 
					
						
							|  |  |  |             // EXPIRED
 | 
					
						
							|  |  |  |             certInfo.renewTimeout = Math.floor(certInfo.renewTimeout / 2); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           certInfo.timeout = setTimeout(function () { | 
					
						
							|  |  |  |             le.register(args, cb); | 
					
						
							|  |  |  |           }, certInfo.renewTimeout); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         cb(null, certInfo.context); | 
					
						
							|  |  |  |       }, cb); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |   , fetch: function (args, cb) { | 
					
						
							|  |  |  |       var hostname = args.domains[0]; | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |       // TODO don't call now() every time because this is hot code
 | 
					
						
							|  |  |  |       var now = Date.now(); | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |       var certInfo = ipc[hostname]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // TODO once ECDSA is available, wait for cert renewal if its due
 | 
					
						
							|  |  |  |       if (certInfo) { | 
					
						
							|  |  |  |         if (now > certInfo.bestIfUsedBy && !certInfo.timeout) { | 
					
						
							|  |  |  |           // EXPIRING
 | 
					
						
							|  |  |  |           if (now  > certInfo.expiresAt) { | 
					
						
							|  |  |  |             // EXPIRED
 | 
					
						
							|  |  |  |             certInfo.renewTimeout = Math.floor(certInfo.renewTimeout / 2); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           certInfo.timeout = setTimeout(function () { | 
					
						
							|  |  |  |             le.register(args, cb); | 
					
						
							|  |  |  |           }, certInfo.renewTimeout); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         cb(null, certInfo.context); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |         if ((now - certInfo.loadedAt) < (certInfo.memorizeFor)) { | 
					
						
							|  |  |  |           // these aren't stale, so don't fall through
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |       le._fetchHelper(args, cb); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |   , register: function (args, cb) { | 
					
						
							|  |  |  |       // this may be run in a cluster environment
 | 
					
						
							|  |  |  |       // in that case it should NOT check the cache
 | 
					
						
							|  |  |  |       // but ensure that it has the most fresh copy
 | 
					
						
							|  |  |  |       // before attempting a renew
 | 
					
						
							|  |  |  |       le._fetchHelper(args, function (err, hit) { | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |         var hostname = args.domains[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           cb(err); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else if (hit) { | 
					
						
							|  |  |  |           cb(null, hit); | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |         return le._registerHelper(args, function (err) { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             cb(err); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           le._fetchHelper(args, function (err, cache) { | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |             if (cache) { | 
					
						
							|  |  |  |               cb(null, cache.context); | 
					
						
							|  |  |  |               return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |             // still couldn't read the certs after success... that's weird
 | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |             cb(err, null); | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |           }); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |         }, function (err) { | 
					
						
							|  |  |  |           console.error("[Error] Let's Encrypt failed:"); | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  |           console.error(err.stack || new Error(err.message || err.toString()).stack); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |           // wasn't successful with lets encrypt, don't automatically try again for 12 hours
 | 
					
						
							|  |  |  |           // TODO what's the better way to handle this?
 | 
					
						
							|  |  |  |           // failure callback?
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |           ipc[hostname] = { | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |             context: null // TODO default context
 | 
					
						
							|  |  |  |           , issuedAt: Date.now() | 
					
						
							|  |  |  |           , lifetime: (12 * 60 * 60 * 1000) | 
					
						
							|  |  |  |           // , expiresAt: generated in next step
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |           }; | 
					
						
							| 
									
										
										
										
											2015-12-13 01:04:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  |           cb(err, ipc[hostname]); | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-12 14:20:12 +00:00
										 |  |  |   return le; | 
					
						
							| 
									
										
										
										
											2015-12-11 06:22:46 -08:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2015-12-13 05:03:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | LE.cacheCertInfo = function (args, certInfo, ipc, handlers) { | 
					
						
							|  |  |  |   // TODO IPC via process and worker to guarantee no races
 | 
					
						
							|  |  |  |   // rather than just "really good odds"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var hostname = args.domains[0]; | 
					
						
							|  |  |  |   var now = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Stagger randomly by plus 0% to 25% to prevent all caches expiring at once
 | 
					
						
							|  |  |  |   var rnd1 = (crypto.randomBytes(1)[0] / 255); | 
					
						
							|  |  |  |   var memorizeFor = Math.floor(handlers.memorizeFor + ((handlers.memorizeFor / 4) * rnd1)); | 
					
						
							|  |  |  |   // Stagger randomly to renew between n and 2n days before renewal is due
 | 
					
						
							|  |  |  |   // this *greatly* reduces the risk of multiple cluster processes renewing the same domain at once
 | 
					
						
							|  |  |  |   var rnd2 = (crypto.randomBytes(1)[0] / 255); | 
					
						
							|  |  |  |   var bestIfUsedBy = certInfo.expiresAt - (handlers.renewWithin + Math.floor(handlers.renewWithin * rnd2)); | 
					
						
							|  |  |  |   // Stagger randomly by plus 0 to 5 min to reduce risk of multiple cluster processes
 | 
					
						
							|  |  |  |   // renewing at once on boot when the certs have expired
 | 
					
						
							|  |  |  |   var rnd3 = (crypto.randomBytes(1)[0] / 255); | 
					
						
							|  |  |  |   var renewTimeout = Math.floor((5 * 60 * 1000) * rnd3); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   certInfo.context = tls.createSecureContext({ | 
					
						
							|  |  |  |     key: certInfo.key | 
					
						
							|  |  |  |   , cert: certInfo.cert | 
					
						
							|  |  |  |   //, ciphers // node's defaults are great
 | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   certInfo.loadedAt = now; | 
					
						
							|  |  |  |   certInfo.memorizeFor = memorizeFor; | 
					
						
							|  |  |  |   certInfo.bestIfUsedBy = bestIfUsedBy; | 
					
						
							|  |  |  |   certInfo.renewTimeout = renewTimeout; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ipc[hostname] = certInfo; | 
					
						
							|  |  |  |   return ipc[hostname]; | 
					
						
							|  |  |  | }; |