| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | /*! | 
					
						
							|  |  |  |  * letiny | 
					
						
							|  |  |  |  * Copyright(c) 2015 Anatol Sommer <anatol@anatol.at> | 
					
						
							|  |  |  |  * Some code used from https://github.com/letsencrypt/boulder/tree/master/test/js
 | 
					
						
							|  |  |  |  * MPL 2.0 | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-01 14:10:37 -04:00
										 |  |  | function _toStandardBase64(str) { | 
					
						
							|  |  |  |   var b64 = str.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, ""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   switch (b64.length % 4) { | 
					
						
							|  |  |  |     case 2: b64 += "=="; break; | 
					
						
							|  |  |  |     case 3: b64 += "="; break; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return b64; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  | module.exports.create = function (deps) { | 
					
						
							| 
									
										
										
										
											2015-12-16 03:32:25 +00:00
										 |  |  |   var request=deps.request; | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |   var Acme = deps.Acme; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |   var RSA = deps.RSA; | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-01 21:47:48 -04:00
										 |  |  |   // getCertificate // returns "pems", meaning "certs"
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |   function getCert(options, cb) { | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |     function bodyToError(res, body) { | 
					
						
							|  |  |  |       var err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!body) { | 
					
						
							|  |  |  |         err = new Error("[Error] letiny-core: no request body"); | 
					
						
							|  |  |  |         err.code = "E_NO_RESPONSE_BODY"; | 
					
						
							|  |  |  |         throw err; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if ('{' === body[0] || '{' === String.fromCharCode(body[0])) { | 
					
						
							|  |  |  |         try { | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |           body = JSON.parse(body.toString('utf8')); | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |         } catch(e) { | 
					
						
							|  |  |  |           err = new Error("[Error] letiny-core: body could not be parsed"); | 
					
						
							|  |  |  |           err.code = "E_BODY_PARSE"; | 
					
						
							|  |  |  |           err.description = body; | 
					
						
							|  |  |  |           throw err; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (Math.floor(res.statusCode / 100) !== 2) { | 
					
						
							|  |  |  |         err = new Error("[Error] letiny-core: not 200 ok"); | 
					
						
							|  |  |  |         err.code = "E_STATUS_CODE"; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |         err.type = body.type; | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |         err.description = body; | 
					
						
							|  |  |  |         err.detail = body.detail; | 
					
						
							| 
									
										
										
										
											2016-02-16 10:08:12 -07:00
										 |  |  |         console.error("TODO: modules which depend on this module should expose this error properly but since some of them don't, I expose it here directly:"); | 
					
						
							|  |  |  |         console.error(err.stack); | 
					
						
							|  |  |  |         console.error(body); | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |         throw err; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (body.type && body.detail) { | 
					
						
							|  |  |  |         err = new Error("[Error] letiny-core: " + body.detail); | 
					
						
							|  |  |  |         err.code = body.type; | 
					
						
							|  |  |  |         err.type = body.type; | 
					
						
							|  |  |  |         err.description = body.detail; | 
					
						
							|  |  |  |         err.detail = body.detail; | 
					
						
							|  |  |  |         throw err; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return body; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function nextDomain() { | 
					
						
							|  |  |  |       if (state.domains.length > 0) { | 
					
						
							|  |  |  |         getChallenges(state.domains.shift()); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         getCertificate(); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-15 15:05:20 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function getChallenges(domain) { | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       state.domain = domain; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 03:23:34 +00:00
										 |  |  |       state.acme.post(state.newAuthzUrl, { | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |         resource: 'new-authz', | 
					
						
							|  |  |  |         identifier: { | 
					
						
							|  |  |  |           type: 'dns', | 
					
						
							|  |  |  |           value: state.domain, | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |       }, function (err, res, body) { | 
					
						
							|  |  |  |         if (!err && res.body) { | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             body = bodyToError(res, body); | 
					
						
							|  |  |  |           } catch(e) { | 
					
						
							|  |  |  |             err = e; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |         getReadyToValidate(err, res, body); | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function getReadyToValidate(err, res, body) { | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       var links; | 
					
						
							|  |  |  |       var authz; | 
					
						
							|  |  |  |       var httpChallenges; | 
					
						
							|  |  |  |       var challenge; | 
					
						
							|  |  |  |       var thumbprint; | 
					
						
							|  |  |  |       var keyAuthorization; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 15:22:41 -05:00
										 |  |  |       function challengeDone(err) { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           console.error('[letiny-core] setChallenge Error:'); | 
					
						
							|  |  |  |           console.error(err && err.stack || err); | 
					
						
							|  |  |  |           ensureValidation(err, null, null, function () { | 
					
						
							|  |  |  |             options.removeChallenge(state.domain, challenge.token, function () { | 
					
						
							|  |  |  |               // ignore
 | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         state.acme.post(state.responseUrl, { | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |           resource: 'challenge', | 
					
						
							|  |  |  |           keyAuthorization: keyAuthorization | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         }, function(err, res, body) { | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |           if (!err && res.body) { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |               body = bodyToError(res, body); | 
					
						
							|  |  |  |             } catch(e) { | 
					
						
							|  |  |  |               err = e; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |           ensureValidation(err, res, body, function unlink() { | 
					
						
							|  |  |  |             options.removeChallenge(state.domain, challenge.token, function () { | 
					
						
							|  |  |  |               // ignore
 | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         return handleErr(err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (Math.floor(res.statusCode/100)!==2) { | 
					
						
							|  |  |  |         return handleErr(null, 'Authorization request failed ('+res.statusCode+')'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       links = Acme.parseLink(res.headers.link); | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |       if (!links || !('next' in links)) { | 
					
						
							|  |  |  |         return handleErr(err, 'Server didn\'t provide information to proceed (2)'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       state.authorizationUrl = res.headers.location; | 
					
						
							|  |  |  |       state.newCertUrl = links.next; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       authz = body; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       httpChallenges = authz.challenges.filter(function(x) { | 
					
						
							|  |  |  |         return x.type === options.challengeType; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       if (httpChallenges.length === 0) { | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |         return handleErr(null, 'Server didn\'t offer any challenge we can handle.'); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       challenge = httpChallenges[0]; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       thumbprint = RSA.thumbprint(state.accountKeypair); | 
					
						
							|  |  |  |       keyAuthorization = challenge.token + '.' + thumbprint; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       state.responseUrl = challenge.uri; | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       options.setChallenge(state.domain, challenge.token, keyAuthorization, challengeDone); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function ensureValidation(err, res, body, unlink) { | 
					
						
							| 
									
										
										
										
											2016-02-15 23:06:41 +09:00
										 |  |  |       var authz, challengesState; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       if (err || Math.floor(res.statusCode/100)!==2) { | 
					
						
							|  |  |  |         unlink(); | 
					
						
							| 
									
										
										
										
											2016-02-11 15:22:41 -05:00
										 |  |  |         return handleErr(err, 'Authorization status request failed (' | 
					
						
							|  |  |  |           + (res && res.statusCode || err.code || err.message || err) + ')'); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |       authz=body; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       if (authz.status==='pending') { | 
					
						
							|  |  |  |         setTimeout(function() { | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |           request({ | 
					
						
							|  |  |  |             method: 'GET' | 
					
						
							|  |  |  |           , url: state.authorizationUrl | 
					
						
							|  |  |  |           }, function(err, res, body) { | 
					
						
							|  |  |  |             if (!err && res.body) { | 
					
						
							|  |  |  |               try { | 
					
						
							|  |  |  |                 body = bodyToError(res, body); | 
					
						
							|  |  |  |               } catch(e) { | 
					
						
							|  |  |  |                 err = e; | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |             ensureValidation(err, res, body, unlink); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         }, 1000); | 
					
						
							|  |  |  |       } else if (authz.status==='valid') { | 
					
						
							|  |  |  |         log('Validating domain ... done'); | 
					
						
							|  |  |  |         state.validatedDomains.push(state.domain); | 
					
						
							|  |  |  |         state.validAuthorizationUrls.push(state.authorizationUrl); | 
					
						
							|  |  |  |         unlink(); | 
					
						
							|  |  |  |         nextDomain(); | 
					
						
							|  |  |  |       } else if (authz.status==='invalid') { | 
					
						
							|  |  |  |         unlink(); | 
					
						
							| 
									
										
										
										
											2016-02-16 01:41:02 +09:00
										 |  |  |         challengesState = (authz.challenges || []).map(function (challenge) { | 
					
						
							| 
									
										
										
										
											2016-02-15 23:06:41 +09:00
										 |  |  |           var result =  ' - ' + challenge.uri + ' [' + challenge.status + ']'; | 
					
						
							| 
									
										
										
										
											2016-02-16 01:30:05 +09:00
										 |  |  |           if (challenge.error) { | 
					
						
							|  |  |  |             result += '\n   ' + challenge.error.detail; | 
					
						
							| 
									
										
										
										
											2016-02-15 23:06:41 +09:00
										 |  |  |           } | 
					
						
							|  |  |  |           return result; | 
					
						
							|  |  |  |         }).join('\n'); | 
					
						
							|  |  |  |         return handleErr(null, | 
					
						
							|  |  |  |             'The CA was unable to validate the file you provisioned. ' | 
					
						
							|  |  |  |           + (authz.detail ? 'Details: ' + authz.detail : '') | 
					
						
							|  |  |  |           + (challengesState ? '\n' + challengesState : ''), body); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         unlink(); | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |         return handleErr(null, 'CA returned an authorization in an unexpected state' + authz.detail, authz); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function getCertificate() { | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       var csr=RSA.generateCsrWeb64(state.certKeypair, state.validatedDomains); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       log('Requesting certificate...'); | 
					
						
							| 
									
										
										
										
											2015-12-16 03:23:34 +00:00
										 |  |  |       state.acme.post(state.newCertUrl, { | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         resource:'new-cert', | 
					
						
							|  |  |  |         csr:csr, | 
					
						
							|  |  |  |         authorizations:state.validAuthorizationUrls | 
					
						
							| 
									
										
										
										
											2015-12-20 00:29:16 +00:00
										 |  |  |       }, function (err, res, body ) { | 
					
						
							|  |  |  |         if (!err && res.body) { | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             body = bodyToError(res, body); | 
					
						
							|  |  |  |           } catch(e) { | 
					
						
							|  |  |  |             err = e; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         downloadCertificate(err, res, body); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function downloadCertificate(err, res, body) { | 
					
						
							|  |  |  |       var links, certUrl; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |       if (err) { | 
					
						
							|  |  |  |         handleErr(err, 'Certificate request failed'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (Math.floor(res.statusCode/100)!==2) { | 
					
						
							|  |  |  |         err = new Error("invalid status code: " + res.statusCode); | 
					
						
							|  |  |  |         err.code = "E_STATUS_CODE"; | 
					
						
							|  |  |  |         err.description = body; | 
					
						
							|  |  |  |         handleErr(err); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       links=Acme.parseLink(res.headers.link); | 
					
						
							|  |  |  |       if (!links || !('up' in links)) { | 
					
						
							|  |  |  |         return handleErr(err, 'Failed to fetch issuer certificate'); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       log('Requesting certificate: done'); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       state.certificate=body; | 
					
						
							|  |  |  |       certUrl=res.headers.location; | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |       request({ | 
					
						
							|  |  |  |         method: 'GET' | 
					
						
							|  |  |  |       , url: certUrl | 
					
						
							|  |  |  |       , encoding: null | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       }, function(err, res, body) { | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |         if (!err) { | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             body = bodyToError(res, body); | 
					
						
							|  |  |  |           } catch(e) { | 
					
						
							|  |  |  |             err = e; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         if (err) { | 
					
						
							|  |  |  |           return handleErr(err, 'Failed to fetch cert from '+certUrl); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         if (res.statusCode!==200) { | 
					
						
							|  |  |  |           return handleErr(err, 'Failed to fetch cert from '+certUrl, res.body.toString()); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         if (body.toString()!==state.certificate.toString()) { | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |           return handleErr(null, 'Cert at '+certUrl+' did not match returned cert'); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         log('Successfully verified cert at '+certUrl); | 
					
						
							|  |  |  |         log('Requesting issuer certificate...'); | 
					
						
							|  |  |  |         request({ | 
					
						
							|  |  |  |           method: 'GET' | 
					
						
							|  |  |  |         , url: links.up | 
					
						
							|  |  |  |         , encoding: null | 
					
						
							|  |  |  |         }, function(err, res, body) { | 
					
						
							|  |  |  |           if (!err) { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |               body = bodyToError(res, body); | 
					
						
							|  |  |  |             } catch(e) { | 
					
						
							|  |  |  |               err = e; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (err || res.statusCode!==200) { | 
					
						
							|  |  |  |             return handleErr(err, 'Failed to fetch issuer certificate'); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |           state.caCertPem=certBufferToPem(body); | 
					
						
							| 
									
										
										
										
											2015-12-19 22:25:11 +00:00
										 |  |  |           log('Requesting issuer certificate: done'); | 
					
						
							|  |  |  |           done(); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function done() { | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |       var certPem; | 
					
						
							|  |  |  |       var privkeyPem; | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       try { | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |         certPem = certBufferToPem(state.certificate); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       } catch(e) { | 
					
						
							|  |  |  |         console.error(e.stack); | 
					
						
							|  |  |  |         //cb(new Error("Could not write output files. Please check permissions!"));
 | 
					
						
							|  |  |  |         handleErr(e, 'Could not write output files. Please check permissions!'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       privkeyPem = RSA.exportPrivatePem(state.certKeypair); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       cb(null, { | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |         cert: certPem | 
					
						
							| 
									
										
										
										
											2016-08-01 21:19:17 -04:00
										 |  |  |       // TODO privkey isn't necessary
 | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |       , privkey: privkeyPem | 
					
						
							| 
									
										
										
										
											2016-08-01 21:19:17 -04:00
										 |  |  |       , chain: state.caCertPem | 
					
						
							|  |  |  |       // TODO nix key, ca
 | 
					
						
							|  |  |  |       , key: privkeyPem | 
					
						
							| 
									
										
										
										
											2016-08-01 21:16:25 -04:00
										 |  |  |       , ca: state.caCertPem | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     function handleErr(err, text, info) { | 
					
						
							|  |  |  |       log(text, err, info); | 
					
						
							|  |  |  |       cb(err || new Error(text)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     var NOOP = function () {}; | 
					
						
							|  |  |  |     var log = options.debug ? console.log : NOOP; | 
					
						
							|  |  |  |     var state={ | 
					
						
							|  |  |  |       validatedDomains:[] | 
					
						
							|  |  |  |     , validAuthorizationUrls:[] | 
					
						
							|  |  |  |     , newAuthzUrl: options.newAuthzUrl | 
					
						
							|  |  |  |     , newCertUrl: options.newCertUrl | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |     if (!options.challengeType) { | 
					
						
							|  |  |  |       options.challengeType = 'http-01'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (-1 === [ 'http-01', 'tls-sni-01', 'dns-01' ].indexOf(options.challengeType)) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.challengeType '" + options.challengeType + "' is not yet supported")); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |     if (!options.newAuthzUrl) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.newAuthzUrl must be the authorization url")); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!options.newCertUrl) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.newCertUrl must be the new certificate url")); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-01 20:00:04 -04:00
										 |  |  |     if (!options.accountKeypair) { | 
					
						
							| 
									
										
										
										
											2016-08-01 20:07:54 -04:00
										 |  |  |       if (!options.accountPrivateKeyPem) { | 
					
						
							|  |  |  |         return handleErr(new Error("options.accountKeypair must be an object with `privateKeyPem` and/or `privateKeyJwk`")); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       console.warn("'accountPrivateKeyPem' is deprecated. Use options.accountKeypair.privateKeyPem instead."); | 
					
						
							|  |  |  |       options.accountKeypair = RSA.import({ privateKeyPem: options.accountPrivateKeyPem }); | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-08-01 20:00:04 -04:00
										 |  |  |     if (!options.domainKeypair) { | 
					
						
							| 
									
										
										
										
											2016-08-01 20:07:54 -04:00
										 |  |  |       if (!options.domainPrivateKeyPem) { | 
					
						
							|  |  |  |         return handleErr(new Error("options.domainKeypair must be an object with `privateKeyPem` and/or `privateKeyJwk`")); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       console.warn("'domainPrivateKeyPem' is deprecated. Use options.domainKeypair.privateKeyPem instead."); | 
					
						
							|  |  |  |       options.domainKeypair = RSA.import({ privateKeyPem: options.domainPrivateKeyPem }); | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (!options.setChallenge) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.setChallenge must be function(hostname, challengeKey, tokenValue, done) {}")); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!options.removeChallenge) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.removeChallenge must be function(hostname, challengeKey, done) {}")); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!(options.domains && options.domains.length)) { | 
					
						
							|  |  |  |       return handleErr(new Error("options.domains must be an array of domains such as ['example.com', 'www.example.com']")); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     state.domains = options.domains.slice(0); // copy array
 | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2016-08-01 20:00:04 -04:00
										 |  |  |       state.accountKeypair = options.accountKeypair; | 
					
						
							|  |  |  |       state.certKeypair = options.domainKeypair; | 
					
						
							| 
									
										
										
										
											2016-08-03 22:10:26 -04:00
										 |  |  |       state.acme = new Acme(state.accountKeypair); | 
					
						
							| 
									
										
										
										
											2016-08-01 05:53:50 -04:00
										 |  |  |     } catch(err) { | 
					
						
							|  |  |  |       return handleErr(err, 'Failed to parse privateKey'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nextDomain(); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |   function certBufferToPem(cert) { | 
					
						
							| 
									
										
										
										
											2016-08-01 14:10:37 -04:00
										 |  |  |     cert=_toStandardBase64(cert.toString('base64')); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |     cert=cert.match(/.{1,64}/g).join('\n'); | 
					
						
							|  |  |  |     return '-----BEGIN CERTIFICATE-----\n'+cert+'\n-----END CERTIFICATE-----'; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-12-15 14:33:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:13:07 +00:00
										 |  |  |   return getCert; | 
					
						
							|  |  |  | }; |