mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2025-11-03 21:42:47 +00:00 
			
		
		
		
	sni callback stuff
This commit is contained in:
		
							parent
							
								
									3132e7a592
								
							
						
					
					
						commit
						75d259dbb1
					
				
							
								
								
									
										23
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								README.md
									
									
									
									
									
								
							@ -24,7 +24,7 @@ npm install --save letsencrypt-express
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Note: using staging server url, remove .testing() for production
 | 
					// Note: using staging server url, remove .testing() for production
 | 
				
			||||||
var le = require('letsencrypt-express').testing();
 | 
					var lex = require('letsencrypt-express').testing();
 | 
				
			||||||
var express = require('express');
 | 
					var express = require('express');
 | 
				
			||||||
var app = express();
 | 
					var app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,7 +32,7 @@ app.use('/', function (req, res) {
 | 
				
			|||||||
  res.send({ success: true });
 | 
					  res.send({ success: true });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
le.create('/etc/letsencrypt', app).listen([80], [443, 5001], function () {
 | 
					lex.create('/etc/letsencrypt', app).listen([80], [443, 5001], function () {
 | 
				
			||||||
  console.log("ENCRYPT __ALL__ THE DOMAINS!");
 | 
					  console.log("ENCRYPT __ALL__ THE DOMAINS!");
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@ -42,7 +42,7 @@ le.create('/etc/letsencrypt', app).listen([80], [443, 5001], function () {
 | 
				
			|||||||
```javascript
 | 
					```javascript
 | 
				
			||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var le = require('letsencrypt-express');
 | 
					var lex = require('letsencrypt-express');
 | 
				
			||||||
var express = require('express');
 | 
					var express = require('express');
 | 
				
			||||||
var app = express();
 | 
					var app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,7 +50,7 @@ app.use('/', function (req, res) {
 | 
				
			|||||||
  res.send({ success: true });
 | 
					  res.send({ success: true });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var results = le.create({
 | 
					var results = lex.create({
 | 
				
			||||||
  configDir: '/etc/letsencrypt'
 | 
					  configDir: '/etc/letsencrypt'
 | 
				
			||||||
, onRequest: app
 | 
					, onRequest: app
 | 
				
			||||||
, server: require('letsencrypt').productionServerUrl
 | 
					, server: require('letsencrypt').productionServerUrl
 | 
				
			||||||
@ -84,6 +84,21 @@ results.tlsServers.forEach(function (server) {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					LEX.create(options)             // checks options and sets up defaults. returns object with `listen`
 | 
				
			||||||
 | 
					                                // (it was really just done this way to appeal to what people are used to seeing)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lex.listen(plain, tls, fn)    // actually creates the servers and causes them to listen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LEX.createSniCallback(le)       // receives an instance of letsencrypt, returns an SNICallback handler for https.createServer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LEX.getChallenge(opts, hostname, key cb)  // uses `opts.webrootPath` to read from the filesystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Options
 | 
					## Options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If any of these values are `undefined` or `null` the will assume use reasonable defaults.
 | 
					If any of these values are `undefined` or `null` the will assume use reasonable defaults.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										128
									
								
								lib/sni-callback.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								lib/sni-callback.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var crypto = require('crypto');
 | 
				
			||||||
 | 
					var tls = require('tls');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.create = function (memos) {
 | 
				
			||||||
 | 
					  var ipc = {}; // in-process cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!memos) { throw new Error("requires opts to be an object"); }
 | 
				
			||||||
 | 
					  if (!memos.letsencrypt) { throw new Error("requires opts.letsencrypt to be a letsencrypt instance"); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!memos.lifetime) { memos.lifetime = 90 * 24 * 60 * 60 * 1000; }
 | 
				
			||||||
 | 
					  if (!memos.failedWait) { memos.failedWait = 5 * 60 * 1000; }
 | 
				
			||||||
 | 
					  if (!memos.renewWithin) { memos.renewWithin = 3 * 24 * 60 * 60 * 1000; }
 | 
				
			||||||
 | 
					  if (!memos.memorizeFor) { memos.memorizeFor = 1 * 24 * 60 * 60 * 1000; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!memos.handleRegistration) { memos.handleRegistration = function (args, cb) { cb(null, null); }; }
 | 
				
			||||||
 | 
					  if (!memos.handleRenewFailure) { memos.handleRenewFailure = function () {}; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function assignBestByDates(now, certInfo) {
 | 
				
			||||||
 | 
					    certInfo = certInfo || { loadedAt: now, expiresAt: 0, issuedAt: 0, lifetime: 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var rnds = crypto.randomBytes(3)[0];
 | 
				
			||||||
 | 
					    var rnd1 = ((rnds[0] + 1) / 257);
 | 
				
			||||||
 | 
					    var rnd2 = ((rnds[1] + 1) / 257);
 | 
				
			||||||
 | 
					    var rnd3 = ((rnds[2] + 1) / 257);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Stagger randomly by plus 0% to 25% to prevent all caches expiring at once
 | 
				
			||||||
 | 
					    var memorizeFor = Math.floor(memos.memorizeFor + ((memos.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 bestIfUsedBy = certInfo.expiresAt - (memos.renewWithin + Math.floor(memos.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 renewTimeout = Math.floor((5 * 60 * 1000) * rnd3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    certInfo.loadedAt = now;
 | 
				
			||||||
 | 
					    certInfo.memorizeFor = memorizeFor;
 | 
				
			||||||
 | 
					    certInfo.bestIfUsedBy = bestIfUsedBy;
 | 
				
			||||||
 | 
					    certInfo.renewTimeout = renewTimeout;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function renewInBackground(now, hostname, certInfo) {
 | 
				
			||||||
 | 
					    if ((now - certInfo.loadedAt) < memos.failedWait) {
 | 
				
			||||||
 | 
					      // wait a few minutes
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (now > certInfo.bestIfUsedBy && !certInfo.timeout) {
 | 
				
			||||||
 | 
					      // EXPIRING
 | 
				
			||||||
 | 
					      if (now > certInfo.expiresAt) {
 | 
				
			||||||
 | 
					        // EXPIRED
 | 
				
			||||||
 | 
					        certInfo.renewTimeout = Math.floor(certInfo.renewTimeout / 2);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      certInfo.timeout = setTimeout(function () {
 | 
				
			||||||
 | 
					        var opts = { domains: [ hostname ], duplicate: false };
 | 
				
			||||||
 | 
					        le.renew(opts, function (err, certInfo) {
 | 
				
			||||||
 | 
					          if (err || !certInfo) {
 | 
				
			||||||
 | 
					            memos.handleRenewFailure(err, certInfo, opts);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          ipc[hostname] = assignBestByDates(now, certInfo);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }, certInfo.renewTimeout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function fetch(hostname, cb) {
 | 
				
			||||||
 | 
					    le.fetch({ domains: [hostname] }, function (err, certInfo) {
 | 
				
			||||||
 | 
					      var now = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ipc[hostname] = assignBestByDates(now, certInfo);
 | 
				
			||||||
 | 
					      if (!certInfo) {
 | 
				
			||||||
 | 
					        // handles registration
 | 
				
			||||||
 | 
					        memos.handleRegistration(hostname, cb);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // handles renewals
 | 
				
			||||||
 | 
					      renewInBackground(now, hostname, certInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (err) {
 | 
				
			||||||
 | 
					        cb(err);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        certInfo.tlsContext = tls.createSecureContext({
 | 
				
			||||||
 | 
					          key: certInfo.key                             // privkey.pem
 | 
				
			||||||
 | 
					        , cert: certInfo.cert                           // fullchain.pem (cert.pem + '\n' + chain.pem)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } catch(e) {
 | 
				
			||||||
 | 
					        console.warn("[Sanity Check Fail]: a weird object was passed back through le.fetch to lex.fetch");
 | 
				
			||||||
 | 
					        cb(e);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      cb(null, certInfo.tlsContext);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return function sniCallback(hostname, cb) {
 | 
				
			||||||
 | 
					    var now = Date.now();
 | 
				
			||||||
 | 
					    var certInfo = ipc[hostname];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO once ECDSA is available, wait for cert renewal if its due
 | 
				
			||||||
 | 
					    if (!certInfo) {
 | 
				
			||||||
 | 
					      fetch(hostname, cb);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (certInfo.context) {
 | 
				
			||||||
 | 
					      cb(null, certInfo.context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if ((now - certInfo.loadedAt) < (certInfo.memorizeFor)) {
 | 
				
			||||||
 | 
					        // these aren't stale, so don't fall through
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else if ((now - certInfo.loadedAt) < memos.failedWait) {
 | 
				
			||||||
 | 
					      // this was just fetched and failed, wait a few minutes
 | 
				
			||||||
 | 
					      cb(null, null);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetch({ domains: [hostname] }, cb);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var path = require('path');
 | 
					var path = require('path');
 | 
				
			||||||
var challengeStore = require('./lib/challange-handlers');
 | 
					var challengeStore = require('./lib/challange-handlers');
 | 
				
			||||||
 | 
					var createSniCallback = require('./lib/sni-callback').create;
 | 
				
			||||||
var LE = require('letsencrypt');
 | 
					var LE = require('letsencrypt');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function LEX(obj, app) {
 | 
					function LEX(obj, app) {
 | 
				
			||||||
@ -105,6 +106,7 @@ function LEX(obj, app) {
 | 
				
			|||||||
    httpsOptions.SNICallback = obj.sniCallback;
 | 
					    httpsOptions.SNICallback = obj.sniCallback;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  else if (sniCallback) {
 | 
					  else if (sniCallback) {
 | 
				
			||||||
 | 
					    obj._sniCallback = createSniCallback(obj);
 | 
				
			||||||
    httpsOptions.SNICallback = function (domain, cb) {
 | 
					    httpsOptions.SNICallback = function (domain, cb) {
 | 
				
			||||||
      sniCallback(domain, function (err, context) {
 | 
					      sniCallback(domain, function (err, context) {
 | 
				
			||||||
        if (context) {
 | 
					        if (context) {
 | 
				
			||||||
@ -112,12 +114,12 @@ function LEX(obj, app) {
 | 
				
			|||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        obj.letsencrypt.sniCallback(domain, cb);
 | 
					        obj._sniCallback(domain, cb);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  else {
 | 
					  else {
 | 
				
			||||||
    httpsOptions.SNICallback = obj.letsencrypt.sniCallback;
 | 
					    httpsOptions.SNICallback = createSniCallback(obj);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function listen(plainPorts, tlsPorts, onListening) {
 | 
					  function listen(plainPorts, tlsPorts, onListening) {
 | 
				
			||||||
@ -190,6 +192,7 @@ function LEX(obj, app) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = LEX;
 | 
					module.exports = LEX;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LEX.create = LEX;
 | 
					LEX.create = LEX;
 | 
				
			||||||
LEX.setChallenge = challengeStore.set;
 | 
					LEX.setChallenge = challengeStore.set;
 | 
				
			||||||
LEX.getChallenge = challengeStore.get;
 | 
					LEX.getChallenge = challengeStore.get;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user