forked from root/acme.js
		
	WIP gets a cert... nice!
This commit is contained in:
		
							parent
							
								
									e75c503356
								
							
						
					
					
						commit
						24c3633d75
					
				
							
								
								
									
										703
									
								
								lib/acme.js
									
									
									
									
									
								
							
							
						
						
									
										703
									
								
								lib/acme.js
									
									
									
									
									
								
							@ -165,7 +165,7 @@ ACME._registerAccount = function(me, options) {
 | 
				
			|||||||
					} else if (options.email) {
 | 
										} else if (options.email) {
 | 
				
			||||||
						contact = ['mailto:' + options.email];
 | 
											contact = ['mailto:' + options.email];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					var body = {
 | 
										var accountRequest = {
 | 
				
			||||||
						termsOfServiceAgreed: tosUrl === me._tos,
 | 
											termsOfServiceAgreed: tosUrl === me._tos,
 | 
				
			||||||
						onlyReturnExisting: false,
 | 
											onlyReturnExisting: false,
 | 
				
			||||||
						contact: contact
 | 
											contact: contact
 | 
				
			||||||
@ -182,14 +182,14 @@ ACME._registerAccount = function(me, options) {
 | 
				
			|||||||
							},
 | 
												},
 | 
				
			||||||
							payload: Enc.strToBuf(JSON.stringify(pair.public))
 | 
												payload: Enc.strToBuf(JSON.stringify(pair.public))
 | 
				
			||||||
						}).then(function(jws) {
 | 
											}).then(function(jws) {
 | 
				
			||||||
							body.externalAccountBinding = jws;
 | 
												accountRequest.externalAccountBinding = jws;
 | 
				
			||||||
							return body;
 | 
												return accountRequest;
 | 
				
			||||||
						});
 | 
											});
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						pExt = Promise.resolve(body);
 | 
											pExt = Promise.resolve(accountRequest);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return pExt.then(function(body) {
 | 
										return pExt.then(function(accountRequest) {
 | 
				
			||||||
						var payload = JSON.stringify(body);
 | 
											var payload = JSON.stringify(accountRequest);
 | 
				
			||||||
						return ACME._jwsRequest(me, {
 | 
											return ACME._jwsRequest(me, {
 | 
				
			||||||
							options: options,
 | 
												options: options,
 | 
				
			||||||
							url: me._directoryUrls.newAccount,
 | 
												url: me._directoryUrls.newAccount,
 | 
				
			||||||
@ -199,10 +199,20 @@ ACME._registerAccount = function(me, options) {
 | 
				
			|||||||
							.then(function(resp) {
 | 
												.then(function(resp) {
 | 
				
			||||||
								var account = resp.body;
 | 
													var account = resp.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								if (2 !== Math.floor(resp.statusCode / 100)) {
 | 
													if (
 | 
				
			||||||
 | 
														resp.statusCode < 200 ||
 | 
				
			||||||
 | 
														resp.statusCode >= 300
 | 
				
			||||||
 | 
													) {
 | 
				
			||||||
 | 
														if ('string' !== typeof account) {
 | 
				
			||||||
 | 
															account = JSON.stringify(account);
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
									throw new Error(
 | 
														throw new Error(
 | 
				
			||||||
										'account error: ' +
 | 
															'account error: ' +
 | 
				
			||||||
											JSON.stringify(resp.body)
 | 
																resp.statusCode +
 | 
				
			||||||
 | 
																' ' +
 | 
				
			||||||
 | 
																account +
 | 
				
			||||||
 | 
																'\n' +
 | 
				
			||||||
 | 
																JSON.stringify(accountRequest)
 | 
				
			||||||
									);
 | 
														);
 | 
				
			||||||
								}
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -344,7 +354,24 @@ ACME._testChallengeOptions = function() {
 | 
				
			|||||||
	];
 | 
						];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
ACME._testChallenges = function(me, options) {
 | 
					ACME._testChallenges = function(me, options) {
 | 
				
			||||||
 | 
						console.log('[debug] testChallenges');
 | 
				
			||||||
	var CHECK_DELAY = 0;
 | 
						var CHECK_DELAY = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// memoized so that it doesn't run until it's first called
 | 
				
			||||||
 | 
						var getThumbnail = function() {
 | 
				
			||||||
 | 
							var thumbPromise = ACME._importKeypair(me, options.accountKeypair).then(
 | 
				
			||||||
 | 
								function(pair) {
 | 
				
			||||||
 | 
									return me.Keypairs.thumbprint({
 | 
				
			||||||
 | 
										jwk: pair.public
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							getThumbnail = function() {
 | 
				
			||||||
 | 
								return thumbPromise;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							return thumbPromise;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Promise.all(
 | 
						return Promise.all(
 | 
				
			||||||
		options.domains.map(function(identifierValue) {
 | 
							options.domains.map(function(identifierValue) {
 | 
				
			||||||
			// TODO we really only need one to pass, not all to pass
 | 
								// TODO we really only need one to pass, not all to pass
 | 
				
			||||||
@ -389,10 +416,11 @@ ACME._testChallenges = function(me, options) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			if ('dns-01' === challenge.type) {
 | 
								if ('dns-01' === challenge.type) {
 | 
				
			||||||
				// Give the nameservers a moment to propagate
 | 
									// Give the nameservers a moment to propagate
 | 
				
			||||||
				CHECK_DELAY = 1.5 * 1000;
 | 
									// TODO get this value from the plugin
 | 
				
			||||||
 | 
									CHECK_DELAY = 7 * 1000;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return Promise.resolve().then(function() {
 | 
								return getThumbnail().then(function(accountKeyThumb) {
 | 
				
			||||||
				var results = {
 | 
									var results = {
 | 
				
			||||||
					identifier: {
 | 
										identifier: {
 | 
				
			||||||
						type: 'dns',
 | 
											type: 'dns',
 | 
				
			||||||
@ -409,6 +437,7 @@ ACME._testChallenges = function(me, options) {
 | 
				
			|||||||
				return ACME._challengeToAuth(
 | 
									return ACME._challengeToAuth(
 | 
				
			||||||
					me,
 | 
										me,
 | 
				
			||||||
					options,
 | 
										options,
 | 
				
			||||||
 | 
										accountKeyThumb,
 | 
				
			||||||
					results,
 | 
										results,
 | 
				
			||||||
					challenge,
 | 
										challenge,
 | 
				
			||||||
					dryrun
 | 
										dryrun
 | 
				
			||||||
@ -460,7 +489,14 @@ ACME._chooseChallenge = function(options, results) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return challenge;
 | 
						return challenge;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
					ACME._challengeToAuth = function(
 | 
				
			||||||
 | 
						me,
 | 
				
			||||||
 | 
						options,
 | 
				
			||||||
 | 
						accountKeyThumb,
 | 
				
			||||||
 | 
						request,
 | 
				
			||||||
 | 
						challenge,
 | 
				
			||||||
 | 
						dryrun
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
	// we don't poison the dns cache with our dummy request
 | 
						// we don't poison the dns cache with our dummy request
 | 
				
			||||||
	var dnsPrefix = ACME.challengePrefixes['dns-01'];
 | 
						var dnsPrefix = ACME.challengePrefixes['dns-01'];
 | 
				
			||||||
	if (dryrun) {
 | 
						if (dryrun) {
 | 
				
			||||||
@ -486,38 +522,58 @@ ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
				
			|||||||
		auth[key] = challenge[key];
 | 
							auth[key] = challenge[key];
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var zone = pluckZone(options.zonenames || [], auth.identifier.value);
 | 
				
			||||||
	// batteries-included helpers
 | 
						// batteries-included helpers
 | 
				
			||||||
	auth.hostname = auth.identifier.value;
 | 
						auth.hostname = auth.identifier.value;
 | 
				
			||||||
	// because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | 
						// because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | 
				
			||||||
	auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
 | 
						auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
 | 
				
			||||||
	return ACME._importKeypair(me, options.accountKeypair).then(function(pair) {
 | 
						// we must accept JWKs that we didn't generate and we can't guarantee
 | 
				
			||||||
		return me.Keypairs.thumbprint({ jwk: pair.public }).then(function(
 | 
						// that they properly set kid to thumbnail (especially since ACME doesn't do this)
 | 
				
			||||||
			thumb
 | 
						// so we have to regenerate it every time we need it, which is quite often
 | 
				
			||||||
		) {
 | 
						auth.thumbprint = accountKeyThumb;
 | 
				
			||||||
			auth.thumbprint = thumb;
 | 
						//   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | 
				
			||||||
			//   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | 
						auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
				
			||||||
			auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
						// conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | 
				
			||||||
			// conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | 
						// TODO auth.http01Url ?
 | 
				
			||||||
			// TODO auth.http01Url ?
 | 
						auth.challengeUrl =
 | 
				
			||||||
			auth.challengeUrl =
 | 
							'http://' +
 | 
				
			||||||
				'http://' +
 | 
							auth.identifier.value +
 | 
				
			||||||
				auth.identifier.value +
 | 
							ACME.challengePrefixes['http-01'] +
 | 
				
			||||||
				ACME.challengePrefixes['http-01'] +
 | 
							'/' +
 | 
				
			||||||
				'/' +
 | 
							auth.token;
 | 
				
			||||||
				auth.token;
 | 
						auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
 | 
				
			||||||
			auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return sha2
 | 
						// Always calculate dnsAuthorization because we
 | 
				
			||||||
				.sum(256, auth.keyAuthorization)
 | 
						// may need to present to the user for confirmation / instruction
 | 
				
			||||||
				.then(function(hash) {
 | 
						// _as part of_ the decision making process
 | 
				
			||||||
					return Enc.bufToUrlBase64(new Uint8Array(hash));
 | 
						return sha2
 | 
				
			||||||
				})
 | 
							.sum(256, auth.keyAuthorization)
 | 
				
			||||||
				.then(function(hash64) {
 | 
							.then(function(hash) {
 | 
				
			||||||
					auth.dnsAuthorization = hash64;
 | 
								return Enc.bufToUrlBase64(new Uint8Array(hash));
 | 
				
			||||||
					return auth;
 | 
							})
 | 
				
			||||||
				});
 | 
							.then(function(hash64) {
 | 
				
			||||||
 | 
								auth.dnsAuthorization = hash64;
 | 
				
			||||||
 | 
								if (zone) {
 | 
				
			||||||
 | 
									auth.dnsZone = zone;
 | 
				
			||||||
 | 
									auth.dnsPrefix = auth.dnsHost
 | 
				
			||||||
 | 
										.replace(newZoneRegExp(zone), '')
 | 
				
			||||||
 | 
										.replace(/\.$/, '');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// For backwards compat with the v2.7 plugins
 | 
				
			||||||
 | 
								auth.challenge = auth;
 | 
				
			||||||
 | 
								// TODO can we use just { challenge: auth }?
 | 
				
			||||||
 | 
								auth.request = function() {
 | 
				
			||||||
 | 
									// TODO see https://git.rootprojects.org/root/acme.js/issues/###
 | 
				
			||||||
 | 
									console.warn(
 | 
				
			||||||
 | 
										"[warn] deprecated use of request on '" +
 | 
				
			||||||
 | 
											auth.type +
 | 
				
			||||||
 | 
											"' challenge object. Receive from challenger.init() instead."
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
									me.request.apply(null, arguments);
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								return auth;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ACME._untame = function(name, wild) {
 | 
					ACME._untame = function(name, wild) {
 | 
				
			||||||
@ -597,7 +653,7 @@ ACME._postChallenge = function(me, options, auth) {
 | 
				
			|||||||
			.then(function(resp) {
 | 
								.then(function(resp) {
 | 
				
			||||||
				if ('processing' === resp.body.status) {
 | 
									if ('processing' === resp.body.status) {
 | 
				
			||||||
					if (me.debug) {
 | 
										if (me.debug) {
 | 
				
			||||||
						console.debug('poll: again');
 | 
											console.debug('poll: again', auth.url);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return ACME._wait(RETRY_INTERVAL).then(pollStatus);
 | 
										return ACME._wait(RETRY_INTERVAL).then(pollStatus);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -610,14 +666,14 @@ ACME._postChallenge = function(me, options, auth) {
 | 
				
			|||||||
							.then(respondToChallenge);
 | 
												.then(respondToChallenge);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					if (me.debug) {
 | 
										if (me.debug) {
 | 
				
			||||||
						console.debug('poll: again');
 | 
											console.debug('poll: again', auth.url);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
 | 
										return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if ('valid' === resp.body.status) {
 | 
									if ('valid' === resp.body.status) {
 | 
				
			||||||
					if (me.debug) {
 | 
										if (me.debug) {
 | 
				
			||||||
						console.debug('poll: valid');
 | 
											console.debug('VALID !!!!!!!!!!!!!!!! poll: valid');
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					try {
 | 
										try {
 | 
				
			||||||
@ -637,7 +693,8 @@ ACME._postChallenge = function(me, options, auth) {
 | 
				
			|||||||
						"[acme-v2] (E_STATE_INVALID) challenge state for '" +
 | 
											"[acme-v2] (E_STATE_INVALID) challenge state for '" +
 | 
				
			||||||
						altname +
 | 
											altname +
 | 
				
			||||||
						"': '" +
 | 
											"': '" +
 | 
				
			||||||
						resp.body.status +
 | 
											//resp.body.status +
 | 
				
			||||||
 | 
											JSON.stringify(resp.body) +
 | 
				
			||||||
						"'";
 | 
											"'";
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					errmsg =
 | 
										errmsg =
 | 
				
			||||||
@ -675,17 +732,20 @@ ACME._postChallenge = function(me, options, auth) {
 | 
				
			|||||||
	return respondToChallenge();
 | 
						return respondToChallenge();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
ACME._setChallenge = function(me, options, auth) {
 | 
					ACME._setChallenge = function(me, options, auth) {
 | 
				
			||||||
	return new Promise(function(resolve, reject) {
 | 
						return Promise.resolve().then(function() {
 | 
				
			||||||
		var challengers = options.challenges || {};
 | 
							var challengers = options.challenges || {};
 | 
				
			||||||
		var challenger =
 | 
							var challenger = challengers[auth.type] && challengers[auth.type].set;
 | 
				
			||||||
			(challengers[auth.type] && challengers[auth.type].set) ||
 | 
							if (!challenger) {
 | 
				
			||||||
			options.setChallenge;
 | 
								throw new Error(
 | 
				
			||||||
		try {
 | 
									"options.challenges did not have a valid entry for '" +
 | 
				
			||||||
			if (1 === challenger.length) {
 | 
										auth.type +
 | 
				
			||||||
				challenger(auth)
 | 
										"'"
 | 
				
			||||||
					.then(resolve)
 | 
								);
 | 
				
			||||||
					.catch(reject);
 | 
							}
 | 
				
			||||||
			} else if (2 === challenger.length) {
 | 
							if (1 === challenger.length) {
 | 
				
			||||||
 | 
								return Promise.resolve(challenger(auth));
 | 
				
			||||||
 | 
							} else if (2 === challenger.length) {
 | 
				
			||||||
 | 
								return new Promise(function(resolve, reject) {
 | 
				
			||||||
				challenger(auth, function(err) {
 | 
									challenger(auth, function(err) {
 | 
				
			||||||
					if (err) {
 | 
										if (err) {
 | 
				
			||||||
						reject(err);
 | 
											reject(err);
 | 
				
			||||||
@ -693,45 +753,12 @@ ACME._setChallenge = function(me, options, auth) {
 | 
				
			|||||||
						resolve();
 | 
											resolve();
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else {
 | 
								});
 | 
				
			||||||
				// TODO remove this old backwards-compat
 | 
							} else {
 | 
				
			||||||
				var challengeCb = function(err) {
 | 
								throw new Error(
 | 
				
			||||||
					if (err) {
 | 
									"Bad function signature for '" + auth.type + "' challenge.set()"
 | 
				
			||||||
						reject(err);
 | 
								);
 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						resolve();
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
				// for backwards compat adding extra keys without changing params length
 | 
					 | 
				
			||||||
				Object.keys(auth).forEach(function(key) {
 | 
					 | 
				
			||||||
					challengeCb[key] = auth[key];
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
				if (!ACME._setChallengeWarn) {
 | 
					 | 
				
			||||||
					console.warn(
 | 
					 | 
				
			||||||
						'Please update to acme-v2 setChallenge(options) <Promise> or setChallenge(options, cb).'
 | 
					 | 
				
			||||||
					);
 | 
					 | 
				
			||||||
					console.warn(
 | 
					 | 
				
			||||||
						"The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."
 | 
					 | 
				
			||||||
					);
 | 
					 | 
				
			||||||
					ACME._setChallengeWarn = true;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				challenger(
 | 
					 | 
				
			||||||
					auth.identifier.value,
 | 
					 | 
				
			||||||
					auth.token,
 | 
					 | 
				
			||||||
					auth.keyAuthorization,
 | 
					 | 
				
			||||||
					challengeCb
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} catch (e) {
 | 
					 | 
				
			||||||
			reject(e);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}).then(function() {
 | 
					 | 
				
			||||||
		// TODO: Do we still need this delay? Or shall we leave it to plugins to account for themselves?
 | 
					 | 
				
			||||||
		var DELAY = me.setChallengeWait || 500;
 | 
					 | 
				
			||||||
		if (me.debug) {
 | 
					 | 
				
			||||||
			console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return ACME._wait(DELAY);
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
ACME._finalizeOrder = function(me, options, validatedDomains) {
 | 
					ACME._finalizeOrder = function(me, options, validatedDomains) {
 | 
				
			||||||
@ -943,170 +970,234 @@ ACME._getCertificate = function(me, options) {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Do a little dry-run / self-test
 | 
						// TODO Promise.all()?
 | 
				
			||||||
	return ACME._testChallenges(me, options).then(function() {
 | 
						Object.keys(options.challenges).forEach(function(key) {
 | 
				
			||||||
		if (me.debug) {
 | 
							var presenter = options.challenges[key];
 | 
				
			||||||
			console.debug('[acme-v2] certificates.create');
 | 
							if ('function' === typeof presenter.init && !presenter._initialized) {
 | 
				
			||||||
 | 
								presenter._initialized = true;
 | 
				
			||||||
 | 
								return ACME._depInit(me, presenter);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		var body = {
 | 
						});
 | 
				
			||||||
			// raw wildcard syntax MUST be used here
 | 
					
 | 
				
			||||||
			identifiers: options.domains
 | 
						var promiseZones;
 | 
				
			||||||
				.sort(function(a, b) {
 | 
						if (options.challenges['dns-01']) {
 | 
				
			||||||
					// the first in the list will be the subject of the certificate, I believe (and hope)
 | 
							// a little bit of random to ensure that getZones()
 | 
				
			||||||
					if (!options.subject) {
 | 
							// actually returns the zones and not the hosts as zones
 | 
				
			||||||
 | 
							var dnsHosts = options.domains.map(function(d) {
 | 
				
			||||||
 | 
								var rnd = require('crypto')
 | 
				
			||||||
 | 
									.randomBytes(2)
 | 
				
			||||||
 | 
									.toString('hex');
 | 
				
			||||||
 | 
								return rnd + '.' + d;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							promiseZones = ACME._getZones(
 | 
				
			||||||
 | 
								me,
 | 
				
			||||||
 | 
								options.challenges['dns-01'],
 | 
				
			||||||
 | 
								dnsHosts
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							promiseZones = Promise.resolve([]);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return promiseZones
 | 
				
			||||||
 | 
							.then(function(zonenames) {
 | 
				
			||||||
 | 
								options.zonenames = zonenames;
 | 
				
			||||||
 | 
								// Do a little dry-run / self-test
 | 
				
			||||||
 | 
								return ACME._testChallenges(me, options);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.then(function() {
 | 
				
			||||||
 | 
								if (me.debug) {
 | 
				
			||||||
 | 
									console.debug('[acme-v2] certificates.create');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var certOrder = {
 | 
				
			||||||
 | 
									// raw wildcard syntax MUST be used here
 | 
				
			||||||
 | 
									identifiers: options.domains
 | 
				
			||||||
 | 
										.sort(function(a, b) {
 | 
				
			||||||
 | 
											// the first in the list will be the subject of the certificate, I believe (and hope)
 | 
				
			||||||
 | 
											if (!options.subject) {
 | 
				
			||||||
 | 
												return 0;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (options.subject === a) {
 | 
				
			||||||
 | 
												return -1;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (options.subject === b) {
 | 
				
			||||||
 | 
												return 1;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
						return 0;
 | 
											return 0;
 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					if (options.subject === a) {
 | 
					 | 
				
			||||||
						return -1;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					if (options.subject === b) {
 | 
					 | 
				
			||||||
						return 1;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					return 0;
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
				.map(function(hostname) {
 | 
					 | 
				
			||||||
					return { type: 'dns', value: hostname };
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
			//, "notBefore": "2016-01-01T00:00:00Z"
 | 
					 | 
				
			||||||
			//, "notAfter": "2016-01-08T00:00:00Z"
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var payload = JSON.stringify(body);
 | 
					 | 
				
			||||||
		if (me.debug) {
 | 
					 | 
				
			||||||
			console.debug('\n[DEBUG] newOrder\n');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return ACME._jwsRequest(me, {
 | 
					 | 
				
			||||||
			options: options,
 | 
					 | 
				
			||||||
			url: me._directoryUrls.newOrder,
 | 
					 | 
				
			||||||
			protected: { kid: options._kid },
 | 
					 | 
				
			||||||
			payload: Enc.strToBuf(payload)
 | 
					 | 
				
			||||||
		}).then(function(resp) {
 | 
					 | 
				
			||||||
			var location = resp.headers.location;
 | 
					 | 
				
			||||||
			var setAuths;
 | 
					 | 
				
			||||||
			var validAuths = [];
 | 
					 | 
				
			||||||
			var auths = [];
 | 
					 | 
				
			||||||
			if (me.debug) {
 | 
					 | 
				
			||||||
				console.debug('[ordered]', location);
 | 
					 | 
				
			||||||
			} // the account id url
 | 
					 | 
				
			||||||
			if (me.debug) {
 | 
					 | 
				
			||||||
				console.debug(resp);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			options._authorizations = resp.body.authorizations;
 | 
					 | 
				
			||||||
			options._order = location;
 | 
					 | 
				
			||||||
			options._finalize = resp.body.finalize;
 | 
					 | 
				
			||||||
			//if (me.debug) console.debug('[DEBUG] finalize:', options._finalize); return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!options._authorizations) {
 | 
					 | 
				
			||||||
				return Promise.reject(
 | 
					 | 
				
			||||||
					new Error(
 | 
					 | 
				
			||||||
						"[acme-v2.js] authorizations were not fetched for '" +
 | 
					 | 
				
			||||||
							options.domains.join() +
 | 
					 | 
				
			||||||
							"':\n" +
 | 
					 | 
				
			||||||
							JSON.stringify(resp.body)
 | 
					 | 
				
			||||||
					)
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if (me.debug) {
 | 
					 | 
				
			||||||
				console.debug('[acme-v2] POST newOrder has authorizations');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			setAuths = options._authorizations.slice(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			function setNext() {
 | 
					 | 
				
			||||||
				var authUrl = setAuths.shift();
 | 
					 | 
				
			||||||
				if (!authUrl) {
 | 
					 | 
				
			||||||
					return;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return ACME._getChallenges(me, options, authUrl).then(function(
 | 
					 | 
				
			||||||
					results
 | 
					 | 
				
			||||||
				) {
 | 
					 | 
				
			||||||
					// var domain = options.domains[i]; // results.identifier.value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					// If it's already valid, we're golden it regardless
 | 
					 | 
				
			||||||
					if (
 | 
					 | 
				
			||||||
						results.challenges.some(function(ch) {
 | 
					 | 
				
			||||||
							return 'valid' === ch.status;
 | 
					 | 
				
			||||||
						})
 | 
					 | 
				
			||||||
					) {
 | 
					 | 
				
			||||||
						return setNext();
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					var challenge = ACME._chooseChallenge(options, results);
 | 
					 | 
				
			||||||
					if (!challenge) {
 | 
					 | 
				
			||||||
						// For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | 
					 | 
				
			||||||
						return Promise.reject(
 | 
					 | 
				
			||||||
							new Error(
 | 
					 | 
				
			||||||
								"Server didn't offer any challenge we can handle for '" +
 | 
					 | 
				
			||||||
									options.domains.join() +
 | 
					 | 
				
			||||||
									"'."
 | 
					 | 
				
			||||||
							)
 | 
					 | 
				
			||||||
						);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					return ACME._challengeToAuth(
 | 
					 | 
				
			||||||
						me,
 | 
					 | 
				
			||||||
						options,
 | 
					 | 
				
			||||||
						results,
 | 
					 | 
				
			||||||
						challenge,
 | 
					 | 
				
			||||||
						false
 | 
					 | 
				
			||||||
					).then(function(auth) {
 | 
					 | 
				
			||||||
						auths.push(auth);
 | 
					 | 
				
			||||||
						return ACME._setChallenge(me, options, auth).then(
 | 
					 | 
				
			||||||
							setNext
 | 
					 | 
				
			||||||
						);
 | 
					 | 
				
			||||||
					});
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			function checkNext() {
 | 
					 | 
				
			||||||
				var auth = auths.shift();
 | 
					 | 
				
			||||||
				if (!auth) {
 | 
					 | 
				
			||||||
					return;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (!me._canUse[auth.type] || me.skipChallengeTest) {
 | 
					 | 
				
			||||||
					// not so much "valid" as "not invalid"
 | 
					 | 
				
			||||||
					// but in this case we can't confirm either way
 | 
					 | 
				
			||||||
					validAuths.push(auth);
 | 
					 | 
				
			||||||
					return Promise.resolve();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return ACME.challengeTests[auth.type](me, auth)
 | 
					 | 
				
			||||||
					.then(function() {
 | 
					 | 
				
			||||||
						validAuths.push(auth);
 | 
					 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
					.then(checkNext);
 | 
										.map(function(hostname) {
 | 
				
			||||||
			}
 | 
											return { type: 'dns', value: hostname };
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									//, "notBefore": "2016-01-01T00:00:00Z"
 | 
				
			||||||
 | 
									//, "notAfter": "2016-01-08T00:00:00Z"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			function challengeNext() {
 | 
								var payload = JSON.stringify(certOrder);
 | 
				
			||||||
				var auth = validAuths.shift();
 | 
								if (me.debug) {
 | 
				
			||||||
				if (!auth) {
 | 
									console.debug('\n[DEBUG] newOrder\n');
 | 
				
			||||||
					return;
 | 
								}
 | 
				
			||||||
 | 
								return ACME._jwsRequest(me, {
 | 
				
			||||||
 | 
									options: options,
 | 
				
			||||||
 | 
									url: me._directoryUrls.newOrder,
 | 
				
			||||||
 | 
									protected: { kid: options._kid },
 | 
				
			||||||
 | 
									payload: Enc.strToBuf(payload)
 | 
				
			||||||
 | 
								}).then(function(resp) {
 | 
				
			||||||
 | 
									var location = resp.headers.location;
 | 
				
			||||||
 | 
									var setAuths;
 | 
				
			||||||
 | 
									var validAuths = [];
 | 
				
			||||||
 | 
									var auths = [];
 | 
				
			||||||
 | 
									if (me.debug) {
 | 
				
			||||||
 | 
										console.debug('[ordered]', location);
 | 
				
			||||||
 | 
									} // the account id url
 | 
				
			||||||
 | 
									if (me.debug) {
 | 
				
			||||||
 | 
										console.debug(resp);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return ACME._postChallenge(me, options, auth).then(
 | 
									options._authorizations = resp.body.authorizations;
 | 
				
			||||||
					challengeNext
 | 
									options._order = location;
 | 
				
			||||||
				);
 | 
									options._finalize = resp.body.finalize;
 | 
				
			||||||
			}
 | 
									//if (me.debug) console.debug('[DEBUG] finalize:', options._finalize); return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// First we set every challenge
 | 
									if (!options._authorizations) {
 | 
				
			||||||
			// Then we ask for each challenge to be checked
 | 
										return Promise.reject(
 | 
				
			||||||
			// Doing otherwise would potentially cause us to poison our own DNS cache with misses
 | 
											new Error(
 | 
				
			||||||
			return setNext()
 | 
												"[acme-v2.js] authorizations were not fetched for '" +
 | 
				
			||||||
				.then(checkNext)
 | 
													options.domains.join() +
 | 
				
			||||||
				.then(challengeNext)
 | 
													"':\n" +
 | 
				
			||||||
				.then(function() {
 | 
													JSON.stringify(resp.body)
 | 
				
			||||||
 | 
											)
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (me.debug) {
 | 
				
			||||||
 | 
										console.debug('[acme-v2] POST newOrder has authorizations');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									setAuths = options._authorizations.slice(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var accountKeyThumb;
 | 
				
			||||||
 | 
									function setThumbnail() {
 | 
				
			||||||
 | 
										return ACME._importKeypair(me, options.accountKeypair).then(
 | 
				
			||||||
 | 
											function(pair) {
 | 
				
			||||||
 | 
												return me.Keypairs.thumbprint({
 | 
				
			||||||
 | 
													jwk: pair.public
 | 
				
			||||||
 | 
												}).then(function(_thumb) {
 | 
				
			||||||
 | 
													accountKeyThumb = _thumb;
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									function setNext() {
 | 
				
			||||||
 | 
										var authUrl = setAuths.shift();
 | 
				
			||||||
 | 
										if (!authUrl) {
 | 
				
			||||||
 | 
											return;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										return ACME._getChallenges(me, options, authUrl).then(
 | 
				
			||||||
 | 
											function(results) {
 | 
				
			||||||
 | 
												// var domain = options.domains[i]; // results.identifier.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												// If it's already valid, we're golden it regardless
 | 
				
			||||||
 | 
												if (
 | 
				
			||||||
 | 
													results.challenges.some(function(ch) {
 | 
				
			||||||
 | 
														return 'valid' === ch.status;
 | 
				
			||||||
 | 
													})
 | 
				
			||||||
 | 
												) {
 | 
				
			||||||
 | 
													return setNext();
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												var challenge = ACME._chooseChallenge(
 | 
				
			||||||
 | 
													options,
 | 
				
			||||||
 | 
													results
 | 
				
			||||||
 | 
												);
 | 
				
			||||||
 | 
												if (!challenge) {
 | 
				
			||||||
 | 
													// For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | 
				
			||||||
 | 
													return Promise.reject(
 | 
				
			||||||
 | 
														new Error(
 | 
				
			||||||
 | 
															"Server didn't offer any challenge we can handle for '" +
 | 
				
			||||||
 | 
																options.domains.join() +
 | 
				
			||||||
 | 
																"'."
 | 
				
			||||||
 | 
														)
 | 
				
			||||||
 | 
													);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												return ACME._challengeToAuth(
 | 
				
			||||||
 | 
													me,
 | 
				
			||||||
 | 
													options,
 | 
				
			||||||
 | 
													accountKeyThumb,
 | 
				
			||||||
 | 
													results,
 | 
				
			||||||
 | 
													challenge,
 | 
				
			||||||
 | 
													false
 | 
				
			||||||
 | 
												).then(function(auth) {
 | 
				
			||||||
 | 
													console.log('ADD DUBIOUS AUTH');
 | 
				
			||||||
 | 
													auths.push(auth);
 | 
				
			||||||
 | 
													return ACME._setChallenge(
 | 
				
			||||||
 | 
														me,
 | 
				
			||||||
 | 
														options,
 | 
				
			||||||
 | 
														auth
 | 
				
			||||||
 | 
													).then(setNext);
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									function waitAll() {
 | 
				
			||||||
 | 
										// TODO take the max wait of all challenge plugins and wait that long, or 1000ms
 | 
				
			||||||
 | 
										var DELAY = me.setChallengeWait || 7000;
 | 
				
			||||||
 | 
										if (true || me.debug) {
 | 
				
			||||||
 | 
											console.debug(
 | 
				
			||||||
 | 
												'\n[DEBUG] waitChallengeDelay %s\n',
 | 
				
			||||||
 | 
												DELAY
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return ACME._wait(DELAY);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									function checkNext() {
 | 
				
			||||||
 | 
										console.log('CONSUME DUBIOUS AUTH', auths.length);
 | 
				
			||||||
 | 
										var auth = auths.shift();
 | 
				
			||||||
 | 
										if (!auth) {
 | 
				
			||||||
 | 
											return;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (!me._canUse[auth.type] || me.skipChallengeTest) {
 | 
				
			||||||
 | 
											// not so much "valid" as "not invalid"
 | 
				
			||||||
 | 
											// but in this case we can't confirm either way
 | 
				
			||||||
 | 
											validAuths.push(auth);
 | 
				
			||||||
 | 
											console.log('ADD VALID AUTH (skip)', validAuths.length);
 | 
				
			||||||
 | 
											return checkNext();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										return ACME.challengeTests[auth.type](me, auth)
 | 
				
			||||||
 | 
											.then(function() {
 | 
				
			||||||
 | 
												console.log('ADD VALID AUTH');
 | 
				
			||||||
 | 
												validAuths.push(auth);
 | 
				
			||||||
 | 
											})
 | 
				
			||||||
 | 
											.then(checkNext);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									function presentNext() {
 | 
				
			||||||
 | 
										console.log('CONSUME VALID AUTH', validAuths.length);
 | 
				
			||||||
 | 
										var auth = validAuths.shift();
 | 
				
			||||||
 | 
										if (!auth) {
 | 
				
			||||||
 | 
											return;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return ACME._postChallenge(me, options, auth).then(
 | 
				
			||||||
 | 
											presentNext
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									function finalizeOrder() {
 | 
				
			||||||
					if (me.debug) {
 | 
										if (me.debug) {
 | 
				
			||||||
						console.debug('[getCertificate] next.then');
 | 
											console.debug('[getCertificate] next.then');
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					var validatedDomains = body.identifiers.map(function(
 | 
										var validatedDomains = certOrder.identifiers.map(function(
 | 
				
			||||||
						ident
 | 
											ident
 | 
				
			||||||
					) {
 | 
										) {
 | 
				
			||||||
						return ident.value;
 | 
											return ident.value;
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					return ACME._finalizeOrder(me, options, validatedDomains);
 | 
										return ACME._finalizeOrder(me, options, validatedDomains);
 | 
				
			||||||
				})
 | 
									}
 | 
				
			||||||
				.then(function(order) {
 | 
					
 | 
				
			||||||
 | 
									function retrieveCerts(order) {
 | 
				
			||||||
					if (me.debug) {
 | 
										if (me.debug) {
 | 
				
			||||||
						console.debug('acme-v2: order was finalized');
 | 
											console.debug('acme-v2: order was finalized');
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
@ -1141,10 +1232,22 @@ ACME._getCertificate = function(me, options) {
 | 
				
			|||||||
							}
 | 
												}
 | 
				
			||||||
							return certs;
 | 
												return certs;
 | 
				
			||||||
						});
 | 
											});
 | 
				
			||||||
				});
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// First we set each and every challenge
 | 
				
			||||||
 | 
									// Then we ask for each challenge to be checked
 | 
				
			||||||
 | 
									// Doing otherwise would potentially cause us to poison our own DNS cache with misses
 | 
				
			||||||
 | 
									return setThumbnail()
 | 
				
			||||||
 | 
										.then(setNext)
 | 
				
			||||||
 | 
										.then(waitAll)
 | 
				
			||||||
 | 
										.then(checkNext)
 | 
				
			||||||
 | 
										.then(presentNext)
 | 
				
			||||||
 | 
										.then(finalizeOrder)
 | 
				
			||||||
 | 
										.then(retrieveCerts);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
					ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
				
			||||||
	var csr;
 | 
						var csr;
 | 
				
			||||||
	if (options.csr) {
 | 
						if (options.csr) {
 | 
				
			||||||
@ -1153,6 +1256,7 @@ ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
				
			|||||||
		if ('string' !== typeof csr) {
 | 
							if ('string' !== typeof csr) {
 | 
				
			||||||
			csr = Enc.bufToUrlBase64(csr);
 | 
								csr = Enc.bufToUrlBase64(csr);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// TODO PEM.parseBlock()
 | 
				
			||||||
		// nix PEM headers, if any
 | 
							// nix PEM headers, if any
 | 
				
			||||||
		if ('-' === csr[0]) {
 | 
							if ('-' === csr[0]) {
 | 
				
			||||||
			csr = csr
 | 
								csr = csr
 | 
				
			||||||
@ -1168,15 +1272,13 @@ ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
				
			|||||||
		me,
 | 
							me,
 | 
				
			||||||
		options.serverKeypair || options.domainKeypair
 | 
							options.serverKeypair || options.domainKeypair
 | 
				
			||||||
	).then(function(pair) {
 | 
						).then(function(pair) {
 | 
				
			||||||
		return me
 | 
							return me.CSR.csr({
 | 
				
			||||||
			.CSR({
 | 
								jwk: pair.private,
 | 
				
			||||||
				jwk: pair.private,
 | 
								domains: validatedDomains,
 | 
				
			||||||
				domains: validatedDomains,
 | 
								encoding: 'der'
 | 
				
			||||||
				encoding: 'der'
 | 
							}).then(function(der) {
 | 
				
			||||||
			})
 | 
								return Enc.bufToUrlBase64(der);
 | 
				
			||||||
			.then(function(der) {
 | 
							});
 | 
				
			||||||
				return Enc.bufToUrlBase64(der);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1276,6 +1378,7 @@ ACME._jwsRequest = function(me, bigopts) {
 | 
				
			|||||||
				bigopts.protected.kid = bigopts.options._kid;
 | 
									bigopts.protected.kid = bigopts.options._kid;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// this will shasum the thumbnail the 2nd time
 | 
				
			||||||
		return me.Keypairs.signJws({
 | 
							return me.Keypairs.signJws({
 | 
				
			||||||
			jwk: bigopts.options.accountKeypair.privateKeyJwk,
 | 
								jwk: bigopts.options.accountKeypair.privateKeyJwk,
 | 
				
			||||||
			protected: bigopts.protected,
 | 
								protected: bigopts.protected,
 | 
				
			||||||
@ -1291,6 +1394,7 @@ ACME._jwsRequest = function(me, bigopts) {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Handle some ACME-specific defaults
 | 
					// Handle some ACME-specific defaults
 | 
				
			||||||
ACME._request = function(me, opts) {
 | 
					ACME._request = function(me, opts) {
 | 
				
			||||||
	if (!opts.headers) {
 | 
						if (!opts.headers) {
 | 
				
			||||||
@ -1430,24 +1534,99 @@ ACME._http01 = function(me, auth) {
 | 
				
			|||||||
ACME._removeChallenge = function(me, options, auth) {
 | 
					ACME._removeChallenge = function(me, options, auth) {
 | 
				
			||||||
	var challengers = options.challenges || {};
 | 
						var challengers = options.challenges || {};
 | 
				
			||||||
	var removeChallenge =
 | 
						var removeChallenge =
 | 
				
			||||||
		(challengers[auth.type] && challengers[auth.type].remove) ||
 | 
							challengers[auth.type] && challengers[auth.type].remove;
 | 
				
			||||||
		options.removeChallenge;
 | 
					 | 
				
			||||||
	if (1 === removeChallenge.length) {
 | 
						if (1 === removeChallenge.length) {
 | 
				
			||||||
		removeChallenge(auth).then(function() {}, function() {});
 | 
							return Promise.resolve(removeChallenge(auth)).then(
 | 
				
			||||||
 | 
								function() {},
 | 
				
			||||||
 | 
								function() {}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
	} else if (2 === removeChallenge.length) {
 | 
						} else if (2 === removeChallenge.length) {
 | 
				
			||||||
		removeChallenge(auth, function(err) {
 | 
							removeChallenge(auth, function(err) {
 | 
				
			||||||
			return err;
 | 
								return err;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if (!ACME._removeChallengeWarn) {
 | 
							throw new Error(
 | 
				
			||||||
			console.warn(
 | 
								"Bad function signature for '" + auth.type + "' challenge.remove()"
 | 
				
			||||||
				'Please update to acme-v2 removeChallenge(options) <Promise> or removeChallenge(options, cb).'
 | 
							);
 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			console.warn(
 | 
					 | 
				
			||||||
				"The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			ACME._removeChallengeWarn = true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		removeChallenge(auth.request.identifier, auth.token, function() {});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ACME._depInit = function(me, presenter) {
 | 
				
			||||||
 | 
						if ('function' !== typeof presenter.init) {
 | 
				
			||||||
 | 
							return Promise.resolve(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ACME._wrapCb(
 | 
				
			||||||
 | 
							me,
 | 
				
			||||||
 | 
							presenter,
 | 
				
			||||||
 | 
							'init',
 | 
				
			||||||
 | 
							{ type: '*', request: me.request },
 | 
				
			||||||
 | 
							'null'
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ACME._getZones = function(me, presenter, dnsHosts) {
 | 
				
			||||||
 | 
						if ('function' !== typeof presenter.zones) {
 | 
				
			||||||
 | 
							presenter.zones = function() {
 | 
				
			||||||
 | 
								return Promise.resolve([]);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var challenge = {
 | 
				
			||||||
 | 
							type: 'dns-01',
 | 
				
			||||||
 | 
							dnsHosts: dnsHosts,
 | 
				
			||||||
 | 
							request: me.request
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						// back/forwards-compat
 | 
				
			||||||
 | 
						challenge.challenge = challenge;
 | 
				
			||||||
 | 
						return ACME._wrapCb(
 | 
				
			||||||
 | 
							me,
 | 
				
			||||||
 | 
							presenter,
 | 
				
			||||||
 | 
							'zones',
 | 
				
			||||||
 | 
							challenge,
 | 
				
			||||||
 | 
							'an array of zone names'
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ACME._wrapCb = function(me, options, _name, args, _desc) {
 | 
				
			||||||
 | 
						return new Promise(function(resolve, reject) {
 | 
				
			||||||
 | 
							if (options[_name].length <= 1) {
 | 
				
			||||||
 | 
								return Promise.resolve(options[_name](args))
 | 
				
			||||||
 | 
									.then(resolve)
 | 
				
			||||||
 | 
									.catch(reject);
 | 
				
			||||||
 | 
							} else if (2 === options[_name].length) {
 | 
				
			||||||
 | 
								options[_name](args, function(err, results) {
 | 
				
			||||||
 | 
									if (err) {
 | 
				
			||||||
 | 
										reject(err);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										resolve(results);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								throw new Error(
 | 
				
			||||||
 | 
									'options.' + _name + ' should accept opts and Promise ' + _desc
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function newZoneRegExp(zonename) {
 | 
				
			||||||
 | 
						// (^|\.)example\.com$
 | 
				
			||||||
 | 
						// which matches:
 | 
				
			||||||
 | 
						//  foo.example.com
 | 
				
			||||||
 | 
						//  example.com
 | 
				
			||||||
 | 
						// but not:
 | 
				
			||||||
 | 
						//  fooexample.com
 | 
				
			||||||
 | 
						return new RegExp('(^|\\.)' + zonename.replace(/\./g, '\\.') + '$');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function pluckZone(zonenames, dnsHost) {
 | 
				
			||||||
 | 
						return zonenames
 | 
				
			||||||
 | 
							.filter(function(zonename) {
 | 
				
			||||||
 | 
								// the only character that needs to be escaped for regex
 | 
				
			||||||
 | 
								// and is allowed in a domain name is '.'
 | 
				
			||||||
 | 
								return newZoneRegExp(zonename).test(dnsHost);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.sort(function(a, b) {
 | 
				
			||||||
 | 
								// longest match first
 | 
				
			||||||
 | 
								return b.length - a.length;
 | 
				
			||||||
 | 
							})[0];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								lib/csr.js
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								lib/csr.js
									
									
									
									
									
								
							@ -5,18 +5,19 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
/*global Promise*/
 | 
					/*global Promise*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ASN1 = require('./asn1/parser.js'); // DER, actually
 | 
					var ASN1 = require('./asn1/packer.js'); // DER, actually
 | 
				
			||||||
var Asn1 = ASN1.Any;
 | 
					var Asn1 = ASN1.Any;
 | 
				
			||||||
var BitStr = ASN1.BitStr;
 | 
					var BitStr = ASN1.BitStr;
 | 
				
			||||||
var UInt = ASN1.UInt;
 | 
					var UInt = ASN1.UInt;
 | 
				
			||||||
var Asn1Parser = require('./asn1/packer.js'); // DER, actually
 | 
					var Asn1Parser = require('./asn1/parser.js');
 | 
				
			||||||
var Enc = require('omnibuffer');
 | 
					var Enc = require('omnibuffer');
 | 
				
			||||||
var PEM = require('./pem.js');
 | 
					var PEM = require('./pem.js');
 | 
				
			||||||
var X509 = require('./x509.js');
 | 
					var X509 = require('./x509.js');
 | 
				
			||||||
var Keypairs = require('./keypairs');
 | 
					var Keypairs = require('./keypairs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | 
					// TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | 
				
			||||||
var CSR = (exports.CSR = function(opts) {
 | 
					var CSR = module.exports;
 | 
				
			||||||
 | 
					CSR.csr = function(opts) {
 | 
				
			||||||
	// We're using a Promise here to be compatible with the browser version
 | 
						// We're using a Promise here to be compatible with the browser version
 | 
				
			||||||
	// which will probably use the webcrypto API for some of the conversions
 | 
						// which will probably use the webcrypto API for some of the conversions
 | 
				
			||||||
	return CSR._prepare(opts).then(function(opts) {
 | 
						return CSR._prepare(opts).then(function(opts) {
 | 
				
			||||||
@ -24,11 +25,10 @@ var CSR = (exports.CSR = function(opts) {
 | 
				
			|||||||
			return CSR._encode(opts, bytes);
 | 
								return CSR._encode(opts, bytes);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CSR._prepare = function(opts) {
 | 
					CSR._prepare = function(opts) {
 | 
				
			||||||
	return Promise.resolve().then(function() {
 | 
						return Promise.resolve().then(function() {
 | 
				
			||||||
		var Keypairs;
 | 
					 | 
				
			||||||
		opts = JSON.parse(JSON.stringify(opts));
 | 
							opts = JSON.parse(JSON.stringify(opts));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// We do a bit of extra error checking for user convenience
 | 
							// We do a bit of extra error checking for user convenience
 | 
				
			||||||
@ -66,16 +66,6 @@ CSR._prepare = function(opts) {
 | 
				
			|||||||
			throw new Error('You must pass options.key as a JSON web key');
 | 
								throw new Error('You must pass options.key as a JSON web key');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Keypairs = exports.Keypairs;
 | 
					 | 
				
			||||||
		if (!exports.Keypairs) {
 | 
					 | 
				
			||||||
			throw new Error(
 | 
					 | 
				
			||||||
				'Keypairs.js is an optional dependency for PEM-to-JWK.\n' +
 | 
					 | 
				
			||||||
					"Install it if you'd like to use it:\n" +
 | 
					 | 
				
			||||||
					'\tnpm install --save rasha\n' +
 | 
					 | 
				
			||||||
					'Otherwise supply a jwk as the private key.'
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return Keypairs.import({ pem: opts.pem || opts.key }).then(function(
 | 
							return Keypairs.import({ pem: opts.pem || opts.key }).then(function(
 | 
				
			||||||
			pair
 | 
								pair
 | 
				
			||||||
		) {
 | 
							) {
 | 
				
			||||||
@ -119,7 +109,7 @@ CSR._sign = function csrEcSig(jwk, request) {
 | 
				
			|||||||
	// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | 
						// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | 
				
			||||||
	// TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | 
						// TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | 
				
			||||||
	// TODO have a consistent non-private way to sign
 | 
						// TODO have a consistent non-private way to sign
 | 
				
			||||||
	return Keypairs._sign(
 | 
						return Keypairs.sign(
 | 
				
			||||||
		{ jwk: jwk, format: 'x509' },
 | 
							{ jwk: jwk, format: 'x509' },
 | 
				
			||||||
		Enc.hexToBuf(request)
 | 
							Enc.hexToBuf(request)
 | 
				
			||||||
	).then(function(sig) {
 | 
						).then(function(sig) {
 | 
				
			||||||
 | 
				
			|||||||
@ -76,12 +76,13 @@ Keypairs.neuter = function(opts) {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Keypairs.thumbprint = function(opts) {
 | 
					Keypairs.thumbprint = function(opts) {
 | 
				
			||||||
 | 
						//console.log('[debug]', new Error('NOT_ERROR').stack);
 | 
				
			||||||
	return Promise.resolve().then(function() {
 | 
						return Promise.resolve().then(function() {
 | 
				
			||||||
		if (/EC/i.test(opts.jwk.kty)) {
 | 
							if (/EC/i.test(opts.jwk.kty)) {
 | 
				
			||||||
      console.log('[debug] EC thumbprint');
 | 
								console.log('[debug] EC thumbprint');
 | 
				
			||||||
			return Eckles.thumbprint(opts);
 | 
								return Eckles.thumbprint(opts);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
      console.log('[debug] RSA thumbprint');
 | 
								console.log('[debug] RSA thumbprint');
 | 
				
			||||||
			return Rasha.thumbprint(opts);
 | 
								return Rasha.thumbprint(opts);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
@ -121,6 +122,7 @@ Keypairs.publish = function(opts) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// JWT a.k.a. JWS with Claims using Compact Serialization
 | 
					// JWT a.k.a. JWS with Claims using Compact Serialization
 | 
				
			||||||
Keypairs.signJwt = function(opts) {
 | 
					Keypairs.signJwt = function(opts) {
 | 
				
			||||||
 | 
						console.log('[debug] signJwt');
 | 
				
			||||||
	return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) {
 | 
						return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) {
 | 
				
			||||||
		var header = opts.header || {};
 | 
							var header = opts.header || {};
 | 
				
			||||||
		var claims = JSON.parse(JSON.stringify(opts.claims || {}));
 | 
							var claims = JSON.parse(JSON.stringify(opts.claims || {}));
 | 
				
			||||||
@ -255,6 +257,9 @@ Keypairs.signJws = function(opts) {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO expose consistently
 | 
				
			||||||
 | 
					Keypairs.sign = native._sign;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Keypairs._getBits = function(opts) {
 | 
					Keypairs._getBits = function(opts) {
 | 
				
			||||||
	if (opts.alg) {
 | 
						if (opts.alg) {
 | 
				
			||||||
		return opts.alg.replace(/[a-z\-]/gi, '');
 | 
							return opts.alg.replace(/[a-z\-]/gi, '');
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ Keypairs._sign = function(opts, payload) {
 | 
				
			|||||||
			.update(payload)
 | 
								.update(payload)
 | 
				
			||||||
			.sign(pem);
 | 
								.sign(pem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ('EC' === opts.jwk.kty) {
 | 
							if ('EC' === opts.jwk.kty && !/x509|asn1/i.test(opts.format)) {
 | 
				
			||||||
			// ECDSA JWT signatures differ from "normal" ECDSA signatures
 | 
								// ECDSA JWT signatures differ from "normal" ECDSA signatures
 | 
				
			||||||
			// https://tools.ietf.org/html/rfc7518#section-3.4
 | 
								// https://tools.ietf.org/html/rfc7518#section-3.4
 | 
				
			||||||
			binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig);
 | 
								binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,39 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require('dotenv').config();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ACME = require('../');
 | 
					var ACME = require('../');
 | 
				
			||||||
var Keypairs = require('../lib/keypairs.js');
 | 
					var Keypairs = require('../lib/keypairs.js');
 | 
				
			||||||
var acme = ACME.create({});
 | 
					var acme = ACME.create({ debug: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var config = {
 | 
					var config = {
 | 
				
			||||||
	env: process.env.ENV,
 | 
						env: process.env.ENV,
 | 
				
			||||||
	email: process.env.SUBSCRIBER_EMAIL,
 | 
						email: process.env.SUBSCRIBER_EMAIL,
 | 
				
			||||||
	domain: process.env.BASE_DOMAIN
 | 
						domain: process.env.BASE_DOMAIN,
 | 
				
			||||||
 | 
						challengeType: process.env.CHALLENGE_TYPE,
 | 
				
			||||||
 | 
						challengeModule: process.env.CHALLENGE_MODULE,
 | 
				
			||||||
 | 
						challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
config.debug = !/^PROD/i.test(config.env);
 | 
					config.debug = !/^PROD/i.test(config.env);
 | 
				
			||||||
 | 
					config.challenger = require('acme-' +
 | 
				
			||||||
 | 
						config.challengeType +
 | 
				
			||||||
 | 
						'-' +
 | 
				
			||||||
 | 
						config.challengeModule).create(config.challengeOptions);
 | 
				
			||||||
 | 
					if (!config.challengeType || !config.domain) {
 | 
				
			||||||
 | 
						console.error(
 | 
				
			||||||
 | 
							new Error('Missing config variables. Check you .env and the docs')
 | 
				
			||||||
 | 
								.message
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						console.error(config);
 | 
				
			||||||
 | 
						process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var challenges = {};
 | 
				
			||||||
 | 
					challenges[config.challengeType] = config.challenger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function happyPath() {
 | 
					async function happyPath() {
 | 
				
			||||||
	var domains = randomDomains();
 | 
					 | 
				
			||||||
	var agreed = false;
 | 
						var agreed = false;
 | 
				
			||||||
	var metadata = await acme.init(
 | 
						var metadata = await acme.init(
 | 
				
			||||||
		'https://acme-staging-v02.api.letsencrypt.org/directory'
 | 
							'https://acme-staging-v02.api.letsencrypt.org/directory'
 | 
				
			||||||
@ -66,8 +87,31 @@ async function happyPath() {
 | 
				
			|||||||
	if (config.debug) {
 | 
						if (config.debug) {
 | 
				
			||||||
		console.info('Server Key Created');
 | 
							console.info('Server Key Created');
 | 
				
			||||||
		console.info(JSON.stringify(serverKeypair, null, 2));
 | 
							console.info(JSON.stringify(serverKeypair, null, 2));
 | 
				
			||||||
		console.info('');
 | 
					 | 
				
			||||||
		console.info();
 | 
							console.info();
 | 
				
			||||||
 | 
							console.info();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var domains = randomDomains();
 | 
				
			||||||
 | 
						if (config.debug) {
 | 
				
			||||||
 | 
							console.info('Get certificates for random domains:');
 | 
				
			||||||
 | 
							console.info(domains);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var results = await acme.certificates.create({
 | 
				
			||||||
 | 
							account: account,
 | 
				
			||||||
 | 
							accountKeypair: { privateKeyJwk: accountKeypair.private },
 | 
				
			||||||
 | 
							serverKeypair: { privateKeyJwk: serverKeypair.private },
 | 
				
			||||||
 | 
							domains: domains,
 | 
				
			||||||
 | 
							challenges: challenges, // must be implemented
 | 
				
			||||||
 | 
							skipDryRun: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (config.debug) {
 | 
				
			||||||
 | 
							console.info('Got SSL Certificate:');
 | 
				
			||||||
 | 
							console.info(results.expires);
 | 
				
			||||||
 | 
							console.info(results.cert);
 | 
				
			||||||
 | 
							console.info(results.chain);
 | 
				
			||||||
 | 
							console.info('');
 | 
				
			||||||
 | 
							console.info('');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user