| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var C = module.exports; | 
					
						
							|  |  |  | var U = require('./utils.js'); | 
					
						
							|  |  |  | var CSR = require('@root/csr'); | 
					
						
							|  |  |  | var Enc = require('@root/encoding'); | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | var Keypairs = require('@root/keypairs'); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | var pending = {}; | 
					
						
							|  |  |  | var rawPending = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | // What the abbreviations mean
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // gnlkc => greenlock
 | 
					
						
							|  |  |  | // mconf => manager config
 | 
					
						
							|  |  |  | // db => greenlock store instance
 | 
					
						
							|  |  |  | // acme => instance of ACME.js
 | 
					
						
							|  |  |  | // chs => instances of challenges
 | 
					
						
							|  |  |  | // acc => account
 | 
					
						
							|  |  |  | // args => site / extra options
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | // Certificates
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | C._getOrOrder = function(gnlck, mconf, db, acme, chs, acc, args) { | 
					
						
							|  |  |  | 	var email = | 
					
						
							|  |  |  | 		args.subscriberEmail || | 
					
						
							|  |  |  | 		mconf.subscriberEmail || | 
					
						
							|  |  |  | 		gnlck._defaults.subscriberEmail; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var id = args.altnames.join(' '); | 
					
						
							|  |  |  | 	if (pending[id]) { | 
					
						
							|  |  |  | 		return pending[id]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pending[id] = C._rawGetOrOrder( | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 		gnlck, | 
					
						
							|  |  |  | 		mconf, | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		db, | 
					
						
							|  |  |  | 		acme, | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 		chs, | 
					
						
							|  |  |  | 		acc, | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		email, | 
					
						
							|  |  |  | 		args | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 		.then(function(pems) { | 
					
						
							|  |  |  | 			delete pending[id]; | 
					
						
							|  |  |  | 			return pems; | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.catch(function(err) { | 
					
						
							|  |  |  | 			delete pending[id]; | 
					
						
							|  |  |  | 			throw err; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pending[id]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Certificates
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | C._rawGetOrOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) { | 
					
						
							|  |  |  | 	return C._check(gnlck, mconf, db, args).then(function(pems) { | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		// Nice and fresh? We're done!
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 		if (pems) { | 
					
						
							|  |  |  | 			if (!C._isStale(gnlck, mconf, args, pems)) { | 
					
						
							|  |  |  | 				// return existing unexpired (although potentially stale) certificates when available
 | 
					
						
							|  |  |  | 				// there will be an additional .renewing property if the certs are being asynchronously renewed
 | 
					
						
							|  |  |  | 				//pems._type = 'current';
 | 
					
						
							|  |  |  | 				return pems; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 		// We're either starting fresh or freshening up...
 | 
					
						
							|  |  |  | 		var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args); | 
					
						
							|  |  |  | 		var evname = pems ? 'cert_renewal' : 'cert_issue'; | 
					
						
							|  |  |  | 		p.then(function(newPems) { | 
					
						
							|  |  |  | 			// notify in the background
 | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | 			var renewAt = C._renewWithStagger(gnlck, mconf, args, newPems); | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 			gnlck._notify(evname, { | 
					
						
							|  |  |  | 				renewAt: renewAt, | 
					
						
							|  |  |  | 				subject: args.subject, | 
					
						
							|  |  |  | 				altnames: args.altnames | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | 			gnlck._notify('_cert_issue', { | 
					
						
							|  |  |  | 				renewAt: renewAt, | 
					
						
							|  |  |  | 				subject: args.subject, | 
					
						
							|  |  |  | 				altnames: args.altnames, | 
					
						
							|  |  |  | 				pems: newPems | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 		}).catch(function(err) { | 
					
						
							|  |  |  | 			if (!err.context) { | 
					
						
							|  |  |  | 				err.context = evname; | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 			err.subject = args.subject; | 
					
						
							|  |  |  | 			err.altnames = args.altnames; | 
					
						
							|  |  |  | 			gnlck._notify('error', err); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// No choice but to hang tight and wait for it
 | 
					
						
							|  |  |  | 		if (!pems) { | 
					
						
							|  |  |  | 			return p; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 		// Wait it out
 | 
					
						
							|  |  |  | 		// TODO should we call this waitForRenewal?
 | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		if (args.waitForRenewal) { | 
					
						
							|  |  |  | 			return p; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:18:13 +00:00
										 |  |  | 		// Let the certs renew in the background
 | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		return pems; | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // we have another promise here because it the optional renewal
 | 
					
						
							|  |  |  | // may resolve in a different stack than the returned pems
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | C._rawOrder = function(gnlck, mconf, db, acme, chs, acc, email, args) { | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	var id = args.altnames | 
					
						
							|  |  |  | 		.slice(0) | 
					
						
							|  |  |  | 		.sort() | 
					
						
							|  |  |  | 		.join(' '); | 
					
						
							|  |  |  | 	if (rawPending[id]) { | 
					
						
							|  |  |  | 		return rawPending[id]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 	var keyType = | 
					
						
							|  |  |  | 		args.serverKeyType || | 
					
						
							|  |  |  | 		mconf.serverKeyType || | 
					
						
							|  |  |  | 		gnlck._defaults.serverKeyType; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	var query = { | 
					
						
							|  |  |  | 		subject: args.subject, | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 		certificate: args.certificate || {}, | 
					
						
							|  |  |  | 		directoryUrl: | 
					
						
							|  |  |  | 			args.directoryUrl || | 
					
						
							|  |  |  | 			mconf.directoryUrl || | 
					
						
							|  |  |  | 			gnlck._defaults.directoryUrl | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	}; | 
					
						
							|  |  |  | 	rawPending[id] = U._getOrCreateKeypair(db, args.subject, query, keyType) | 
					
						
							|  |  |  | 		.then(function(kresult) { | 
					
						
							|  |  |  | 			var serverKeypair = kresult.keypair; | 
					
						
							|  |  |  | 			var domains = args.altnames.slice(0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return CSR.csr({ | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 				jwk: serverKeypair.privateKeyJwk || serverKeypair.private, | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 				domains: domains, | 
					
						
							|  |  |  | 				encoding: 'der' | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 				.then(function(csrDer) { | 
					
						
							|  |  |  | 					// TODO let CSR support 'urlBase64' ?
 | 
					
						
							|  |  |  | 					return Enc.bufToUrlBase64(csrDer); | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				.then(function(csr) { | 
					
						
							| 
									
										
										
										
											2019-10-28 02:25:32 -06:00
										 |  |  | 					function notify(ev, opts) { | 
					
						
							|  |  |  | 						gnlck._notify(ev, opts); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 					} | 
					
						
							|  |  |  | 					var certReq = { | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 						debug: args.debug || gnlck._defaults.debug, | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 						challenges: chs, | 
					
						
							|  |  |  | 						account: acc, // only used if accounts.key.kid exists
 | 
					
						
							|  |  |  | 						accountKey: | 
					
						
							|  |  |  | 							acc.keypair.privateKeyJwk || acc.keypair.private, | 
					
						
							|  |  |  | 						keypair: acc.keypair, // TODO
 | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 						csr: csr, | 
					
						
							|  |  |  | 						domains: domains, // because ACME.js v3 uses `domains` still, actually
 | 
					
						
							|  |  |  | 						onChallengeStatus: notify, | 
					
						
							|  |  |  | 						notify: notify // TODO
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// TODO handle this in acme-v2
 | 
					
						
							|  |  |  | 						//subject: args.subject,
 | 
					
						
							|  |  |  | 						//altnames: args.altnames.slice(0),
 | 
					
						
							|  |  |  | 					}; | 
					
						
							|  |  |  | 					return acme.certificates | 
					
						
							|  |  |  | 						.create(certReq) | 
					
						
							|  |  |  | 						.then(U._attachCertInfo); | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				.then(function(pems) { | 
					
						
							|  |  |  | 					if (kresult.exists) { | 
					
						
							|  |  |  | 						return pems; | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2019-10-20 03:17:19 -06:00
										 |  |  | 					query.keypair = serverKeypair; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 					return db.setKeypair(query, serverKeypair).then(function() { | 
					
						
							|  |  |  | 						return pems; | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.then(function(pems) { | 
					
						
							|  |  |  | 			// TODO put this in the docs
 | 
					
						
							|  |  |  | 			// { cert, chain, privkey, subject, altnames, issuedAt, expiresAt }
 | 
					
						
							|  |  |  | 			// Note: the query has been updated
 | 
					
						
							|  |  |  | 			query.pems = pems; | 
					
						
							|  |  |  | 			return db.set(query); | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.then(function() { | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 			return C._check(gnlck, mconf, db, args); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 		.then(function(bundle) { | 
					
						
							|  |  |  | 			// TODO notify Manager
 | 
					
						
							|  |  |  | 			delete rawPending[id]; | 
					
						
							|  |  |  | 			return bundle; | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.catch(function(err) { | 
					
						
							|  |  |  | 			// Todo notify manager
 | 
					
						
							|  |  |  | 			delete rawPending[id]; | 
					
						
							|  |  |  | 			throw err; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rawPending[id]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // returns pems, if they exist
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | C._check = function(gnlck, mconf, db, args) { | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	var query = { | 
					
						
							|  |  |  | 		subject: args.subject, | 
					
						
							|  |  |  | 		// may contain certificate.id
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 		certificate: args.certificate, | 
					
						
							|  |  |  | 		directoryUrl: | 
					
						
							|  |  |  | 			args.directoryUrl || | 
					
						
							|  |  |  | 			mconf.directoryUrl || | 
					
						
							|  |  |  | 			gnlck._defaults.directoryUrl | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	}; | 
					
						
							|  |  |  | 	return db.check(query).then(function(pems) { | 
					
						
							|  |  |  | 		if (!pems) { | 
					
						
							|  |  |  | 			return null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pems = U._attachCertInfo(pems); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// For eager management
 | 
					
						
							|  |  |  | 		if (args.subject && !U._certHasDomain(pems, args.subject)) { | 
					
						
							|  |  |  | 			// TODO report error, but continue the process as with no cert
 | 
					
						
							|  |  |  | 			return null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// For lazy SNI requests
 | 
					
						
							|  |  |  | 		if (args.domain && !U._certHasDomain(pems, args.domain)) { | 
					
						
							|  |  |  | 			// TODO report error, but continue the process as with no cert
 | 
					
						
							|  |  |  | 			return null; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return U._getKeypair(db, args.subject, query) | 
					
						
							|  |  |  | 			.then(function(keypair) { | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 				return Keypairs.export({ | 
					
						
							|  |  |  | 					jwk: keypair.privateKeyJwk || keypair.private, | 
					
						
							|  |  |  | 					encoding: 'pem' | 
					
						
							|  |  |  | 				}).then(function(pem) { | 
					
						
							|  |  |  | 					pems.privkey = pem; | 
					
						
							|  |  |  | 					return pems; | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 			.catch(function() { | 
					
						
							|  |  |  | 				// TODO report error, but continue the process as with no cert
 | 
					
						
							|  |  |  | 				return null; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Certificates
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | C._isStale = function(gnlck, mconf, args, pems) { | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	if (args.duplicate) { | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 	var renewAt = C._renewableAt(gnlck, mconf, args, pems); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (Date.now() >= renewAt) { | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | C._renewWithStagger = function(gnlck, mconf, args, pems) { | 
					
						
							|  |  |  | 	var renewOffset = C._renewOffset(gnlck, mconf, args, pems); | 
					
						
							|  |  |  | 	var renewStagger; | 
					
						
							|  |  |  | 	try { | 
					
						
							|  |  |  | 		renewStagger = U._parseDuration( | 
					
						
							|  |  |  | 			args.renewStagger || | 
					
						
							|  |  |  | 				mconf.renewStagger || | 
					
						
							|  |  |  | 				gnlck._defaults.renewStagger || | 
					
						
							|  |  |  | 				0 | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} catch (e) { | 
					
						
							|  |  |  | 		renewStagger = U._parseDuration(gnlck._defaults.renewStagger); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO check this beforehand
 | 
					
						
							|  |  |  | 	if (!args.force && renewStagger / renewOffset >= 0.5) { | 
					
						
							|  |  |  | 		renewStagger = renewOffset * 0.1; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | 	if (renewOffset > 0) { | 
					
						
							|  |  |  | 		// stagger forward, away from issued at
 | 
					
						
							|  |  |  | 		return Math.round( | 
					
						
							|  |  |  | 			pems.issuedAt + renewOffset + Math.random() * renewStagger | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// stagger backward, toward issued at
 | 
					
						
							|  |  |  | 	return Math.round( | 
					
						
							|  |  |  | 		pems.expiresAt + renewOffset - Math.random() * renewStagger | 
					
						
							|  |  |  | 	); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | C._renewOffset = function(gnlck, mconf, args, pems) { | 
					
						
							|  |  |  | 	var renewOffset = U._parseDuration( | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | 		args.renewOffset || | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | 			mconf.renewOffset || | 
					
						
							|  |  |  | 			gnlck._defaults.renewOffset || | 
					
						
							|  |  |  | 			0 | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	var week = 1000 * 60 * 60 * 24 * 6; | 
					
						
							|  |  |  | 	if (!args.force && Math.abs(renewOffset) < week) { | 
					
						
							|  |  |  | 		throw new Error( | 
					
						
							|  |  |  | 			'developer error: `renewOffset` should always be at least a week, use `force` to not safety-check renewOffset' | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-29 06:19:26 +00:00
										 |  |  | 	return renewOffset; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | C._renewableAt = function(gnlck, mconf, args, pems) { | 
					
						
							|  |  |  | 	if (args.renewAt) { | 
					
						
							|  |  |  | 		return args.renewAt; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var renewOffset = C._renewOffset(gnlck, mconf, args, pems); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (renewOffset > 0) { | 
					
						
							|  |  |  | 		return pems.issuedAt + renewOffset; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pems.expiresAt + renewOffset; | 
					
						
							|  |  |  | }; |