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) {
 | 
			
		||||
						contact = ['mailto:' + options.email];
 | 
			
		||||
					}
 | 
			
		||||
					var body = {
 | 
			
		||||
					var accountRequest = {
 | 
			
		||||
						termsOfServiceAgreed: tosUrl === me._tos,
 | 
			
		||||
						onlyReturnExisting: false,
 | 
			
		||||
						contact: contact
 | 
			
		||||
@ -182,14 +182,14 @@ ACME._registerAccount = function(me, options) {
 | 
			
		||||
							},
 | 
			
		||||
							payload: Enc.strToBuf(JSON.stringify(pair.public))
 | 
			
		||||
						}).then(function(jws) {
 | 
			
		||||
							body.externalAccountBinding = jws;
 | 
			
		||||
							return body;
 | 
			
		||||
							accountRequest.externalAccountBinding = jws;
 | 
			
		||||
							return accountRequest;
 | 
			
		||||
						});
 | 
			
		||||
					} else {
 | 
			
		||||
						pExt = Promise.resolve(body);
 | 
			
		||||
						pExt = Promise.resolve(accountRequest);
 | 
			
		||||
					}
 | 
			
		||||
					return pExt.then(function(body) {
 | 
			
		||||
						var payload = JSON.stringify(body);
 | 
			
		||||
					return pExt.then(function(accountRequest) {
 | 
			
		||||
						var payload = JSON.stringify(accountRequest);
 | 
			
		||||
						return ACME._jwsRequest(me, {
 | 
			
		||||
							options: options,
 | 
			
		||||
							url: me._directoryUrls.newAccount,
 | 
			
		||||
@ -199,10 +199,20 @@ ACME._registerAccount = function(me, options) {
 | 
			
		||||
							.then(function(resp) {
 | 
			
		||||
								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(
 | 
			
		||||
										'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) {
 | 
			
		||||
	console.log('[debug] testChallenges');
 | 
			
		||||
	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(
 | 
			
		||||
		options.domains.map(function(identifierValue) {
 | 
			
		||||
			// 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) {
 | 
			
		||||
				// 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 = {
 | 
			
		||||
					identifier: {
 | 
			
		||||
						type: 'dns',
 | 
			
		||||
@ -409,6 +437,7 @@ ACME._testChallenges = function(me, options) {
 | 
			
		||||
				return ACME._challengeToAuth(
 | 
			
		||||
					me,
 | 
			
		||||
					options,
 | 
			
		||||
					accountKeyThumb,
 | 
			
		||||
					results,
 | 
			
		||||
					challenge,
 | 
			
		||||
					dryrun
 | 
			
		||||
@ -460,7 +489,14 @@ ACME._chooseChallenge = function(options, results) {
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	var dnsPrefix = ACME.challengePrefixes['dns-01'];
 | 
			
		||||
	if (dryrun) {
 | 
			
		||||
@ -486,38 +522,58 @@ ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
			
		||||
		auth[key] = challenge[key];
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	var zone = pluckZone(options.zonenames || [], auth.identifier.value);
 | 
			
		||||
	// batteries-included helpers
 | 
			
		||||
	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
 | 
			
		||||
	auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
 | 
			
		||||
	return ACME._importKeypair(me, options.accountKeypair).then(function(pair) {
 | 
			
		||||
		return me.Keypairs.thumbprint({ jwk: pair.public }).then(function(
 | 
			
		||||
			thumb
 | 
			
		||||
		) {
 | 
			
		||||
			auth.thumbprint = thumb;
 | 
			
		||||
			//   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | 
			
		||||
			auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
			
		||||
			// conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | 
			
		||||
			// TODO auth.http01Url ?
 | 
			
		||||
			auth.challengeUrl =
 | 
			
		||||
				'http://' +
 | 
			
		||||
				auth.identifier.value +
 | 
			
		||||
				ACME.challengePrefixes['http-01'] +
 | 
			
		||||
				'/' +
 | 
			
		||||
				auth.token;
 | 
			
		||||
			auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
 | 
			
		||||
	// we must accept JWKs that we didn't generate and we can't guarantee
 | 
			
		||||
	// that they properly set kid to thumbnail (especially since ACME doesn't do this)
 | 
			
		||||
	// so we have to regenerate it every time we need it, which is quite often
 | 
			
		||||
	auth.thumbprint = accountKeyThumb;
 | 
			
		||||
	//   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | 
			
		||||
	auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
			
		||||
	// conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | 
			
		||||
	// TODO auth.http01Url ?
 | 
			
		||||
	auth.challengeUrl =
 | 
			
		||||
		'http://' +
 | 
			
		||||
		auth.identifier.value +
 | 
			
		||||
		ACME.challengePrefixes['http-01'] +
 | 
			
		||||
		'/' +
 | 
			
		||||
		auth.token;
 | 
			
		||||
	auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', '');
 | 
			
		||||
 | 
			
		||||
			return sha2
 | 
			
		||||
				.sum(256, auth.keyAuthorization)
 | 
			
		||||
				.then(function(hash) {
 | 
			
		||||
					return Enc.bufToUrlBase64(new Uint8Array(hash));
 | 
			
		||||
				})
 | 
			
		||||
				.then(function(hash64) {
 | 
			
		||||
					auth.dnsAuthorization = hash64;
 | 
			
		||||
					return auth;
 | 
			
		||||
				});
 | 
			
		||||
	// Always calculate dnsAuthorization because we
 | 
			
		||||
	// may need to present to the user for confirmation / instruction
 | 
			
		||||
	// _as part of_ the decision making process
 | 
			
		||||
	return sha2
 | 
			
		||||
		.sum(256, auth.keyAuthorization)
 | 
			
		||||
		.then(function(hash) {
 | 
			
		||||
			return Enc.bufToUrlBase64(new Uint8Array(hash));
 | 
			
		||||
		})
 | 
			
		||||
		.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) {
 | 
			
		||||
@ -597,7 +653,7 @@ ACME._postChallenge = function(me, options, auth) {
 | 
			
		||||
			.then(function(resp) {
 | 
			
		||||
				if ('processing' === resp.body.status) {
 | 
			
		||||
					if (me.debug) {
 | 
			
		||||
						console.debug('poll: again');
 | 
			
		||||
						console.debug('poll: again', auth.url);
 | 
			
		||||
					}
 | 
			
		||||
					return ACME._wait(RETRY_INTERVAL).then(pollStatus);
 | 
			
		||||
				}
 | 
			
		||||
@ -610,14 +666,14 @@ ACME._postChallenge = function(me, options, auth) {
 | 
			
		||||
							.then(respondToChallenge);
 | 
			
		||||
					}
 | 
			
		||||
					if (me.debug) {
 | 
			
		||||
						console.debug('poll: again');
 | 
			
		||||
						console.debug('poll: again', auth.url);
 | 
			
		||||
					}
 | 
			
		||||
					return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if ('valid' === resp.body.status) {
 | 
			
		||||
					if (me.debug) {
 | 
			
		||||
						console.debug('poll: valid');
 | 
			
		||||
						console.debug('VALID !!!!!!!!!!!!!!!! poll: valid');
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					try {
 | 
			
		||||
@ -637,7 +693,8 @@ ACME._postChallenge = function(me, options, auth) {
 | 
			
		||||
						"[acme-v2] (E_STATE_INVALID) challenge state for '" +
 | 
			
		||||
						altname +
 | 
			
		||||
						"': '" +
 | 
			
		||||
						resp.body.status +
 | 
			
		||||
						//resp.body.status +
 | 
			
		||||
						JSON.stringify(resp.body) +
 | 
			
		||||
						"'";
 | 
			
		||||
				} else {
 | 
			
		||||
					errmsg =
 | 
			
		||||
@ -675,17 +732,20 @@ ACME._postChallenge = function(me, options, auth) {
 | 
			
		||||
	return respondToChallenge();
 | 
			
		||||
};
 | 
			
		||||
ACME._setChallenge = function(me, options, auth) {
 | 
			
		||||
	return new Promise(function(resolve, reject) {
 | 
			
		||||
	return Promise.resolve().then(function() {
 | 
			
		||||
		var challengers = options.challenges || {};
 | 
			
		||||
		var challenger =
 | 
			
		||||
			(challengers[auth.type] && challengers[auth.type].set) ||
 | 
			
		||||
			options.setChallenge;
 | 
			
		||||
		try {
 | 
			
		||||
			if (1 === challenger.length) {
 | 
			
		||||
				challenger(auth)
 | 
			
		||||
					.then(resolve)
 | 
			
		||||
					.catch(reject);
 | 
			
		||||
			} else if (2 === challenger.length) {
 | 
			
		||||
		var challenger = challengers[auth.type] && challengers[auth.type].set;
 | 
			
		||||
		if (!challenger) {
 | 
			
		||||
			throw new Error(
 | 
			
		||||
				"options.challenges did not have a valid entry for '" +
 | 
			
		||||
					auth.type +
 | 
			
		||||
					"'"
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
		if (1 === challenger.length) {
 | 
			
		||||
			return Promise.resolve(challenger(auth));
 | 
			
		||||
		} else if (2 === challenger.length) {
 | 
			
		||||
			return new Promise(function(resolve, reject) {
 | 
			
		||||
				challenger(auth, function(err) {
 | 
			
		||||
					if (err) {
 | 
			
		||||
						reject(err);
 | 
			
		||||
@ -693,45 +753,12 @@ ACME._setChallenge = function(me, options, auth) {
 | 
			
		||||
						resolve();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				// TODO remove this old backwards-compat
 | 
			
		||||
				var challengeCb = function(err) {
 | 
			
		||||
					if (err) {
 | 
			
		||||
						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);
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			throw new Error(
 | 
			
		||||
				"Bad function signature for '" + auth.type + "' challenge.set()"
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	}).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) {
 | 
			
		||||
@ -943,170 +970,234 @@ ACME._getCertificate = function(me, options) {
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Do a little dry-run / self-test
 | 
			
		||||
	return ACME._testChallenges(me, options).then(function() {
 | 
			
		||||
		if (me.debug) {
 | 
			
		||||
			console.debug('[acme-v2] certificates.create');
 | 
			
		||||
	// TODO Promise.all()?
 | 
			
		||||
	Object.keys(options.challenges).forEach(function(key) {
 | 
			
		||||
		var presenter = options.challenges[key];
 | 
			
		||||
		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
 | 
			
		||||
				.sort(function(a, b) {
 | 
			
		||||
					// the first in the list will be the subject of the certificate, I believe (and hope)
 | 
			
		||||
					if (!options.subject) {
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	var promiseZones;
 | 
			
		||||
	if (options.challenges['dns-01']) {
 | 
			
		||||
		// a little bit of random to ensure that getZones()
 | 
			
		||||
		// 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;
 | 
			
		||||
					}
 | 
			
		||||
					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 auth = validAuths.shift();
 | 
			
		||||
				if (!auth) {
 | 
			
		||||
					return;
 | 
			
		||||
			var payload = JSON.stringify(certOrder);
 | 
			
		||||
			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);
 | 
			
		||||
				}
 | 
			
		||||
				return ACME._postChallenge(me, options, auth).then(
 | 
			
		||||
					challengeNext
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
				options._authorizations = resp.body.authorizations;
 | 
			
		||||
				options._order = location;
 | 
			
		||||
				options._finalize = resp.body.finalize;
 | 
			
		||||
				//if (me.debug) console.debug('[DEBUG] finalize:', options._finalize); return;
 | 
			
		||||
 | 
			
		||||
			// First we set 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 setNext()
 | 
			
		||||
				.then(checkNext)
 | 
			
		||||
				.then(challengeNext)
 | 
			
		||||
				.then(function() {
 | 
			
		||||
				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);
 | 
			
		||||
 | 
			
		||||
				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) {
 | 
			
		||||
						console.debug('[getCertificate] next.then');
 | 
			
		||||
					}
 | 
			
		||||
					var validatedDomains = body.identifiers.map(function(
 | 
			
		||||
					var validatedDomains = certOrder.identifiers.map(function(
 | 
			
		||||
						ident
 | 
			
		||||
					) {
 | 
			
		||||
						return ident.value;
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					return ACME._finalizeOrder(me, options, validatedDomains);
 | 
			
		||||
				})
 | 
			
		||||
				.then(function(order) {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				function retrieveCerts(order) {
 | 
			
		||||
					if (me.debug) {
 | 
			
		||||
						console.debug('acme-v2: order was finalized');
 | 
			
		||||
					}
 | 
			
		||||
@ -1141,10 +1232,22 @@ ACME._getCertificate = function(me, options) {
 | 
			
		||||
							}
 | 
			
		||||
							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) {
 | 
			
		||||
	var csr;
 | 
			
		||||
	if (options.csr) {
 | 
			
		||||
@ -1153,6 +1256,7 @@ ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
			
		||||
		if ('string' !== typeof csr) {
 | 
			
		||||
			csr = Enc.bufToUrlBase64(csr);
 | 
			
		||||
		}
 | 
			
		||||
		// TODO PEM.parseBlock()
 | 
			
		||||
		// nix PEM headers, if any
 | 
			
		||||
		if ('-' === csr[0]) {
 | 
			
		||||
			csr = csr
 | 
			
		||||
@ -1168,15 +1272,13 @@ ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
 | 
			
		||||
		me,
 | 
			
		||||
		options.serverKeypair || options.domainKeypair
 | 
			
		||||
	).then(function(pair) {
 | 
			
		||||
		return me
 | 
			
		||||
			.CSR({
 | 
			
		||||
				jwk: pair.private,
 | 
			
		||||
				domains: validatedDomains,
 | 
			
		||||
				encoding: 'der'
 | 
			
		||||
			})
 | 
			
		||||
			.then(function(der) {
 | 
			
		||||
				return Enc.bufToUrlBase64(der);
 | 
			
		||||
			});
 | 
			
		||||
		return me.CSR.csr({
 | 
			
		||||
			jwk: pair.private,
 | 
			
		||||
			domains: validatedDomains,
 | 
			
		||||
			encoding: 'der'
 | 
			
		||||
		}).then(function(der) {
 | 
			
		||||
			return Enc.bufToUrlBase64(der);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1276,6 +1378,7 @@ ACME._jwsRequest = function(me, bigopts) {
 | 
			
		||||
				bigopts.protected.kid = bigopts.options._kid;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// this will shasum the thumbnail the 2nd time
 | 
			
		||||
		return me.Keypairs.signJws({
 | 
			
		||||
			jwk: bigopts.options.accountKeypair.privateKeyJwk,
 | 
			
		||||
			protected: bigopts.protected,
 | 
			
		||||
@ -1291,6 +1394,7 @@ ACME._jwsRequest = function(me, bigopts) {
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Handle some ACME-specific defaults
 | 
			
		||||
ACME._request = function(me, opts) {
 | 
			
		||||
	if (!opts.headers) {
 | 
			
		||||
@ -1430,24 +1534,99 @@ ACME._http01 = function(me, auth) {
 | 
			
		||||
ACME._removeChallenge = function(me, options, auth) {
 | 
			
		||||
	var challengers = options.challenges || {};
 | 
			
		||||
	var removeChallenge =
 | 
			
		||||
		(challengers[auth.type] && challengers[auth.type].remove) ||
 | 
			
		||||
		options.removeChallenge;
 | 
			
		||||
		challengers[auth.type] && challengers[auth.type].remove;
 | 
			
		||||
	if (1 === removeChallenge.length) {
 | 
			
		||||
		removeChallenge(auth).then(function() {}, function() {});
 | 
			
		||||
		return Promise.resolve(removeChallenge(auth)).then(
 | 
			
		||||
			function() {},
 | 
			
		||||
			function() {}
 | 
			
		||||
		);
 | 
			
		||||
	} else if (2 === removeChallenge.length) {
 | 
			
		||||
		removeChallenge(auth, function(err) {
 | 
			
		||||
			return err;
 | 
			
		||||
		});
 | 
			
		||||
	} else {
 | 
			
		||||
		if (!ACME._removeChallengeWarn) {
 | 
			
		||||
			console.warn(
 | 
			
		||||
				'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() {});
 | 
			
		||||
		throw new Error(
 | 
			
		||||
			"Bad function signature for '" + auth.type + "' challenge.remove()"
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
/*global Promise*/
 | 
			
		||||
 | 
			
		||||
var ASN1 = require('./asn1/parser.js'); // DER, actually
 | 
			
		||||
var ASN1 = require('./asn1/packer.js'); // DER, actually
 | 
			
		||||
var Asn1 = ASN1.Any;
 | 
			
		||||
var BitStr = ASN1.BitStr;
 | 
			
		||||
var UInt = ASN1.UInt;
 | 
			
		||||
var Asn1Parser = require('./asn1/packer.js'); // DER, actually
 | 
			
		||||
var Asn1Parser = require('./asn1/parser.js');
 | 
			
		||||
var Enc = require('omnibuffer');
 | 
			
		||||
var PEM = require('./pem.js');
 | 
			
		||||
var X509 = require('./x509.js');
 | 
			
		||||
var Keypairs = require('./keypairs');
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	// which will probably use the webcrypto API for some of the conversions
 | 
			
		||||
	return CSR._prepare(opts).then(function(opts) {
 | 
			
		||||
@ -24,11 +25,10 @@ var CSR = (exports.CSR = function(opts) {
 | 
			
		||||
			return CSR._encode(opts, bytes);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CSR._prepare = function(opts) {
 | 
			
		||||
	return Promise.resolve().then(function() {
 | 
			
		||||
		var Keypairs;
 | 
			
		||||
		opts = JSON.parse(JSON.stringify(opts));
 | 
			
		||||
 | 
			
		||||
		// 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');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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(
 | 
			
		||||
			pair
 | 
			
		||||
		) {
 | 
			
		||||
@ -119,7 +109,7 @@ CSR._sign = function csrEcSig(jwk, request) {
 | 
			
		||||
	// 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 have a consistent non-private way to sign
 | 
			
		||||
	return Keypairs._sign(
 | 
			
		||||
	return Keypairs.sign(
 | 
			
		||||
		{ jwk: jwk, format: 'x509' },
 | 
			
		||||
		Enc.hexToBuf(request)
 | 
			
		||||
	).then(function(sig) {
 | 
			
		||||
 | 
			
		||||
@ -76,12 +76,13 @@ Keypairs.neuter = function(opts) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Keypairs.thumbprint = function(opts) {
 | 
			
		||||
	//console.log('[debug]', new Error('NOT_ERROR').stack);
 | 
			
		||||
	return Promise.resolve().then(function() {
 | 
			
		||||
		if (/EC/i.test(opts.jwk.kty)) {
 | 
			
		||||
      console.log('[debug] EC thumbprint');
 | 
			
		||||
			console.log('[debug] EC thumbprint');
 | 
			
		||||
			return Eckles.thumbprint(opts);
 | 
			
		||||
		} else {
 | 
			
		||||
      console.log('[debug] RSA thumbprint');
 | 
			
		||||
			console.log('[debug] RSA thumbprint');
 | 
			
		||||
			return Rasha.thumbprint(opts);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
@ -121,6 +122,7 @@ Keypairs.publish = function(opts) {
 | 
			
		||||
 | 
			
		||||
// JWT a.k.a. JWS with Claims using Compact Serialization
 | 
			
		||||
Keypairs.signJwt = function(opts) {
 | 
			
		||||
	console.log('[debug] signJwt');
 | 
			
		||||
	return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) {
 | 
			
		||||
		var header = opts.header || {};
 | 
			
		||||
		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) {
 | 
			
		||||
	if (opts.alg) {
 | 
			
		||||
		return opts.alg.replace(/[a-z\-]/gi, '');
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ Keypairs._sign = function(opts, payload) {
 | 
			
		||||
			.update(payload)
 | 
			
		||||
			.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
 | 
			
		||||
			// https://tools.ietf.org/html/rfc7518#section-3.4
 | 
			
		||||
			binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig);
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,39 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
require('dotenv').config();
 | 
			
		||||
 | 
			
		||||
var ACME = require('../');
 | 
			
		||||
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 = {
 | 
			
		||||
	env: process.env.ENV,
 | 
			
		||||
	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.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() {
 | 
			
		||||
	var domains = randomDomains();
 | 
			
		||||
	var agreed = false;
 | 
			
		||||
	var metadata = await acme.init(
 | 
			
		||||
		'https://acme-staging-v02.api.letsencrypt.org/directory'
 | 
			
		||||
@ -66,8 +87,31 @@ async function happyPath() {
 | 
			
		||||
	if (config.debug) {
 | 
			
		||||
		console.info('Server Key Created');
 | 
			
		||||
		console.info(JSON.stringify(serverKeypair, null, 2));
 | 
			
		||||
		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