115 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			115 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| // opts = { notBefore, notAfter, renew, register, httpsOptions }
 | |
| module.exports.create = function (opts) {
 | |
| 
 | |
|   if (!opts.notBefore) { throw new Error("must supply options.notBefore (and options.notAfter)"); }
 | |
|   if (!opts.notAfter) { opts.notAfter = opts.notBefore - (3 * 24 * 60 * 60 * 1000); }
 | |
|   if (!opts.httpsOptions) { opts.httpOptions = {}; }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|   //opts.renewWithin = opts.notBefore;                    // i.e. 15 days
 | |
|   opts.renewWindow = opts.notBefore - opts.notAfter;      // i.e. 1 day
 | |
|   //opts.renewRatio = opts.notBefore = opts.renewWindow;  // i.e. 1/15 (6.67%)
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|   var tls = require('tls');
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|   var snicb = {
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|     // in-process cache
 | |
|     _ipc: {}
 | |
|     // just to account for clock skew
 | |
|   , _fiveMin: 5 * 60 * 1000
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|     // cache and format incoming certs
 | |
|   , cacheCerts: function (certs) {
 | |
|       var meta = {
 | |
|         certs: certs
 | |
|       , tlsContext: tls.createSecureContext({
 | |
|           key: certs.privkey
 | |
|         , cert: certs.cert + certs.chain
 | |
|         , rejectUnauthorized: opts.httpsOptions.rejectUnauthorized
 | |
| 
 | |
|         , requestCert: opts.httpsOptions.requestCert  // request peer verification
 | |
|         , ca: opts.httpsOptions.ca                    // this chain is for incoming peer connctions
 | |
|         , crl: opts.httpsOptions.crl                  // this crl is for incoming peer connections
 | |
|         })
 | |
| 
 | |
|       , subject: certs.subject
 | |
|         // stagger renewal time by a little bit of randomness
 | |
|       , renewAt: (certs.expiresAt - (opts.notBefore - (opts.renewWindow * Math.random())))
 | |
|         // err just barely on the side of safety
 | |
|       , expiresNear: certs.expiresAt - snicb._fiveMin
 | |
|       };
 | |
| 
 | |
|       certs.altnames.forEach(function (domain) {
 | |
|         snicb._ipc[domain] = { subject: certs.subject };
 | |
|       });
 | |
|       snicb._ipc[certs.subject] = meta;
 | |
| 
 | |
|       return meta;
 | |
|     }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|     // automate certificate registration on request
 | |
|   , sniCallback: function (domain, cb) {
 | |
|       var certMeta = snicb._ipc[domain];
 | |
|       var promise;
 | |
|       var now = Date.now();
 | |
| 
 | |
|       if (certMeta && certMeta.subject !== domain) {
 | |
|         certMeta = snicb._ipc[domain];
 | |
|       }
 | |
| 
 | |
|       if (!certMeta) {
 | |
|         // we don't have a cert and must get one
 | |
|         promise = opts.register(domain);
 | |
|       }
 | |
|       else if (now >= certMeta.expiresNear) {
 | |
|         // we have a cert, but it's no good for the average user
 | |
|         promise = opts.renew(domain, certMeta.certs);
 | |
|       } else {
 | |
| 
 | |
|         // we could stand to try to renew the cert
 | |
|         if (now >= certMeta.renewAt) {
 | |
|           // give the cert some time to be validated and replaced before trying again
 | |
|           certMeta.renewAt = Date.now() + (2 * 60 * 60 * 1000) + (3 * 60 * 60 * 1000 * Math.random());
 | |
|           // let the update happen in the background
 | |
|           opts.renew(domain, certMeta.certs).then(snicb.cacheCerts);
 | |
|         }
 | |
| 
 | |
|         // return the valid cert right away
 | |
|         cb(null, certMeta.certs);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // promise the non-existent or expired cert
 | |
|       promise.then(snicb.cacheCerts).then(function (certMeta) {
 | |
|         cb(null, certMeta.tlsContext);
 | |
|       }, cb);
 | |
|     }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|   };
 | |
| 
 | |
|   return snicb;
 | |
| };
 |