| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  | var LE = require('../'); | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | var ipc = {}; // in-process cache
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  | module.exports.create = function (defaults, handlers, backend) { | 
					
						
							|  |  |  |   defaults.server = defaults.server || LE.liveServer; | 
					
						
							|  |  |  |   handlers.merge = require('./common').merge; | 
					
						
							|  |  |  |   handlers.tplCopy = require('./common').tplCopy; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var PromiseA = require('bluebird'); | 
					
						
							|  |  |  |   var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA); | 
					
						
							|  |  |  |   var LeCore = PromiseA.promisifyAll(require('letiny-core')); | 
					
						
							|  |  |  |   var crypto = require('crypto'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function createAccount(args, handlers) { | 
					
						
							|  |  |  |     // arg.rsaBitLength args.rsaExponent
 | 
					
						
							|  |  |  |     return RSA.generateKeypairAsync(args.rsaKeySize || 2048, 65537, { public: true, pem: true }).then(function (keypair) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return LeCore.registerNewAccountAsync({ | 
					
						
							|  |  |  |         email: args.email | 
					
						
							|  |  |  |       , newRegUrl: args._acmeUrls.newReg | 
					
						
							|  |  |  |       , agreeToTerms: function (tosUrl, agree) { | 
					
						
							|  |  |  |           // args.email = email; // already there
 | 
					
						
							|  |  |  |           args.tosUrl = tosUrl; | 
					
						
							|  |  |  |           handlers.agreeToTerms(args, agree); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       , accountKeypair: keypair | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       , debug: defaults.debug || args.debug || handlers.debug | 
					
						
							|  |  |  |       }).then(function (body) { | 
					
						
							|  |  |  |         // TODO XXX use sha256 (the python client uses md5)
 | 
					
						
							|  |  |  |         // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
 | 
					
						
							|  |  |  |         keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex'); | 
					
						
							|  |  |  |         keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex'); | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         var accountId = keypair.publicKeyMd5; | 
					
						
							|  |  |  |         var regr = { body: body }; | 
					
						
							|  |  |  |         var account = {}; | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         args.accountId = accountId; | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         account.keypair = keypair; | 
					
						
							|  |  |  |         account.regr = regr; | 
					
						
							|  |  |  |         account.accountId = accountId; | 
					
						
							|  |  |  |         account.id = accountId; | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         args.account = account; | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         return backend.setAccountAsync(args, account).then(function () { | 
					
						
							|  |  |  |           return account; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   function getAcmeUrls(args) { | 
					
						
							|  |  |  |     var now = Date.now(); | 
					
						
							| 
									
										
										
										
											2015-12-20 02:01:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     // TODO check response header on request for cache time
 | 
					
						
							|  |  |  |     if ((now - ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) { | 
					
						
							|  |  |  |       return PromiseA.resolve(ipc.acmeUrls); | 
					
						
							| 
									
										
										
										
											2015-12-20 03:31:05 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     return LeCore.getAcmeUrlsAsync(args.server).then(function (data) { | 
					
						
							|  |  |  |       ipc.acmeUrlsUpdatedAt = Date.now(); | 
					
						
							|  |  |  |       ipc.acmeUrls = data; | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       return ipc.acmeUrls; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   function getCertificateAsync(args, defaults, handlers) { | 
					
						
							|  |  |  |     function log() { | 
					
						
							|  |  |  |       if (args.debug || defaults.debug) { | 
					
						
							|  |  |  |         console.log.apply(console, arguments); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     var account = args.account; | 
					
						
							|  |  |  |     var promise; | 
					
						
							|  |  |  |     var keypairOpts = { public: true, pem: true }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     promise = backend.getPrivatePem(args).then(function (pem) { | 
					
						
							|  |  |  |       return RSA.import({ privateKeyPem: pem }); | 
					
						
							|  |  |  |     }, function (/*err*/) { | 
					
						
							|  |  |  |       return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { | 
					
						
							|  |  |  |         keypair.privateKeyPem = RSA.exportPrivatePem(keypair); | 
					
						
							|  |  |  |         keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair); | 
					
						
							|  |  |  |         return backend.setPrivatePem(args, keypair); | 
					
						
							| 
									
										
										
										
											2016-08-01 17:11:42 -04:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-12-17 08:46:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     return promise.then(function (domainKeypair) { | 
					
						
							|  |  |  |       log("[le/core.js] get certificate"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       args.domainKeypair = domainKeypair; | 
					
						
							|  |  |  |       //args.registration = domainKey;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return LeCore.getCertificateAsync({ | 
					
						
							|  |  |  |         debug: args.debug | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       , newAuthzUrl: args._acmeUrls.newAuthz | 
					
						
							|  |  |  |       , newCertUrl: args._acmeUrls.newCert | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       , accountKeypair: RSA.import(account.keypair) | 
					
						
							|  |  |  |       , domainKeypair: domainKeypair | 
					
						
							|  |  |  |       , domains: args.domains | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //
 | 
					
						
							|  |  |  |         // 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)
 | 
					
						
							|  |  |  |         //
 | 
					
						
							|  |  |  |       , setChallenge: function (domain, key, value, done) { | 
					
						
							|  |  |  |           var copy = handlers.merge({ domains: [domain] }, defaults); | 
					
						
							|  |  |  |           handlers.tplCopy(copy); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           args.domains = [domain]; | 
					
						
							|  |  |  |           //args.domains = args.domains || [domain];
 | 
					
						
							|  |  |  |           if (4 === handlers.setChallenge.length) { | 
					
						
							|  |  |  |             console.warn('[WARNING] deprecated use. Define setChallenge as function (opts, domain, key, val, cb) { }'); | 
					
						
							|  |  |  |             handlers.setChallenge(copy, key, value, done); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           else if (5 === handlers.setChallenge.length) { | 
					
						
							|  |  |  |             handlers.setChallenge(copy, domain, key, value, done); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           else { | 
					
						
							|  |  |  |             done(new Error("handlers.setChallenge receives the wrong number of arguments")); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2015-12-17 04:44:28 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       , removeChallenge: function (domain, key, done) { | 
					
						
							|  |  |  |           var copy = handlers.merge({ domains: [domain] }, defaults); | 
					
						
							|  |  |  |           handlers.tplCopy(copy); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (3 === handlers.removeChallenge.length) { | 
					
						
							|  |  |  |             handlers.removeChallenge(copy, key, done); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           else if (4 === handlers.removeChallenge.length) { | 
					
						
							|  |  |  |             handlers.removeChallenge(copy, domain, key, done); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           else { | 
					
						
							|  |  |  |             done(new Error("handlers.removeChallenge receives the wrong number of arguments")); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2015-12-17 04:44:28 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }).then(function (results) { | 
					
						
							|  |  |  |       // { cert, chain, fullchain, privkey }
 | 
					
						
							|  |  |  |       args.pems = results; | 
					
						
							|  |  |  |       return backend.setRegistration(args, defaults, handlers); | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   function getOrCreateDomainCertificate(args, defaults, handlers) { | 
					
						
							|  |  |  |     if (args.duplicate) { | 
					
						
							|  |  |  |       // we're forcing a refresh via 'dupliate: true'
 | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  |       return getCertificateAsync(args, defaults, handlers); | 
					
						
							| 
									
										
										
										
											2015-12-20 00:27:48 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     return backend.getRegistration(args).then(function (certs) { | 
					
						
							|  |  |  |       var halfLife = (certs.expiresAt - certs.issuedAt) / 2; | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       if (!certs || (Date.now() - certs.issuedAt) > halfLife) { | 
					
						
							|  |  |  |         // There is no cert available
 | 
					
						
							|  |  |  |         // Or the cert is more than half-expired
 | 
					
						
							|  |  |  |         return getCertificateAsync(args, defaults, handlers); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       return PromiseA.reject(new Error( | 
					
						
							|  |  |  |           "[ERROR] Certificate issued at '" | 
					
						
							|  |  |  |         + new Date(certs.issuedAt).toISOString() + "' and expires at '" | 
					
						
							|  |  |  |         + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '" | 
					
						
							|  |  |  |         + new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force." | 
					
						
							|  |  |  |       )); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   // returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
 | 
					
						
							|  |  |  |   function getOrCreateAcmeAccount(args, defaults, handlers) { | 
					
						
							|  |  |  |     function log() { | 
					
						
							|  |  |  |       if (args.debug) { | 
					
						
							|  |  |  |         console.log.apply(console, arguments); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |     return backend.getAccountId(args).then(function (accountId) { | 
					
						
							| 
									
										
										
										
											2015-12-20 00:27:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       // Note: the ACME urls are always fetched fresh on purpose
 | 
					
						
							|  |  |  |       return getAcmeUrls(args).then(function (urls) { | 
					
						
							|  |  |  |         args._acmeUrls = urls; | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         if (accountId) { | 
					
						
							|  |  |  |           log('[le/core.js] use account'); | 
					
						
							| 
									
										
										
										
											2016-08-04 14:26:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |           args.accountId = accountId; | 
					
						
							|  |  |  |           return Accounts.getAccount(args, handlers); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           log('[le/core.js] create account'); | 
					
						
							|  |  |  |           return Accounts.createAccount(args, handlers); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }).then(function (account) { | 
					
						
							|  |  |  |       /* | 
					
						
							|  |  |  |       if (renewal.account !== account) { | 
					
						
							|  |  |  |         // the account has become corrupt, re-register
 | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |       */ | 
					
						
							|  |  |  |       log('[le/core.js] created account'); | 
					
						
							|  |  |  |       return account; | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-15 15:21:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   var wrapped = { | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |    registerAsync: function (args) { | 
					
						
							|  |  |  |       var copy = handlers.merge(args, defaults); | 
					
						
							|  |  |  |       handlers.tplCopy(copy); | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 00:27:48 +00:00
										 |  |  |       return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  |         copy.account = account; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         return backend.getOrCreateRenewal(copy).then(function (pyobj) { | 
					
						
							| 
									
										
										
										
											2015-12-20 05:13:41 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |           copy.pyobj = pyobj; | 
					
						
							|  |  |  |           return getOrCreateDomainCertificate(copy, defaults, handlers); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-02-10 15:41:15 -05:00
										 |  |  |       }).then(function (result) { | 
					
						
							|  |  |  |         return result; | 
					
						
							|  |  |  |       }, function (err) { | 
					
						
							|  |  |  |         return PromiseA.reject(err); | 
					
						
							| 
									
										
										
										
											2015-12-20 00:27:48 +00:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |   , getOrCreateAccount: function (args) { | 
					
						
							|  |  |  |       // TODO
 | 
					
						
							|  |  |  |       keypair.privateKeyPem = RSA.exportPrivatePem(keypair); | 
					
						
							|  |  |  |       keypair.publicKeyPem = RSA.exportPublicPem(keypair); | 
					
						
							|  |  |  |       return createAccount(args, handlers); | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-20 02:41:17 -08:00
										 |  |  |   , configureAsync: function (hargs) { | 
					
						
							|  |  |  |       var copy = merge(hargs, defaults); | 
					
						
							|  |  |  |       tplCopy(copy); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-21 10:27:57 -07:00
										 |  |  |       return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { | 
					
						
							|  |  |  |         copy.account = account; | 
					
						
							| 
									
										
										
										
											2016-08-04 18:49:35 -04:00
										 |  |  |         return backend.getOrCreateRenewal(copy); | 
					
						
							| 
									
										
										
										
											2015-12-20 02:41:17 -08:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 03:37:39 -08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return wrapped; | 
					
						
							|  |  |  | }; |