| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var path = require('path'); | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  | var challengeStore = require('./challenge-handlers'); | 
					
						
							|  |  |  | var createSniCallback = require('./sni-callback').create; | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | var LE = require('letsencrypt'); | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | function LEX(obj, app) { | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |   var https = require('https'); | 
					
						
							|  |  |  |   var http = require('http'); | 
					
						
							|  |  |  |   var defaultPems = require('localhost.daplie.com-certificates'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!obj) { | 
					
						
							|  |  |  |     obj = {}; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if ('string' === typeof obj) { | 
					
						
							|  |  |  |     obj = { | 
					
						
							|  |  |  |       configDir: obj | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ('function' === typeof obj) { | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |     obj = { | 
					
						
							|  |  |  |       onRequest: obj | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |   obj.debug = LEX.debug; | 
					
						
							|  |  |  |    | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |   if ('function' === typeof app) { | 
					
						
							|  |  |  |     obj.onRequest = obj.onRequest || app; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |   if (!obj.getChallenge) { | 
					
						
							| 
									
										
										
										
											2015-12-16 23:06:00 +00:00
										 |  |  |     if (false !== obj.getChallenge) { | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |       obj.getChallenge = challengeStore.get; | 
					
						
							| 
									
										
										
										
											2015-12-16 23:06:00 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |     if (!obj.webrootPath) { | 
					
						
							|  |  |  |       obj.webrootPath = path.join(require('os').tmpdir(), 'acme-challenge'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |   if (!obj.onRequest && false !== obj.onRequest) { | 
					
						
							|  |  |  |     console.warn("You should either do args.onRequest = app or server.on('request', app)," | 
					
						
							|  |  |  |       + " otherwise only acme-challenge requests will be handled (and the rest will hang)"); | 
					
						
							|  |  |  |     console.warn("You can silence this warning by setting args.onRequest = false"); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |   if (!obj.configDir) { | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  |     obj.configDir = path.join(require('homedir')(), '/letsencrypt/etc'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!obj.privkeyPath) { | 
					
						
							|  |  |  |     obj.privkeyPath = ':config/live/:hostname/privkey.pem'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!obj.fullchainPath) { | 
					
						
							|  |  |  |     obj.fullchainPath = ':config/live/:hostname/fullchain.pem'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!obj.certPath) { | 
					
						
							|  |  |  |     obj.certPath = ':config/live/:hostname/cert.pem'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!obj.chainPath) { | 
					
						
							|  |  |  |     obj.chainPath = ':config/live/:hostname/chain.pem'; | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!obj.server) { | 
					
						
							|  |  |  |     obj.server = LEX.defaultServerUrl; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |   if (!obj.letsencrypt) { | 
					
						
							|  |  |  |     //LE.merge(obj, );
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |     // { configDir, webrootPath, server }
 | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |     obj.letsencrypt = LE.create(obj, { | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |       setChallenge: challengeStore.set | 
					
						
							|  |  |  |     , removeChallenge: challengeStore.remove | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function acmeResponder(req, res) { | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |     if (LEX.debug) { | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  |       console.debug('[LEX] ', req.method, req.headers.host, req.url); | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |     var acmeChallengePrefix = '/.well-known/acme-challenge/'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (0 !== req.url.indexOf(acmeChallengePrefix)) { | 
					
						
							|  |  |  |       obj.onRequest(req, res); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var key = req.url.slice(acmeChallengePrefix.length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     obj.getChallenge(obj, req.headers.host, key, function (err, val) { | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |       if (LEX.debug) { | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  |         console.debug('[LEX] GET challenge, response:'); | 
					
						
							|  |  |  |         console.debug('challenge:', key); | 
					
						
							|  |  |  |         console.debug('response:', val); | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           console.debug(err.stack); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |       res.end(val || '_'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   var httpsOptions = obj.httpsOptions || {}; | 
					
						
							|  |  |  |   var sniCallback = httpsOptions.SNICallback; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |   // https://nodejs.org/api/https.html
 | 
					
						
							|  |  |  |   // pfx, key, cert, passphrase, ca, ciphers, rejectUnauthorized, secureProtocol
 | 
					
						
							|  |  |  |   if (!httpsOptions.pfx) { | 
					
						
							|  |  |  |     if (!(httpsOptions.cert || httpsOptions.key)) { | 
					
						
							|  |  |  |       httpsOptions.key = defaultPems.key; | 
					
						
							|  |  |  |       httpsOptions.cert = defaultPems.cert; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (!(httpsOptions.cert && httpsOptions.key)) { | 
					
						
							|  |  |  |       if (!httpsOptions.cert) { | 
					
						
							|  |  |  |         console.warn("You specified httpsOptions.cert, but not httpsOptions.key"); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (!httpsOptions.key) { | 
					
						
							|  |  |  |         console.warn("You specified httpsOptions.key, but not httpsOptions.cert"); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |   if (!obj.approveRegistration && LEX.defaultApproveRegistration) { | 
					
						
							|  |  |  |     obj.approveRegistration = function (domain, cb) { | 
					
						
							|  |  |  |       if (LEX.debug) { | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  |         console.debug('[LEX] auto register against staging server'); | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |       cb(null, { | 
					
						
							|  |  |  |         email: 'example@gmail.com' | 
					
						
							|  |  |  |       , domains: [domain] | 
					
						
							|  |  |  |       , agreeTos: true | 
					
						
							|  |  |  |       , server: LEX.stagingServerUrl | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |   if (obj.sniCallback) { | 
					
						
							|  |  |  |     if (sniCallback) { | 
					
						
							|  |  |  |       console.warn("You specified both args.sniCallback and args.httpsOptions.SNICallback," | 
					
						
							|  |  |  |       + " but only args.sniCallback will be used."); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     httpsOptions.SNICallback = obj.sniCallback; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (sniCallback) { | 
					
						
							| 
									
										
										
										
											2015-12-17 03:03:07 +00:00
										 |  |  |     obj._sniCallback = createSniCallback(obj); | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |     httpsOptions.SNICallback = function (domain, cb) { | 
					
						
							|  |  |  |       sniCallback(domain, function (err, context) { | 
					
						
							|  |  |  |         if (context) { | 
					
						
							|  |  |  |           cb(err, context); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:03:07 +00:00
										 |  |  |         obj._sniCallback(domain, cb); | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							| 
									
										
										
										
											2015-12-17 03:03:07 +00:00
										 |  |  |     httpsOptions.SNICallback = createSniCallback(obj); | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  |   function listen(plainPorts, tlsPorts, onListening) { | 
					
						
							|  |  |  |     var results = { | 
					
						
							|  |  |  |       plainServers: [] | 
					
						
							|  |  |  |     , tlsServers: [] | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     plainPorts = plainPorts || [80]; | 
					
						
							|  |  |  |     tlsPorts = tlsPorts || [443, 5001]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function defaultOnListening() { | 
					
						
							|  |  |  |       /*jshint validthis: true*/ | 
					
						
							|  |  |  |       var server = this; | 
					
						
							|  |  |  |       var protocol = ('honorCipherOrder' in server || 'rejectUnauthorized' in server) ? 'https': 'http'; | 
					
						
							|  |  |  |       var addr = server.address(); | 
					
						
							|  |  |  |       var port; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (80 === addr.port || 443 === addr.port) { | 
					
						
							|  |  |  |         port = ''; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         port = ':' + addr.port; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       console.info('Listening ' + protocol + '://' + addr.address + port + '/'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     plainPorts.forEach(function (addr) { | 
					
						
							|  |  |  |       var port = addr.port || addr; | 
					
						
							|  |  |  |       var address = addr.address || ''; | 
					
						
							|  |  |  |       var server = http.createServer(acmeResponder); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       server.__le_onListening = addr.onListen; | 
					
						
							|  |  |  |       server.__le_port = port; | 
					
						
							|  |  |  |       server.__le_address = address; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       results.plainServers.push(server); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     tlsPorts.forEach(function (addr) { | 
					
						
							|  |  |  |       var port = addr.port || addr; | 
					
						
							|  |  |  |       var address = addr.address || ''; | 
					
						
							|  |  |  |       var options = addr.httpsOptions || httpsOptions; | 
					
						
							|  |  |  |       var server = https.createServer(options, acmeResponder); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       server.__le_onListen = addr.onListen; | 
					
						
							|  |  |  |       server.__le_port = port; | 
					
						
							|  |  |  |       server.__le_address = address; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       results.tlsServers.push(server); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results.plainServers.forEach(function (server) { | 
					
						
							|  |  |  |       var listen = server.__le_onListening || onListening || defaultOnListening; | 
					
						
							|  |  |  |       server.listen(server.__le_port, server.__le_address, listen); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results.tlsServers.forEach(function (server) { | 
					
						
							|  |  |  |       var listen = server.__le_onListening || onListening || defaultOnListening; | 
					
						
							|  |  |  |       server.listen(server.__le_port, server.__le_address, listen); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // deleting creates a "slow object", but that's okay (we only use it once)
 | 
					
						
							|  |  |  |     return results; | 
					
						
							| 
									
										
										
										
											2015-12-17 00:51:37 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-16 22:34:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     listen: listen | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | module.exports = LEX; | 
					
						
							| 
									
										
										
										
											2015-12-17 03:03:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | LEX.create = LEX; | 
					
						
							|  |  |  | LEX.setChallenge = challengeStore.set; | 
					
						
							|  |  |  | LEX.getChallenge = challengeStore.get; | 
					
						
							|  |  |  | LEX.removeChallenge = challengeStore.remove; | 
					
						
							| 
									
										
										
										
											2015-12-17 03:15:20 +00:00
										 |  |  | LEX.createSniCallback = createSniCallback; | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  | // TODO not sure how well this works
 | 
					
						
							|  |  |  | LEX.middleware = function (defaults) { | 
					
						
							|  |  |  |   var leCore = require('letiny-core'); | 
					
						
							|  |  |  |   var merge = require('letsencrypt/common').merge; | 
					
						
							|  |  |  |   var tplConfigDir = require('letsencrypt/common').tplConfigDir; | 
					
						
							|  |  |  |   var tplHostname = require('letsencrypt/common').tplHostname; | 
					
						
							|  |  |  |   var prefix = leCore.acmeChallengePrefix; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   tplConfigDir(defaults.configDir || '', defaults); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return function (req, res, next) { | 
					
						
							|  |  |  |     if (LEX.debug) { | 
					
						
							|  |  |  |       console.debug('[LEX middleware]:', req.hostname, req.url, req.url.slice(prefix.length)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (0 !== req.url.indexOf(prefix)) { | 
					
						
							|  |  |  |       next(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function done(err, token) { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         res.send("Error: These aren't the tokens you're looking for. Move along."); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       res.send(token); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var copy = merge(defaults, { domains: [req.hostname] }); | 
					
						
							|  |  |  |     tplHostname(req.hostname, copy); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     LEX.getChallenge(copy, req.hostname, req.url.slice(prefix.length), done); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2015-12-17 03:15:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | LEX.stagingServerUrl = LE.stagingServerUrl; | 
					
						
							|  |  |  | LEX.productionServerUrl = LE.productionServerUrl || LE.liveServerUrl; | 
					
						
							|  |  |  | LEX.defaultServerUrl = LEX.productionServerUrl; | 
					
						
							|  |  |  | LEX.testing = function () { | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |   LEX.debug = true; | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  |   LEX.defaultServerUrl = LEX.stagingServerUrl; | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  |   LEX.defaultApproveRegistration = true; | 
					
						
							| 
									
										
										
										
											2015-12-17 08:44:55 +00:00
										 |  |  |   console.debug = console.log; | 
					
						
							|  |  |  |   console.debug('[LEX] testing mode turned on'); | 
					
						
							|  |  |  |   console.debug('[LEX] default server: ' + LEX.defaultServerUrl); | 
					
						
							|  |  |  |   console.debug('\n'); | 
					
						
							|  |  |  |   console.debug('###################################################'); | 
					
						
							|  |  |  |   console.debug('#                                                 #'); | 
					
						
							|  |  |  |   console.debug('#     Open up a browser and visit this server     #'); | 
					
						
							|  |  |  |   console.debug('#     at its domain name.                         #'); | 
					
						
							|  |  |  |   console.debug('#                                                 #'); | 
					
						
							|  |  |  |   console.debug('#                                 ENJOY!          #'); | 
					
						
							|  |  |  |   console.debug('#                                                 #'); | 
					
						
							|  |  |  |   console.debug('###################################################'); | 
					
						
							|  |  |  |   console.debug('\n'); | 
					
						
							|  |  |  |   console.debug('Note: testing certs will be installed because .testing() was called.'); | 
					
						
							|  |  |  |   console.debug('      remove .testing() to get live certs.'); | 
					
						
							|  |  |  |   console.debug('\n'); | 
					
						
							|  |  |  |   console.debug('[LEX] automatic registration handling turned on for testing.'); | 
					
						
							|  |  |  |   console.debug('\n'); | 
					
						
							| 
									
										
										
										
											2015-12-17 05:08:14 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return module.exports; | 
					
						
							| 
									
										
										
										
											2015-12-17 01:20:56 +00:00
										 |  |  | }; |