mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	update API and tests
This commit is contained in:
		
							parent
							
								
									f05e9db38e
								
							
						
					
					
						commit
						0efa94eeb0
					
				
							
								
								
									
										263
									
								
								acme.js
									
									
									
									
									
								
							
							
						
						
									
										263
									
								
								acme.js
									
									
									
									
									
								
							| @ -179,17 +179,17 @@ ACME._testChallengeOptions = function() { | ||||
| 
 | ||||
| ACME._thumber = function(me, options, thumb) { | ||||
| 	var thumbPromise; | ||||
| 	return function() { | ||||
| 	return function(key) { | ||||
| 		if (thumb) { | ||||
| 			return Promise.resolve(thumb); | ||||
| 		} | ||||
| 		if (thumbPromise) { | ||||
| 			return thumbPromise; | ||||
| 		} | ||||
| 		thumbPromise = U._importKeypair( | ||||
| 			me, | ||||
| 			options.accountKey || options.accountKeypair | ||||
| 		).then(function(pair) { | ||||
| 		if (!key) { | ||||
| 			key = options.accountKey || options.accountKeypair; | ||||
| 		} | ||||
| 		thumbPromise = U._importKeypair(null, key).then(function(pair) { | ||||
| 			return Keypairs.thumbprint({ | ||||
| 				jwk: pair.public | ||||
| 			}); | ||||
| @ -266,7 +266,14 @@ ACME._dryRun = function(me, realOptions) { | ||||
| 					type: ch.type | ||||
| 					//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,95 +317,117 @@ 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( | ||||
| 			request.challenges.map(function(challenge) { | ||||
| 				// Don't do extra work for challenges that we can't satisfy
 | ||||
| 				if (!options._presenterTypes.includes(challenge.type)) { | ||||
| 					return null; | ||||
| 				} | ||||
| 	return Promise.all( | ||||
| 		request.challenges.map(function(challenge) { | ||||
| 			// Don't do extra work for challenges that we can't satisfy
 | ||||
| 			if (!options._presenterTypes.includes(challenge.type)) { | ||||
| 				return null; | ||||
| 			} | ||||
| 
 | ||||
| 				var auth = {}; | ||||
| 			var auth = {}; | ||||
| 
 | ||||
| 				// straight copy from the new order response
 | ||||
| 				// { identifier, status, expires, challenges, wildcard }
 | ||||
| 				Object.keys(request).forEach(function(key) { | ||||
| 					auth[key] = request[key]; | ||||
| 			// straight copy from the new order response
 | ||||
| 			// { identifier, status, expires, challenges, wildcard }
 | ||||
| 			Object.keys(request).forEach(function(key) { | ||||
| 				auth[key] = request[key]; | ||||
| 			}); | ||||
| 
 | ||||
| 			// copy from the challenge we've chosen
 | ||||
| 			// { type, status, url, token }
 | ||||
| 			// (note the duplicate status overwrites the one above, but they should be the same)
 | ||||
| 			Object.keys(challenge).forEach(function(key) { | ||||
| 				// don't confused devs with the id url
 | ||||
| 				auth[key] = challenge[key]; | ||||
| 			}); | ||||
| 
 | ||||
| 			// 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); | ||||
| 
 | ||||
| 			var zone = pluckZone( | ||||
| 				options.zonenames || [], | ||||
| 				auth.identifier.value | ||||
| 			); | ||||
| 
 | ||||
| 			return ACME.computeChallenge({ | ||||
| 				accountKey: options.accountKey, | ||||
| 				_getThumbprint: getThumbprint, | ||||
| 				challenge: auth, | ||||
| 				zone: zone, | ||||
| 				dnsPrefix: dnsPrefix | ||||
| 			}).then(function(resp) { | ||||
| 				Object.keys(resp).forEach(function(k) { | ||||
| 					auth[k] = resp[k]; | ||||
| 				}); | ||||
| 				return auth; | ||||
| 			}); | ||||
| 		}) | ||||
| 	).then(function(auths) { | ||||
| 		return auths.filter(Boolean); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| 				// copy from the challenge we've chosen
 | ||||
| 				// { type, status, url, token }
 | ||||
| 				// (note the duplicate status overwrites the one above, but they should be the same)
 | ||||
| 				Object.keys(challenge).forEach(function(key) { | ||||
| 					// don't confused devs with the id url
 | ||||
| 					auth[key] = challenge[key]; | ||||
| 				}); | ||||
| 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']; | ||||
| 
 | ||||
| 				// 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 getThumbprint(accountKey).then(function(thumb) { | ||||
| 		var resp = {}; | ||||
| 		resp.thumbprint = thumb; | ||||
| 		//   keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
 | ||||
| 		resp.keyAuthorization = auth.token + '.' + thumb; | ||||
| 
 | ||||
| 				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 ?
 | ||||
| 			resp.challengeUrl = | ||||
| 				'http://' + | ||||
| 				// `hostname` is an alias of `auth.indentifier.value`
 | ||||
| 				hostname + | ||||
| 				ACME.challengePrefixes['http-01'] + | ||||
| 				'/' + | ||||
| 				auth.token; | ||||
| 		} | ||||
| 
 | ||||
| 				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 resp; | ||||
| 		} | ||||
| 
 | ||||
| 				if ('dns-01' !== auth.type) { | ||||
| 					return auth; | ||||
| 				} | ||||
| 
 | ||||
| 				var zone = pluckZone( | ||||
| 					options.zonenames || [], | ||||
| 					auth.identifier.value | ||||
| 				); | ||||
| 
 | ||||
| 				// 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.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; | ||||
| 					}); | ||||
| 		// 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(auths) { | ||||
| 			return auths.filter(Boolean); | ||||
| 		}); | ||||
| 			.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 valids = []; | ||||
| 	var auths = []; | ||||
| 	var placed = []; | ||||
| 	var USE_DNS = false; | ||||
| 	var DNS_DELAY = 0; | ||||
| 
 | ||||
| @ -618,6 +648,7 @@ ACME._setChallenges = function(me, options, order) { | ||||
| 					); | ||||
| 				} | ||||
| 				auths.push(selected); | ||||
| 				placed.push(selected); | ||||
| 				ACME._notify(me, options, 'challenge_select', { | ||||
| 					// API-locked
 | ||||
| 					altname: ACME._untame( | ||||
| @ -651,10 +682,13 @@ ACME._setChallenges = function(me, options, order) { | ||||
| 	function waitAll() { | ||||
| 		//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
 | ||||
| 		if (!DNS_DELAY || DNS_DELAY <= 0) { | ||||
| 			console.warn( | ||||
| 				'the given dns-01 challenge did not specify `propagationDelay`' | ||||
| 			); | ||||
| 			console.warn('the default of 5000ms will be used'); | ||||
| 			if (!ACME._propagationDelayWarning) { | ||||
| 				console.warn( | ||||
| 					'warn: the given dns-01 challenge did not specify `propagationDelay`' | ||||
| 				); | ||||
| 				console.warn('warn: the default of 5000ms will be used'); | ||||
| 				ACME._propagationDelayWarning = true; | ||||
| 			} | ||||
| 			DNS_DELAY = 5000; | ||||
| 		} | ||||
| 		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.
 | ||||
| 	return setNext() | ||||
| 		.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) { | ||||
| @ -1283,40 +1332,6 @@ ACME._prnd = function(n) { | ||||
| ACME._toHex = function(pair) { | ||||
| 	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) { | ||||
| 	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' | ||||
| ]; | ||||
| 
 | ||||
| function formatPemChain(str) { | ||||
| 	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'; | ||||
| 		}); | ||||
| } | ||||
| var ACME = require('../'); | ||||
| 
 | ||||
| tests.forEach(function(str) { | ||||
| 	var actual = formatPemChain(str); | ||||
| 	if (expected !== actual) { | ||||
| 		console.error('input:   ', JSON.stringify(str)); | ||||
| 		console.error('expected:', JSON.stringify(expected)); | ||||
| 		console.error('actual:  ', JSON.stringify(actual)); | ||||
| 		throw new Error('did not pass'); | ||||
| module.exports = function() { | ||||
| 	console.info('\n[Test] can split and format PEM chain properly'); | ||||
| 
 | ||||
| 	tests.forEach(function(str) { | ||||
| 		var actual = ACME.formatPemChain(str); | ||||
| 		if (expected !== actual) { | ||||
| 			console.error('input:   ', JSON.stringify(str)); | ||||
| 			console.error('expected:', JSON.stringify(expected)); | ||||
| 			console.error('actual:  ', JSON.stringify(actual)); | ||||
| 			throw new Error('did not pass'); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	if ( | ||||
| 		'----\nxxxx\nyyyy\n----\n' !== | ||||
| 		ACME.formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n') | ||||
| 	) { | ||||
| 		throw new Error('Not proper for single cert in chain'); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| if ( | ||||
| 	'----\nxxxx\nyyyy\n----\n' !== | ||||
| 	formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n') | ||||
| ) { | ||||
| 	throw new Error('Not proper for single cert in chain'); | ||||
| } | ||||
| 
 | ||||
| if ( | ||||
| 	'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !== | ||||
| 	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' | ||||
| 	) | ||||
| ) { | ||||
| 	throw new Error('Not proper for three certs in chain'); | ||||
| } | ||||
| 
 | ||||
| splitPemChain( | ||||
| 	'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' | ||||
| ).forEach(function(str) { | ||||
| 	if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) { | ||||
| 		throw new Error('bad thingy'); | ||||
| 	if ( | ||||
| 		'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !== | ||||
| 		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' | ||||
| 		) | ||||
| 	) { | ||||
| 		throw new Error('Not proper for three certs in chain'); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| console.info('PASS'); | ||||
| 	ACME.splitPemChain( | ||||
| 		'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' | ||||
| 	).forEach(function(str) { | ||||
| 		if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) { | ||||
| 			throw new Error('bad thingy'); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	console.info('PASS'); | ||||
| 
 | ||||
|   return Promise.resolve(); | ||||
| }; | ||||
| @ -1,15 +1,27 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| async function run() { | ||||
| module.exports = async function() { | ||||
| 	console.log('[Test] can generate, export, and import key'); | ||||
| 	var Keypairs = require('@root/keypairs'); | ||||
| 
 | ||||
| 	var certKeypair = await Keypairs.generate({ kty: 'RSA' }); | ||||
| 	console.log(certKeypair); | ||||
| 	//console.log(certKeypair);
 | ||||
| 	var pem = await Keypairs.export({ | ||||
| 		jwk: certKeypair.private, | ||||
| 		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() }); | ||||
| }; | ||||
| 
 | ||||
| U._importKeypair = function(me, kp) { | ||||
| 	var jwk = kp.privateKeyJwk; | ||||
| 	if (kp.kty) { | ||||
| 		jwk = kp; | ||||
| 		kp = {}; | ||||
| 	} | ||||
| 	var pub; | ||||
| U._importKeypair = function(me, key) { | ||||
| 	var p; | ||||
| 	if (jwk) { | ||||
| 	var pub; | ||||
| 
 | ||||
| 	if (key && key.kty) { | ||||
| 		// nix the browser jwk extras
 | ||||
| 		jwk.key_ops = undefined; | ||||
| 		jwk.ext = undefined; | ||||
| 		pub = Keypairs.neuter({ jwk: jwk }); | ||||
| 		key.key_ops = undefined; | ||||
| 		key.ext = undefined; | ||||
| 		pub = Keypairs.neuter({ jwk: key }); | ||||
| 		p = Promise.resolve({ | ||||
| 			private: jwk, | ||||
| 			private: key, | ||||
| 			public: pub | ||||
| 		}); | ||||
| 	} else if ('string' === typeof key) { | ||||
| 		p = Keypairs.import({ pem: key }); | ||||
| 	} else { | ||||
| 		p = Keypairs.import({ pem: kp.privateKeyPem }); | ||||
| 		throw new Error('no private key given'); | ||||
| 	} | ||||
| 
 | ||||
| 	return p.then(function(pair) { | ||||
| 		kp.privateKeyJwk = pair.private; | ||||
| 		kp.publicKeyJwk = pair.public; | ||||
| 		if (pair.public.kid) { | ||||
| 			pair = JSON.parse(JSON.stringify(pair)); | ||||
| 			delete pair.public.kid; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user