forked from root/acme.js
		
	update API and tests
This commit is contained in:
		
							parent
							
								
									f05e9db38e
								
							
						
					
					
						commit
						0efa94eeb0
					
				
							
								
								
									
										199
									
								
								acme.js
									
									
									
									
									
								
							
							
						
						
									
										199
									
								
								acme.js
									
									
									
									
									
								
							@ -179,17 +179,17 @@ ACME._testChallengeOptions = function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
ACME._thumber = function(me, options, thumb) {
 | 
					ACME._thumber = function(me, options, thumb) {
 | 
				
			||||||
	var thumbPromise;
 | 
						var thumbPromise;
 | 
				
			||||||
	return function() {
 | 
						return function(key) {
 | 
				
			||||||
		if (thumb) {
 | 
							if (thumb) {
 | 
				
			||||||
			return Promise.resolve(thumb);
 | 
								return Promise.resolve(thumb);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (thumbPromise) {
 | 
							if (thumbPromise) {
 | 
				
			||||||
			return thumbPromise;
 | 
								return thumbPromise;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		thumbPromise = U._importKeypair(
 | 
							if (!key) {
 | 
				
			||||||
			me,
 | 
								key = options.accountKey || options.accountKeypair;
 | 
				
			||||||
			options.accountKey || options.accountKeypair
 | 
							}
 | 
				
			||||||
		).then(function(pair) {
 | 
							thumbPromise = U._importKeypair(null, key).then(function(pair) {
 | 
				
			||||||
			return Keypairs.thumbprint({
 | 
								return Keypairs.thumbprint({
 | 
				
			||||||
				jwk: pair.public
 | 
									jwk: pair.public
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
@ -266,7 +266,14 @@ ACME._dryRun = function(me, realOptions) {
 | 
				
			|||||||
					type: ch.type
 | 
										type: ch.type
 | 
				
			||||||
					//challenge: ch
 | 
										//challenge: ch
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				noopts.challenges[ch.type].remove({ challenge: ch });
 | 
									noopts.challenges[ch.type]
 | 
				
			||||||
 | 
										.remove({ challenge: ch })
 | 
				
			||||||
 | 
										.catch(function(err) {
 | 
				
			||||||
 | 
											err.action = 'challenge_remove';
 | 
				
			||||||
 | 
											err.altname = ch.altname;
 | 
				
			||||||
 | 
											err.type = ch.type;
 | 
				
			||||||
 | 
											ACME._notify(me, noopts, 'error', err);
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -310,9 +317,8 @@ ACME._computeAuths = function(me, options, thumb, request, dryrun) {
 | 
				
			|||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var getThumbprint = ACME._thumber(me, options, thumb);
 | 
						var getThumbprint = ACME._thumber(null, options, thumb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return getThumbprint().then(function(thumb) {
 | 
					 | 
				
			||||||
	return Promise.all(
 | 
						return Promise.all(
 | 
				
			||||||
		request.challenges.map(function(challenge) {
 | 
							request.challenges.map(function(challenge) {
 | 
				
			||||||
			// Don't do extra work for challenges that we can't satisfy
 | 
								// Don't do extra work for challenges that we can't satisfy
 | 
				
			||||||
@ -340,65 +346,88 @@ ACME._computeAuths = function(me, options, thumb, request, dryrun) {
 | 
				
			|||||||
			auth.hostname = auth.identifier.value;
 | 
								auth.hostname = auth.identifier.value;
 | 
				
			||||||
			// because I'm not 100% clear if the wildcard identifier does or doesn't
 | 
								// because I'm not 100% clear if the wildcard identifier does or doesn't
 | 
				
			||||||
			// have the leading *. in all cases
 | 
								// have the leading *. in all cases
 | 
				
			||||||
				auth.altname = ACME._untame(
 | 
								auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
 | 
				
			||||||
					auth.identifier.value,
 | 
					 | 
				
			||||||
					auth.wildcard
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				auth.thumbprint = thumb;
 | 
					 | 
				
			||||||
				//   keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
 | 
					 | 
				
			||||||
				auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if ('http-01' === auth.type) {
 | 
					 | 
				
			||||||
					// 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;
 | 
					 | 
				
			||||||
					return auth;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if ('dns-01' !== auth.type) {
 | 
					 | 
				
			||||||
					return auth;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var zone = pluckZone(
 | 
								var zone = pluckZone(
 | 
				
			||||||
				options.zonenames || [],
 | 
									options.zonenames || [],
 | 
				
			||||||
				auth.identifier.value
 | 
									auth.identifier.value
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Always calculate dnsAuthorization because we
 | 
								return ACME.computeChallenge({
 | 
				
			||||||
				// may need to present to the user for confirmation / instruction
 | 
									accountKey: options.accountKey,
 | 
				
			||||||
				// _as part of_ the decision making process
 | 
									_getThumbprint: getThumbprint,
 | 
				
			||||||
				return sha2
 | 
									challenge: auth,
 | 
				
			||||||
					.sum(256, auth.keyAuthorization)
 | 
									zone: zone,
 | 
				
			||||||
					.then(function(hash) {
 | 
									dnsPrefix: dnsPrefix
 | 
				
			||||||
						return Enc.bufToUrlBase64(new Uint8Array(hash));
 | 
								}).then(function(resp) {
 | 
				
			||||||
					})
 | 
									Object.keys(resp).forEach(function(k) {
 | 
				
			||||||
					.then(function(hash64) {
 | 
										auth[k] = resp[k];
 | 
				
			||||||
						auth.dnsHost =
 | 
									});
 | 
				
			||||||
							dnsPrefix + '.' + auth.hostname.replace('*.', '');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						auth.dnsAuthorization = hash64;
 | 
					 | 
				
			||||||
						auth.keyAuthorizationDigest = hash64;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						if (zone) {
 | 
					 | 
				
			||||||
							auth.dnsZone = zone;
 | 
					 | 
				
			||||||
							auth.dnsPrefix = auth.dnsHost
 | 
					 | 
				
			||||||
								.replace(newZoneRegExp(zone), '')
 | 
					 | 
				
			||||||
								.replace(/\.$/, '');
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return auth;
 | 
									return auth;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	).then(function(auths) {
 | 
						).then(function(auths) {
 | 
				
			||||||
		return auths.filter(Boolean);
 | 
							return auths.filter(Boolean);
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ACME.computeChallenge = function(opts) {
 | 
				
			||||||
 | 
						var auth = opts.challenge;
 | 
				
			||||||
 | 
						var hostname = auth.hostname || opts.hostname;
 | 
				
			||||||
 | 
						var zone = opts.zone;
 | 
				
			||||||
 | 
						var thumb = opts.thumbprint || '';
 | 
				
			||||||
 | 
						var accountKey = opts.accountKey;
 | 
				
			||||||
 | 
						var getThumbprint = opts._getThumbprint || ACME._thumber(null, opts, thumb);
 | 
				
			||||||
 | 
						var dnsPrefix = opts.dnsPrefix || ACME.challengePrefixes['dns-01'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return getThumbprint(accountKey).then(function(thumb) {
 | 
				
			||||||
 | 
							var resp = {};
 | 
				
			||||||
 | 
							resp.thumbprint = thumb;
 | 
				
			||||||
 | 
							//   keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
 | 
				
			||||||
 | 
							resp.keyAuthorization = auth.token + '.' + thumb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ('http-01' === auth.type) {
 | 
				
			||||||
 | 
								// conflicts with ACME challenge id url is already in use,
 | 
				
			||||||
 | 
								// so we call this challengeUrl instead
 | 
				
			||||||
 | 
								// TODO auth.http01Url ?
 | 
				
			||||||
 | 
								resp.challengeUrl =
 | 
				
			||||||
 | 
									'http://' +
 | 
				
			||||||
 | 
									// `hostname` is an alias of `auth.indentifier.value`
 | 
				
			||||||
 | 
									hostname +
 | 
				
			||||||
 | 
									ACME.challengePrefixes['http-01'] +
 | 
				
			||||||
 | 
									'/' +
 | 
				
			||||||
 | 
									auth.token;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ('dns-01' !== auth.type) {
 | 
				
			||||||
 | 
								return resp;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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, resp.keyAuthorization)
 | 
				
			||||||
 | 
								.then(function(hash) {
 | 
				
			||||||
 | 
									return Enc.bufToUrlBase64(Uint8Array.from(hash));
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.then(function(hash64) {
 | 
				
			||||||
 | 
									resp.dnsHost = dnsPrefix + '.' + hostname; // .replace('*.', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// deprecated
 | 
				
			||||||
 | 
									resp.dnsAuthorization = hash64;
 | 
				
			||||||
 | 
									// should use this instead
 | 
				
			||||||
 | 
									resp.keyAuthorizationDigest = hash64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (zone) {
 | 
				
			||||||
 | 
										resp.dnsZone = zone;
 | 
				
			||||||
 | 
										resp.dnsPrefix = resp.dnsHost
 | 
				
			||||||
 | 
											.replace(newZoneRegExp(zone), '')
 | 
				
			||||||
 | 
											.replace(/\.$/, '');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return resp;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -583,6 +612,7 @@ ACME._setChallenges = function(me, options, order) {
 | 
				
			|||||||
	var claims = order._claims.slice(0);
 | 
						var claims = order._claims.slice(0);
 | 
				
			||||||
	var valids = [];
 | 
						var valids = [];
 | 
				
			||||||
	var auths = [];
 | 
						var auths = [];
 | 
				
			||||||
 | 
						var placed = [];
 | 
				
			||||||
	var USE_DNS = false;
 | 
						var USE_DNS = false;
 | 
				
			||||||
	var DNS_DELAY = 0;
 | 
						var DNS_DELAY = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -618,6 +648,7 @@ ACME._setChallenges = function(me, options, order) {
 | 
				
			|||||||
					);
 | 
										);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				auths.push(selected);
 | 
									auths.push(selected);
 | 
				
			||||||
 | 
									placed.push(selected);
 | 
				
			||||||
				ACME._notify(me, options, 'challenge_select', {
 | 
									ACME._notify(me, options, 'challenge_select', {
 | 
				
			||||||
					// API-locked
 | 
										// API-locked
 | 
				
			||||||
					altname: ACME._untame(
 | 
										altname: ACME._untame(
 | 
				
			||||||
@ -651,10 +682,13 @@ ACME._setChallenges = function(me, options, order) {
 | 
				
			|||||||
	function waitAll() {
 | 
						function waitAll() {
 | 
				
			||||||
		//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
 | 
							//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
 | 
				
			||||||
		if (!DNS_DELAY || DNS_DELAY <= 0) {
 | 
							if (!DNS_DELAY || DNS_DELAY <= 0) {
 | 
				
			||||||
 | 
								if (!ACME._propagationDelayWarning) {
 | 
				
			||||||
				console.warn(
 | 
									console.warn(
 | 
				
			||||||
				'the given dns-01 challenge did not specify `propagationDelay`'
 | 
										'warn: the given dns-01 challenge did not specify `propagationDelay`'
 | 
				
			||||||
				);
 | 
									);
 | 
				
			||||||
			console.warn('the default of 5000ms will be used');
 | 
									console.warn('warn: the default of 5000ms will be used');
 | 
				
			||||||
 | 
									ACME._propagationDelayWarning = true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			DNS_DELAY = 5000;
 | 
								DNS_DELAY = 5000;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return ACME._wait(DNS_DELAY);
 | 
							return ACME._wait(DNS_DELAY);
 | 
				
			||||||
@ -683,7 +717,22 @@ ACME._setChallenges = function(me, options, order) {
 | 
				
			|||||||
	// is so that we don't poison our own DNS cache with misses.
 | 
						// is so that we don't poison our own DNS cache with misses.
 | 
				
			||||||
	return setNext()
 | 
						return setNext()
 | 
				
			||||||
		.then(waitAll)
 | 
							.then(waitAll)
 | 
				
			||||||
		.then(checkNext);
 | 
							.then(checkNext)
 | 
				
			||||||
 | 
							.catch(function(err) {
 | 
				
			||||||
 | 
								if (!options.debug) {
 | 
				
			||||||
 | 
									placed.forEach(function(ch) {
 | 
				
			||||||
 | 
										options.challenges[ch.type]
 | 
				
			||||||
 | 
											.remove({ challenge: ch })
 | 
				
			||||||
 | 
											.catch(function(err) {
 | 
				
			||||||
 | 
												err.action = 'challenge_remove';
 | 
				
			||||||
 | 
												err.altname = ch.altname;
 | 
				
			||||||
 | 
												err.type = ch.type;
 | 
				
			||||||
 | 
												ACME._notify(me, options, 'error', err);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								throw err;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ACME._normalizePresenters = function(me, options, presenters) {
 | 
					ACME._normalizePresenters = function(me, options, presenters) {
 | 
				
			||||||
@ -1283,40 +1332,6 @@ ACME._prnd = function(n) {
 | 
				
			|||||||
ACME._toHex = function(pair) {
 | 
					ACME._toHex = function(pair) {
 | 
				
			||||||
	return parseInt(pair, 10).toString(16);
 | 
						return parseInt(pair, 10).toString(16);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
ACME._removeChallenge = function(me, options, auth) {
 | 
					 | 
				
			||||||
	var challengers = options.challenges || {};
 | 
					 | 
				
			||||||
	var ch = auth.challenge;
 | 
					 | 
				
			||||||
	var removeChallenge = challengers[ch.type] && challengers[ch.type].remove;
 | 
					 | 
				
			||||||
	if (!removeChallenge) {
 | 
					 | 
				
			||||||
		throw new Error('challenge plugin is missing remove()');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO normalize, warn, and just use promises
 | 
					 | 
				
			||||||
	if (1 === removeChallenge.length) {
 | 
					 | 
				
			||||||
		return Promise.resolve(removeChallenge(auth)).then(
 | 
					 | 
				
			||||||
			function() {},
 | 
					 | 
				
			||||||
			function(e) {
 | 
					 | 
				
			||||||
				console.error('Error during remove challenge:');
 | 
					 | 
				
			||||||
				console.error(e);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	} else if (2 === removeChallenge.length) {
 | 
					 | 
				
			||||||
		return new Promise(function(resolve) {
 | 
					 | 
				
			||||||
			removeChallenge(auth, function(err) {
 | 
					 | 
				
			||||||
				resolve();
 | 
					 | 
				
			||||||
				if (err) {
 | 
					 | 
				
			||||||
					console.error('Error during remove challenge:');
 | 
					 | 
				
			||||||
					console.error(err);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return err;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		throw new Error(
 | 
					 | 
				
			||||||
			"Bad function signature for '" + auth.type + "' challenge.remove()"
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ACME._depInit = function(me, presenter) {
 | 
					ACME._depInit = function(me, presenter) {
 | 
				
			||||||
	if ('function' !== typeof presenter.init) {
 | 
						if ('function' !== typeof presenter.init) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										111
									
								
								tests/compute-authorization-response.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								tests/compute-authorization-response.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ACME = require('../');
 | 
				
			||||||
 | 
					var accountKey = require('../fixtures/account.jwk.json').private;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var authorization = {
 | 
				
			||||||
 | 
						identifier: {
 | 
				
			||||||
 | 
							type: 'dns',
 | 
				
			||||||
 | 
							value: 'example.com'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						status: 'pending',
 | 
				
			||||||
 | 
						expires: '2018-04-25T00:23:57Z',
 | 
				
			||||||
 | 
						challenges: [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'dns-01',
 | 
				
			||||||
 | 
								status: 'pending',
 | 
				
			||||||
 | 
								url:
 | 
				
			||||||
 | 
									'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755342',
 | 
				
			||||||
 | 
								token: 'LZdlUiZ-kWPs6q5WTmQFYQHZKpz9szn2vxEUu0XhyyM'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'http-01',
 | 
				
			||||||
 | 
								status: 'pending',
 | 
				
			||||||
 | 
								url:
 | 
				
			||||||
 | 
									'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755343',
 | 
				
			||||||
 | 
								token: '1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					var expectedChallengeUrl =
 | 
				
			||||||
 | 
						'http://example.com/.well-known/acme-challenge/1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU';
 | 
				
			||||||
 | 
					var expectedKeyAuth =
 | 
				
			||||||
 | 
						'1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU.UuuZa_56jCM2douUq1riGyRphPtRvCPkxtkg0bP-pNs';
 | 
				
			||||||
 | 
					var expectedKeyAuthDigest = 'iQiMcQUDiAeD0TJV1RHJuGnI5D2-PuSpxKz9JqUaZ2M';
 | 
				
			||||||
 | 
					var expectedDnsHost = '_test-challenge.example.com';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function main() {
 | 
				
			||||||
 | 
						console.info('\n[Test] computing challenge authorizatin responses');
 | 
				
			||||||
 | 
						var challenges = authorization.challenges.slice(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function next() {
 | 
				
			||||||
 | 
							var ch = challenges.shift();
 | 
				
			||||||
 | 
							if (!ch) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var hostname = authorization.identifier.value;
 | 
				
			||||||
 | 
							return ACME.computeChallenge({
 | 
				
			||||||
 | 
								accountKey: accountKey,
 | 
				
			||||||
 | 
								hostname: hostname,
 | 
				
			||||||
 | 
								challenge: ch,
 | 
				
			||||||
 | 
								dnsPrefix: '_test-challenge'
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
								.then(function(auth) {
 | 
				
			||||||
 | 
									if ('dns-01' === ch.type) {
 | 
				
			||||||
 | 
										if (auth.keyAuthorizationDigest !== expectedKeyAuthDigest) {
 | 
				
			||||||
 | 
											console.error('[keyAuthorizationDigest]');
 | 
				
			||||||
 | 
											console.error(auth.keyAuthorizationDigest);
 | 
				
			||||||
 | 
											console.error(expectedKeyAuthDigest);
 | 
				
			||||||
 | 
											throw new Error('bad keyAuthDigest');
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if (auth.dnsHost !== expectedDnsHost) {
 | 
				
			||||||
 | 
											console.error('[dnsHost]');
 | 
				
			||||||
 | 
											console.error(auth.dnsHost);
 | 
				
			||||||
 | 
											console.error(expectedDnsHost);
 | 
				
			||||||
 | 
											throw new Error('bad dnsHost');
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else if ('http-01' === ch.type) {
 | 
				
			||||||
 | 
										if (auth.challengeUrl !== expectedChallengeUrl) {
 | 
				
			||||||
 | 
											console.error('[challengeUrl]');
 | 
				
			||||||
 | 
											console.error(auth.challengeUrl);
 | 
				
			||||||
 | 
											console.error(expectedChallengeUrl);
 | 
				
			||||||
 | 
											throw new Error('bad challengeUrl');
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if (auth.challengeUrl !== expectedChallengeUrl) {
 | 
				
			||||||
 | 
											console.error('[keyAuthorization]');
 | 
				
			||||||
 | 
											console.error(auth.keyAuthorization);
 | 
				
			||||||
 | 
											console.error(expectedKeyAuth);
 | 
				
			||||||
 | 
											throw new Error('bad keyAuth');
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										throw new Error('bad authorization inputs');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									console.info('PASS', hostname, ch.type);
 | 
				
			||||||
 | 
									return next();
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.catch(function(err) {
 | 
				
			||||||
 | 
									err.message =
 | 
				
			||||||
 | 
										'Error computing ' +
 | 
				
			||||||
 | 
										ch.type +
 | 
				
			||||||
 | 
										' for ' +
 | 
				
			||||||
 | 
										hostname +
 | 
				
			||||||
 | 
										':' +
 | 
				
			||||||
 | 
										err.message;
 | 
				
			||||||
 | 
									throw err;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return next();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function() {
 | 
				
			||||||
 | 
						return main(authorization)
 | 
				
			||||||
 | 
							.then(function() {
 | 
				
			||||||
 | 
								console.info('PASS');
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch(function(err) {
 | 
				
			||||||
 | 
								console.error(err.stack);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -35,55 +35,46 @@ var tests = [
 | 
				
			|||||||
	'----\nxxxx\nyyyy\n----\r\n----\nxxxx\ryyyy\n----\n'
 | 
						'----\nxxxx\nyyyy\n----\r\n----\nxxxx\ryyyy\n----\n'
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function formatPemChain(str) {
 | 
					var ACME = require('../');
 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		str
 | 
					 | 
				
			||||||
			.trim()
 | 
					 | 
				
			||||||
			.replace(/[\r\n]+/g, '\n')
 | 
					 | 
				
			||||||
			.replace(/\-\n\-/g, '-\n\n-') + '\n'
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function splitPemChain(str) {
 | 
					 | 
				
			||||||
	return str
 | 
					 | 
				
			||||||
		.trim()
 | 
					 | 
				
			||||||
		.split(/[\r\n]{2,}/g)
 | 
					 | 
				
			||||||
		.map(function(str) {
 | 
					 | 
				
			||||||
			return str + '\n';
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
tests.forEach(function(str) {
 | 
					module.exports = function() {
 | 
				
			||||||
	var actual = formatPemChain(str);
 | 
						console.info('\n[Test] can split and format PEM chain properly');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests.forEach(function(str) {
 | 
				
			||||||
 | 
							var actual = ACME.formatPemChain(str);
 | 
				
			||||||
		if (expected !== actual) {
 | 
							if (expected !== actual) {
 | 
				
			||||||
			console.error('input:   ', JSON.stringify(str));
 | 
								console.error('input:   ', JSON.stringify(str));
 | 
				
			||||||
			console.error('expected:', JSON.stringify(expected));
 | 
								console.error('expected:', JSON.stringify(expected));
 | 
				
			||||||
			console.error('actual:  ', JSON.stringify(actual));
 | 
								console.error('actual:  ', JSON.stringify(actual));
 | 
				
			||||||
			throw new Error('did not pass');
 | 
								throw new Error('did not pass');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (
 | 
						if (
 | 
				
			||||||
		'----\nxxxx\nyyyy\n----\n' !==
 | 
							'----\nxxxx\nyyyy\n----\n' !==
 | 
				
			||||||
	formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
 | 
							ACME.formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
 | 
				
			||||||
) {
 | 
						) {
 | 
				
			||||||
		throw new Error('Not proper for single cert in chain');
 | 
							throw new Error('Not proper for single cert in chain');
 | 
				
			||||||
}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (
 | 
						if (
 | 
				
			||||||
		'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
 | 
							'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
 | 
				
			||||||
	formatPemChain(
 | 
							ACME.formatPemChain(
 | 
				
			||||||
			'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
 | 
								'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
) {
 | 
						) {
 | 
				
			||||||
		throw new Error('Not proper for three certs in chain');
 | 
							throw new Error('Not proper for three certs in chain');
 | 
				
			||||||
}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
splitPemChain(
 | 
						ACME.splitPemChain(
 | 
				
			||||||
		'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
 | 
							'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
 | 
				
			||||||
).forEach(function(str) {
 | 
						).forEach(function(str) {
 | 
				
			||||||
		if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
 | 
							if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
 | 
				
			||||||
			throw new Error('bad thingy');
 | 
								throw new Error('bad thingy');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
console.info('PASS');
 | 
						console.info('PASS');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Promise.resolve();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,15 +1,27 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function run() {
 | 
					module.exports = async function() {
 | 
				
			||||||
 | 
						console.log('[Test] can generate, export, and import key');
 | 
				
			||||||
	var Keypairs = require('@root/keypairs');
 | 
						var Keypairs = require('@root/keypairs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var certKeypair = await Keypairs.generate({ kty: 'RSA' });
 | 
						var certKeypair = await Keypairs.generate({ kty: 'RSA' });
 | 
				
			||||||
	console.log(certKeypair);
 | 
						//console.log(certKeypair);
 | 
				
			||||||
	var pem = await Keypairs.export({
 | 
						var pem = await Keypairs.export({
 | 
				
			||||||
		jwk: certKeypair.private,
 | 
							jwk: certKeypair.private,
 | 
				
			||||||
		encoding: 'pem'
 | 
							encoding: 'pem'
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	console.log(pem);
 | 
						var jwk = await Keypairs.import({
 | 
				
			||||||
}
 | 
							pem: pem
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						['kty', 'd', 'n', 'e'].forEach(function(k) {
 | 
				
			||||||
 | 
							if (!jwk[k] || jwk[k] !== certKeypair.private[k]) {
 | 
				
			||||||
 | 
								throw new Error('bad export/import');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						//console.log(pem);
 | 
				
			||||||
 | 
						console.log('PASS');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run();
 | 
					if (require.main === module) {
 | 
				
			||||||
 | 
						module.exports();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										245
									
								
								tests/index.js
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								tests/index.js
									
									
									
									
									
								
							@ -1,245 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require('dotenv').config();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var CSR = require('@root/csr');
 | 
					 | 
				
			||||||
var Enc = require('@root/encoding/base64');
 | 
					 | 
				
			||||||
var PEM = require('@root/pem');
 | 
					 | 
				
			||||||
var punycode = require('punycode');
 | 
					 | 
				
			||||||
var ACME = require('../acme.js');
 | 
					 | 
				
			||||||
var Keypairs = require('@root/keypairs');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
					 | 
				
			||||||
if (!process.env.CHALLENGE_OPTIONS) {
 | 
					 | 
				
			||||||
	console.error(
 | 
					 | 
				
			||||||
		'Please create a .env in the format of examples/example.env to run the tests'
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
	process.exit(1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var config = {
 | 
					 | 
				
			||||||
	env: process.env.ENV,
 | 
					 | 
				
			||||||
	email: process.env.SUBSCRIBER_EMAIL,
 | 
					 | 
				
			||||||
	domain: process.env.BASE_DOMAIN,
 | 
					 | 
				
			||||||
	challengeType: process.env.CHALLENGE_TYPE,
 | 
					 | 
				
			||||||
	challengeModule: process.env.CHALLENGE_PLUGIN,
 | 
					 | 
				
			||||||
	challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
config.debug = !/^PROD/i.test(config.env);
 | 
					 | 
				
			||||||
var pluginPrefix = 'acme-' + config.challengeType + '-';
 | 
					 | 
				
			||||||
var pluginName = config.challengeModule;
 | 
					 | 
				
			||||||
var plugin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var acme = ACME.create({
 | 
					 | 
				
			||||||
	// debug: true
 | 
					 | 
				
			||||||
	maintainerEmail: config.email,
 | 
					 | 
				
			||||||
	notify: function(ev, params) {
 | 
					 | 
				
			||||||
		console.info(
 | 
					 | 
				
			||||||
			ev,
 | 
					 | 
				
			||||||
			params.subject || params.altname || params.domain,
 | 
					 | 
				
			||||||
			params.status
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		if ('error' === ev) {
 | 
					 | 
				
			||||||
			console.error(params);
 | 
					 | 
				
			||||||
			console.error(params.error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function badPlugin(err) {
 | 
					 | 
				
			||||||
	if ('MODULE_NOT_FOUND' !== err.code) {
 | 
					 | 
				
			||||||
		console.error(err);
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	console.error("Couldn't find '" + pluginName + "'. Is it installed?");
 | 
					 | 
				
			||||||
	console.error("\tnpm install --save-dev '" + pluginName + "'");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
try {
 | 
					 | 
				
			||||||
	plugin = require(pluginName);
 | 
					 | 
				
			||||||
} catch (err) {
 | 
					 | 
				
			||||||
	if (
 | 
					 | 
				
			||||||
		'MODULE_NOT_FOUND' !== err.code ||
 | 
					 | 
				
			||||||
		0 === pluginName.indexOf(pluginPrefix)
 | 
					 | 
				
			||||||
	) {
 | 
					 | 
				
			||||||
		badPlugin(err);
 | 
					 | 
				
			||||||
		process.exit(1);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	try {
 | 
					 | 
				
			||||||
		pluginName = pluginPrefix + pluginName;
 | 
					 | 
				
			||||||
		plugin = require(pluginName);
 | 
					 | 
				
			||||||
	} catch (e) {
 | 
					 | 
				
			||||||
		badPlugin(e);
 | 
					 | 
				
			||||||
		process.exit(1);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
config.challenger = plugin.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(accKty, srvKty, rnd) {
 | 
					 | 
				
			||||||
	var agreed = false;
 | 
					 | 
				
			||||||
	var metadata = await acme.init(
 | 
					 | 
				
			||||||
		'https://acme-staging-v02.api.letsencrypt.org/directory'
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Ready to use, show page
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('ACME.js initialized');
 | 
					 | 
				
			||||||
		console.info(metadata);
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var accountKeypair = await Keypairs.generate({ kty: accKty });
 | 
					 | 
				
			||||||
	var accountKey = accountKeypair.private;
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('Account Key Created');
 | 
					 | 
				
			||||||
		console.info(JSON.stringify(accountKey, null, 2));
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var account = await acme.accounts.create({
 | 
					 | 
				
			||||||
		agreeToTerms: agree,
 | 
					 | 
				
			||||||
		// TODO detect jwk/pem/der?
 | 
					 | 
				
			||||||
		accountKey: accountKey,
 | 
					 | 
				
			||||||
		subscriberEmail: config.email
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO top-level agree
 | 
					 | 
				
			||||||
	function agree(tos) {
 | 
					 | 
				
			||||||
		if (config.debug) {
 | 
					 | 
				
			||||||
			console.info('Agreeing to Terms of Service:');
 | 
					 | 
				
			||||||
			console.info(tos);
 | 
					 | 
				
			||||||
			console.info();
 | 
					 | 
				
			||||||
			console.info();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		agreed = true;
 | 
					 | 
				
			||||||
		return Promise.resolve(tos);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('New Subscriber Account');
 | 
					 | 
				
			||||||
		console.info(JSON.stringify(account, null, 2));
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (!agreed) {
 | 
					 | 
				
			||||||
		throw new Error('Failed to ask the user to agree to terms');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var certKeypair = await Keypairs.generate({ kty: srvKty });
 | 
					 | 
				
			||||||
	var pem = await Keypairs.export({
 | 
					 | 
				
			||||||
		jwk: certKeypair.private,
 | 
					 | 
				
			||||||
		encoding: 'pem'
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('Server Key Created');
 | 
					 | 
				
			||||||
		console.info('privkey.jwk.json');
 | 
					 | 
				
			||||||
		console.info(JSON.stringify(certKeypair, null, 2));
 | 
					 | 
				
			||||||
		// This should be saved as `privkey.pem`
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
		console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
 | 
					 | 
				
			||||||
		console.info(pem);
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 'subject' should be first in list
 | 
					 | 
				
			||||||
	var domains = randomDomains(rnd);
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('Get certificates for random domains:');
 | 
					 | 
				
			||||||
		console.info(
 | 
					 | 
				
			||||||
			domains
 | 
					 | 
				
			||||||
				.map(function(puny) {
 | 
					 | 
				
			||||||
					var uni = punycode.toUnicode(puny);
 | 
					 | 
				
			||||||
					if (puny !== uni) {
 | 
					 | 
				
			||||||
						return puny + ' (' + uni + ')';
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					return puny;
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
				.join('\n')
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create CSR
 | 
					 | 
				
			||||||
	var csrDer = await CSR.csr({
 | 
					 | 
				
			||||||
		jwk: certKeypair.private,
 | 
					 | 
				
			||||||
		domains: domains,
 | 
					 | 
				
			||||||
		encoding: 'der'
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	var csr = Enc.bufToUrlBase64(csrDer);
 | 
					 | 
				
			||||||
	var csrPem = PEM.packBlock({
 | 
					 | 
				
			||||||
		type: 'CERTIFICATE REQUEST',
 | 
					 | 
				
			||||||
		bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('Certificate Signing Request');
 | 
					 | 
				
			||||||
		console.info(csrPem);
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var results = await acme.certificates.create({
 | 
					 | 
				
			||||||
		account: account,
 | 
					 | 
				
			||||||
		accountKey: accountKey,
 | 
					 | 
				
			||||||
		csr: csr,
 | 
					 | 
				
			||||||
		domains: domains,
 | 
					 | 
				
			||||||
		challenges: challenges, // must be implemented
 | 
					 | 
				
			||||||
		customerEmail: null
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (config.debug) {
 | 
					 | 
				
			||||||
		console.info('Got SSL Certificate:');
 | 
					 | 
				
			||||||
		console.info(Object.keys(results));
 | 
					 | 
				
			||||||
		console.info(results.expires);
 | 
					 | 
				
			||||||
		console.info(results.cert);
 | 
					 | 
				
			||||||
		console.info(results.chain);
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
		console.info();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Try EC + RSA
 | 
					 | 
				
			||||||
var rnd = random();
 | 
					 | 
				
			||||||
happyPath('EC', 'RSA', rnd)
 | 
					 | 
				
			||||||
	.then(function() {
 | 
					 | 
				
			||||||
		// Now try RSA + EC
 | 
					 | 
				
			||||||
		rnd = random();
 | 
					 | 
				
			||||||
		return happyPath('RSA', 'EC', rnd).then(function() {
 | 
					 | 
				
			||||||
			console.info('success');
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	.catch(function(err) {
 | 
					 | 
				
			||||||
		console.error('Error:');
 | 
					 | 
				
			||||||
		console.error(err.stack);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function randomDomains(rnd) {
 | 
					 | 
				
			||||||
	return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
 | 
					 | 
				
			||||||
		function(pre) {
 | 
					 | 
				
			||||||
			return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function random() {
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		parseInt(
 | 
					 | 
				
			||||||
			Math.random()
 | 
					 | 
				
			||||||
				.toString()
 | 
					 | 
				
			||||||
				.slice(2, 99),
 | 
					 | 
				
			||||||
			10
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
			.toString(16)
 | 
					 | 
				
			||||||
			.slice(0, 4) + '例'
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										253
									
								
								tests/issue-certificates.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								tests/issue-certificates.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,253 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require('dotenv').config();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var CSR = require('@root/csr');
 | 
				
			||||||
 | 
					var Enc = require('@root/encoding/base64');
 | 
				
			||||||
 | 
					var PEM = require('@root/pem');
 | 
				
			||||||
 | 
					var punycode = require('punycode');
 | 
				
			||||||
 | 
					var ACME = require('../acme.js');
 | 
				
			||||||
 | 
					var Keypairs = require('@root/keypairs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
				
			||||||
 | 
					if (!process.env.CHALLENGE_OPTIONS) {
 | 
				
			||||||
 | 
						console.error(
 | 
				
			||||||
 | 
							'Please create a .env in the format of examples/example.env to run the tests'
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						process.exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var config = {
 | 
				
			||||||
 | 
						env: process.env.ENV,
 | 
				
			||||||
 | 
						email: process.env.SUBSCRIBER_EMAIL,
 | 
				
			||||||
 | 
						domain: process.env.BASE_DOMAIN,
 | 
				
			||||||
 | 
						challengeType: process.env.CHALLENGE_TYPE,
 | 
				
			||||||
 | 
						challengeModule: process.env.CHALLENGE_PLUGIN,
 | 
				
			||||||
 | 
						challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					//config.debug = !/^PROD/i.test(config.env);
 | 
				
			||||||
 | 
					var pluginPrefix = 'acme-' + config.challengeType + '-';
 | 
				
			||||||
 | 
					var pluginName = config.challengeModule;
 | 
				
			||||||
 | 
					var plugin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function() {
 | 
				
			||||||
 | 
						console.info('\n[Test] end-to-end issue certificates');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var acme = ACME.create({
 | 
				
			||||||
 | 
							// debug: true
 | 
				
			||||||
 | 
							maintainerEmail: config.email,
 | 
				
			||||||
 | 
							notify: function(ev, params) {
 | 
				
			||||||
 | 
								console.info(
 | 
				
			||||||
 | 
									'\t' + ev,
 | 
				
			||||||
 | 
									params.subject || params.altname || params.domain || '',
 | 
				
			||||||
 | 
									params.status || ''
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								if ('error' === ev) {
 | 
				
			||||||
 | 
									console.error(params.action || params.type || '');
 | 
				
			||||||
 | 
									console.error(params);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function badPlugin(err) {
 | 
				
			||||||
 | 
							if ('MODULE_NOT_FOUND' !== err.code) {
 | 
				
			||||||
 | 
								console.error(err);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							console.error("Couldn't find '" + pluginName + "'. Is it installed?");
 | 
				
			||||||
 | 
							console.error("\tnpm install --save-dev '" + pluginName + "'");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							plugin = require(pluginName);
 | 
				
			||||||
 | 
						} catch (err) {
 | 
				
			||||||
 | 
							if (
 | 
				
			||||||
 | 
								'MODULE_NOT_FOUND' !== err.code ||
 | 
				
			||||||
 | 
								0 === pluginName.indexOf(pluginPrefix)
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
 | 
								badPlugin(err);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								pluginName = pluginPrefix + pluginName;
 | 
				
			||||||
 | 
								plugin = require(pluginName);
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								badPlugin(e);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config.challenger = plugin.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(accKty, srvKty, rnd) {
 | 
				
			||||||
 | 
							var agreed = false;
 | 
				
			||||||
 | 
							var metadata = await acme.init(
 | 
				
			||||||
 | 
								'https://acme-staging-v02.api.letsencrypt.org/directory'
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Ready to use, show page
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('ACME.js initialized');
 | 
				
			||||||
 | 
								console.info(metadata);
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var accountKeypair = await Keypairs.generate({ kty: accKty });
 | 
				
			||||||
 | 
							var accountKey = accountKeypair.private;
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('Account Key Created');
 | 
				
			||||||
 | 
								console.info(JSON.stringify(accountKey, null, 2));
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var account = await acme.accounts.create({
 | 
				
			||||||
 | 
								agreeToTerms: agree,
 | 
				
			||||||
 | 
								// TODO detect jwk/pem/der?
 | 
				
			||||||
 | 
								accountKey: accountKey,
 | 
				
			||||||
 | 
								subscriberEmail: config.email
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO top-level agree
 | 
				
			||||||
 | 
							function agree(tos) {
 | 
				
			||||||
 | 
								if (config.debug) {
 | 
				
			||||||
 | 
									console.info('Agreeing to Terms of Service:');
 | 
				
			||||||
 | 
									console.info(tos);
 | 
				
			||||||
 | 
									console.info();
 | 
				
			||||||
 | 
									console.info();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								agreed = true;
 | 
				
			||||||
 | 
								return Promise.resolve(tos);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('New Subscriber Account');
 | 
				
			||||||
 | 
								console.info(JSON.stringify(account, null, 2));
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!agreed) {
 | 
				
			||||||
 | 
								throw new Error('Failed to ask the user to agree to terms');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var certKeypair = await Keypairs.generate({ kty: srvKty });
 | 
				
			||||||
 | 
							var pem = await Keypairs.export({
 | 
				
			||||||
 | 
								jwk: certKeypair.private,
 | 
				
			||||||
 | 
								encoding: 'pem'
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('Server Key Created');
 | 
				
			||||||
 | 
								console.info('privkey.jwk.json');
 | 
				
			||||||
 | 
								console.info(JSON.stringify(certKeypair, null, 2));
 | 
				
			||||||
 | 
								// This should be saved as `privkey.pem`
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
								console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
 | 
				
			||||||
 | 
								console.info(pem);
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 'subject' should be first in list
 | 
				
			||||||
 | 
							var domains = randomDomains(rnd);
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('Get certificates for random domains:');
 | 
				
			||||||
 | 
								console.info(
 | 
				
			||||||
 | 
									domains
 | 
				
			||||||
 | 
										.map(function(puny) {
 | 
				
			||||||
 | 
											var uni = punycode.toUnicode(puny);
 | 
				
			||||||
 | 
											if (puny !== uni) {
 | 
				
			||||||
 | 
												return puny + ' (' + uni + ')';
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											return puny;
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										.join('\n')
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create CSR
 | 
				
			||||||
 | 
							var csrDer = await CSR.csr({
 | 
				
			||||||
 | 
								jwk: certKeypair.private,
 | 
				
			||||||
 | 
								domains: domains,
 | 
				
			||||||
 | 
								encoding: 'der'
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							var csr = Enc.bufToUrlBase64(csrDer);
 | 
				
			||||||
 | 
							var csrPem = PEM.packBlock({
 | 
				
			||||||
 | 
								type: 'CERTIFICATE REQUEST',
 | 
				
			||||||
 | 
								bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('Certificate Signing Request');
 | 
				
			||||||
 | 
								console.info(csrPem);
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var results = await acme.certificates.create({
 | 
				
			||||||
 | 
								account: account,
 | 
				
			||||||
 | 
								accountKey: accountKey,
 | 
				
			||||||
 | 
								csr: csr,
 | 
				
			||||||
 | 
								domains: domains,
 | 
				
			||||||
 | 
								challenges: challenges, // must be implemented
 | 
				
			||||||
 | 
								customerEmail: null
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (config.debug) {
 | 
				
			||||||
 | 
								console.info('Got SSL Certificate:');
 | 
				
			||||||
 | 
								console.info(Object.keys(results));
 | 
				
			||||||
 | 
								console.info(results.expires);
 | 
				
			||||||
 | 
								console.info(results.cert);
 | 
				
			||||||
 | 
								console.info(results.chain);
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
								console.info();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Try EC + RSA
 | 
				
			||||||
 | 
						var rnd = random();
 | 
				
			||||||
 | 
						happyPath('EC', 'RSA', rnd)
 | 
				
			||||||
 | 
							.then(function() {
 | 
				
			||||||
 | 
								console.info('PASS: ECDSA account key with RSA server key');
 | 
				
			||||||
 | 
								// Now try RSA + EC
 | 
				
			||||||
 | 
								rnd = random();
 | 
				
			||||||
 | 
								return happyPath('RSA', 'EC', rnd).then(function() {
 | 
				
			||||||
 | 
									console.info('PASS: RSA account key with ECDSA server key');
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.then(function() {
 | 
				
			||||||
 | 
								console.info('PASS');
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch(function(err) {
 | 
				
			||||||
 | 
								console.error('Error:');
 | 
				
			||||||
 | 
								console.error(err.stack);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function randomDomains(rnd) {
 | 
				
			||||||
 | 
							return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
 | 
				
			||||||
 | 
								function(pre) {
 | 
				
			||||||
 | 
									return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function random() {
 | 
				
			||||||
 | 
							return (
 | 
				
			||||||
 | 
								parseInt(
 | 
				
			||||||
 | 
									Math.random()
 | 
				
			||||||
 | 
										.toString()
 | 
				
			||||||
 | 
										.slice(2, 99),
 | 
				
			||||||
 | 
									10
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
									.toString(16)
 | 
				
			||||||
 | 
									.slice(0, 4) + '例'
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										27
									
								
								utils.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								utils.js
									
									
									
									
									
								
							@ -122,29 +122,26 @@ U._setNonce = function(me, nonce) {
 | 
				
			|||||||
	me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
 | 
						me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
U._importKeypair = function(me, kp) {
 | 
					U._importKeypair = function(me, key) {
 | 
				
			||||||
	var jwk = kp.privateKeyJwk;
 | 
					 | 
				
			||||||
	if (kp.kty) {
 | 
					 | 
				
			||||||
		jwk = kp;
 | 
					 | 
				
			||||||
		kp = {};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var pub;
 | 
					 | 
				
			||||||
	var p;
 | 
						var p;
 | 
				
			||||||
	if (jwk) {
 | 
						var pub;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (key && key.kty) {
 | 
				
			||||||
		// nix the browser jwk extras
 | 
							// nix the browser jwk extras
 | 
				
			||||||
		jwk.key_ops = undefined;
 | 
							key.key_ops = undefined;
 | 
				
			||||||
		jwk.ext = undefined;
 | 
							key.ext = undefined;
 | 
				
			||||||
		pub = Keypairs.neuter({ jwk: jwk });
 | 
							pub = Keypairs.neuter({ jwk: key });
 | 
				
			||||||
		p = Promise.resolve({
 | 
							p = Promise.resolve({
 | 
				
			||||||
			private: jwk,
 | 
								private: key,
 | 
				
			||||||
			public: pub
 | 
								public: pub
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						} else if ('string' === typeof key) {
 | 
				
			||||||
 | 
							p = Keypairs.import({ pem: key });
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		p = Keypairs.import({ pem: kp.privateKeyPem });
 | 
							throw new Error('no private key given');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return p.then(function(pair) {
 | 
						return p.then(function(pair) {
 | 
				
			||||||
		kp.privateKeyJwk = pair.private;
 | 
					 | 
				
			||||||
		kp.publicKeyJwk = pair.public;
 | 
					 | 
				
			||||||
		if (pair.public.kid) {
 | 
							if (pair.public.kid) {
 | 
				
			||||||
			pair = JSON.parse(JSON.stringify(pair));
 | 
								pair = JSON.parse(JSON.stringify(pair));
 | 
				
			||||||
			delete pair.public.kid;
 | 
								delete pair.public.kid;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user