mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	request cleanup
This commit is contained in:
		
							parent
							
								
									54cda5a888
								
							
						
					
					
						commit
						b39a3763cf
					
				
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| # [ACME.js](https://git.rootprojects.org/root/acme.js) v3 | # [ACME.js](https://git.rootprojects.org/root/acme.js) (RFC 8555 / November 2019) | ||||||
| 
 | 
 | ||||||
| | Built by [Root](https://therootcompany.com) for [Greenlock](https://greenlock.domains) | | Built by [Root](https://therootcompany.com) for [Greenlock](https://greenlock.domains) | ||||||
| 
 | 
 | ||||||
| @ -52,6 +52,31 @@ If they don't, please open an issue to let us know why. | |||||||
| We'd much rather improve the app than have a hundred different versions running in the wild. | We'd much rather improve the app than have a hundred different versions running in the wild. | ||||||
| However, in keeping to our values we've made the source visible for others to inspect, improve, and modify. | However, in keeping to our values we've made the source visible for others to inspect, improve, and modify. | ||||||
| 
 | 
 | ||||||
|  | # API Overview | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | ACME.create({ maintainerEmail, packageAgent }); | ||||||
|  | acme.init(directoryUrl); | ||||||
|  | acme.accounts.create({ subscriberEmail, agreeToTerms, accountKey }); | ||||||
|  | acme.certificates.create({ | ||||||
|  | 	customerEmail, // do not use | ||||||
|  | 	account, | ||||||
|  | 	accountKey, | ||||||
|  | 	serverKey, | ||||||
|  | 	csr, | ||||||
|  | 	domains, | ||||||
|  | 	challenges | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | ACME.computeChallenge({ | ||||||
|  | 	accountKey: jwk, | ||||||
|  | 	hostname: 'example.com', | ||||||
|  | 	challenge: { type: 'dns-01', token: 'xxxx' } | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| # Install | # Install | ||||||
| 
 | 
 | ||||||
| To make it easy to generate, encode, and decode keys and certificates, | To make it easy to generate, encode, and decode keys and certificates, | ||||||
| @ -234,9 +259,6 @@ is a required part of the process, which requires `set` and `remove` callbacks/p | |||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var certinfo = await acme.certificates.create({ | var certinfo = await acme.certificates.create({ | ||||||
| 	agreeToTerms: function(tos) { |  | ||||||
| 		return tos; |  | ||||||
| 	}, |  | ||||||
| 	account: account, | 	account: account, | ||||||
| 	accountKey: accountPrivateJwk, | 	accountKey: accountPrivateJwk, | ||||||
| 	csr: csr, | 	csr: csr, | ||||||
|  | |||||||
| @ -18,33 +18,37 @@ native._canCheck = function(me) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| native._dns01 = function(me, ch) { | native._dns01 = function(me, ch) { | ||||||
| 	return new me.request({ | 	return me | ||||||
| 		url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT' | 		.request({ | ||||||
| 	}).then(function(resp) { | 			url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT' | ||||||
| 		var err; | 		}) | ||||||
| 		if (!resp.body || !Array.isArray(resp.body.answer)) { | 		.then(function(resp) { | ||||||
| 			err = new Error('failed to get DNS response'); | 			var err; | ||||||
| 			console.error(err); | 			if (!resp.body || !Array.isArray(resp.body.answer)) { | ||||||
| 			throw err; | 				err = new Error('failed to get DNS response'); | ||||||
| 		} | 				console.error(err); | ||||||
| 		if (!resp.body.answer.length) { | 				throw err; | ||||||
| 			err = new Error('failed to get DNS answer record in response'); | 			} | ||||||
| 			console.error(err); | 			if (!resp.body.answer.length) { | ||||||
| 			throw err; | 				err = new Error('failed to get DNS answer record in response'); | ||||||
| 		} | 				console.error(err); | ||||||
| 		return { | 				throw err; | ||||||
| 			answer: resp.body.answer.map(function(ans) { | 			} | ||||||
| 				return { data: ans.data, ttl: ans.ttl }; | 			return { | ||||||
| 			}) | 				answer: resp.body.answer.map(function(ans) { | ||||||
| 		}; | 					return { data: ans.data, ttl: ans.ttl }; | ||||||
| 	}); | 				}) | ||||||
|  | 			}; | ||||||
|  | 		}); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| native._http01 = function(me, ch) { | native._http01 = function(me, ch) { | ||||||
| 	var url = encodeURIComponent(ch.challengeUrl); | 	var url = encodeURIComponent(ch.challengeUrl); | ||||||
| 	return new me.request({ | 	return me | ||||||
| 		url: me._baseUrl + '/api/http?url=' + url | 		.request({ | ||||||
| 	}).then(function(resp) { | 			url: me._baseUrl + '/api/http?url=' + url | ||||||
| 		return resp.body; | 		}) | ||||||
| 	}); | 		.then(function(resp) { | ||||||
|  | 			return resp.body; | ||||||
|  | 		}); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var UserAgent = module.exports; | var UserAgent = module.exports; | ||||||
| UserAgent.get = function () { | UserAgent.get = function() { | ||||||
|   return false; | 	return false; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| var native = module.exports; | var native = module.exports; | ||||||
| var promisify = require('util').promisify; | var promisify = require('util').promisify; | ||||||
| var resolveTxt = promisify(require('dns').resolveTxt); | var resolveTxt = promisify(require('dns').resolveTxt); | ||||||
|  | var crypto = require('crypto'); | ||||||
| 
 | 
 | ||||||
| native._canCheck = function(me) { | native._canCheck = function(me) { | ||||||
| 	me._canCheck = {}; | 	me._canCheck = {}; | ||||||
| @ -31,3 +32,57 @@ native._http01 = function(me, ch) { | |||||||
| 		return resp.body; | 		return resp.body; | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | // the hashcash here is for browser parity only
 | ||||||
|  | // basically we ask the client to find a needle in a haystack
 | ||||||
|  | // (very similar to CloudFlare's api protection)
 | ||||||
|  | native._hashcash = function(ch) { | ||||||
|  | 	if (!ch || !ch.nonce) { | ||||||
|  | 		ch = { nonce: 'xxx' }; | ||||||
|  | 	} | ||||||
|  | 	return Promise.resolve() | ||||||
|  | 		.then(function() { | ||||||
|  | 			// only get easy answers
 | ||||||
|  | 			var len = ch.needle.length; | ||||||
|  | 			var start = ch.start || 0; | ||||||
|  | 			var end = ch.end || Math.ceil(len / 2); | ||||||
|  | 			var window = parseInt(end - start, 10) || 0; | ||||||
|  | 
 | ||||||
|  | 			var maxLen = 6; | ||||||
|  | 			var maxTries = Math.pow(2, maxLen * 8); | ||||||
|  | 			if ( | ||||||
|  | 				len > maxLen || | ||||||
|  | 				window < Math.ceil(len / 2) || | ||||||
|  | 				ch.needle.toLowerCase() !== ch.needle || | ||||||
|  | 				ch.alg !== 'SHA-256' | ||||||
|  | 			) { | ||||||
|  | 				// bail unless the server is issuing very easy challenges
 | ||||||
|  | 				throw new Error('possible and easy answers only, please'); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var haystack; | ||||||
|  | 			var i; | ||||||
|  | 			var answer; | ||||||
|  | 			var needle = Buffer.from(ch.needle, 'hex'); | ||||||
|  | 			for (i = 0; i < maxTries; i += 1) { | ||||||
|  | 				answer = i.toString(16); | ||||||
|  | 				if (answer.length % 2) { | ||||||
|  | 					answer = '0' + answer; | ||||||
|  | 				} | ||||||
|  | 				haystack = crypto | ||||||
|  | 					.createHash('sha256') | ||||||
|  | 					.update(Buffer.from(ch.nonce + answer, 'hex')) | ||||||
|  | 					.digest() | ||||||
|  | 					.slice(ch.start, ch.end); | ||||||
|  | 				if (-1 !== haystack.indexOf(needle)) { | ||||||
|  | 					return ch.nonce + ':' + answer; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return ch.nonce + ':xxx'; | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			//console.log('[debug]', err);
 | ||||||
|  | 			// ignore any error
 | ||||||
|  | 			return ch.nonce + ':xxx'; | ||||||
|  | 		}); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var os = require('os'); | var os = require('os'); | ||||||
| var ver = require('../../package.json'); | var ver = require('../../package.json').version; | ||||||
| 
 | 
 | ||||||
| var UserAgent = module.exports; | var UserAgent = module.exports; | ||||||
| UserAgent.get = function(me) { | UserAgent.get = function(me) { | ||||||
|  | |||||||
| @ -5,15 +5,5 @@ var promisify = require('util').promisify; | |||||||
| var request = promisify(require('@root/request')); | var request = promisify(require('@root/request')); | ||||||
| 
 | 
 | ||||||
| http.request = function(opts) { | http.request = function(opts) { | ||||||
| 	if (!opts.headers) { |  | ||||||
| 		opts.headers = {}; |  | ||||||
| 	} |  | ||||||
| 	if ( |  | ||||||
| 		!Object.keys(opts.headers).some(function(key) { |  | ||||||
| 			return 'user-agent' === key.toLowerCase(); |  | ||||||
| 		}) |  | ||||||
| 	) { |  | ||||||
| 		// TODO opts.headers['User-Agent'] = 'TODO';
 |  | ||||||
| 	} |  | ||||||
| 	return request(opts); | 	return request(opts); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -2,12 +2,14 @@ | |||||||
| 
 | 
 | ||||||
| require('dotenv').config(); | require('dotenv').config(); | ||||||
| 
 | 
 | ||||||
|  | var pkg = require('../package.json'); | ||||||
| var CSR = require('@root/csr'); | var CSR = require('@root/csr'); | ||||||
| var Enc = require('@root/encoding/base64'); | var Enc = require('@root/encoding/base64'); | ||||||
| var PEM = require('@root/pem'); | var PEM = require('@root/pem'); | ||||||
| var punycode = require('punycode'); | var punycode = require('punycode'); | ||||||
| var ACME = require('../acme.js'); | var ACME = require('../acme.js'); | ||||||
| var Keypairs = require('@root/keypairs'); | var Keypairs = require('@root/keypairs'); | ||||||
|  | var ecJwk = require('../fixtures/account.jwk.json'); | ||||||
| 
 | 
 | ||||||
| // TODO exec npm install --save-dev CHALLENGE_MODULE
 | // TODO exec npm install --save-dev CHALLENGE_MODULE
 | ||||||
| if (!process.env.CHALLENGE_OPTIONS) { | if (!process.env.CHALLENGE_OPTIONS) { | ||||||
| @ -36,6 +38,7 @@ module.exports = function() { | |||||||
| 	var acme = ACME.create({ | 	var acme = ACME.create({ | ||||||
| 		// debug: true
 | 		// debug: true
 | ||||||
| 		maintainerEmail: config.email, | 		maintainerEmail: config.email, | ||||||
|  | 		packageAgent: 'test-' + pkg.name + '/' + pkg.version, | ||||||
| 		notify: function(ev, params) { | 		notify: function(ev, params) { | ||||||
| 			console.info( | 			console.info( | ||||||
| 				'\t' + ev, | 				'\t' + ev, | ||||||
| @ -104,6 +107,10 @@ module.exports = function() { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		var accountKeypair = await Keypairs.generate({ kty: accKty }); | 		var accountKeypair = await Keypairs.generate({ kty: accKty }); | ||||||
|  | 		if (/EC/i.test(accKty)) { | ||||||
|  | 			// to test that an existing account gets back data
 | ||||||
|  | 			accountKeypair = ecJwk; | ||||||
|  | 		} | ||||||
| 		var accountKey = accountKeypair.private; | 		var accountKey = accountKeypair.private; | ||||||
| 		if (config.debug) { | 		if (config.debug) { | ||||||
| 			console.info('Account Key Created'); | 			console.info('Account Key Created'); | ||||||
|  | |||||||
							
								
								
									
										74
									
								
								tests/maintainer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								tests/maintainer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var native = require('../lib/native.js'); | ||||||
|  | var crypto = require('crypto'); | ||||||
|  | 
 | ||||||
|  | native | ||||||
|  | 	._hashcash({ | ||||||
|  | 		alg: 'SHA-256', | ||||||
|  | 		nonce: '00', | ||||||
|  | 		needle: '0000', | ||||||
|  | 		start: 0, | ||||||
|  | 		end: 2 | ||||||
|  | 	}) | ||||||
|  | 	.then(function(hashcash) { | ||||||
|  | 		if ('00:76de' !== hashcash) { | ||||||
|  | 			throw new Error('hashcash algorthim changed'); | ||||||
|  | 		} | ||||||
|  | 		console.info('PASS: known hash solves correctly'); | ||||||
|  | 
 | ||||||
|  | 		return native | ||||||
|  | 			._hashcash({ | ||||||
|  | 				alg: 'SHA-256', | ||||||
|  | 				nonce: '10', | ||||||
|  | 				needle: '', | ||||||
|  | 				start: 0, | ||||||
|  | 				end: 2 | ||||||
|  | 			}) | ||||||
|  | 			.then(function(hashcash) { | ||||||
|  | 				if ('10:00' !== hashcash) { | ||||||
|  | 					throw new Error('hashcash algorthim changed'); | ||||||
|  | 				} | ||||||
|  | 				console.info('PASS: empty hash solves correctly'); | ||||||
|  | 
 | ||||||
|  | 				var now = Date.now(); | ||||||
|  | 				var nonce = '20'; | ||||||
|  | 				var needle = crypto | ||||||
|  | 					.randomBytes(3) | ||||||
|  | 					.toString('hex') | ||||||
|  | 					.slice(0, 5); | ||||||
|  | 				native | ||||||
|  | 					._hashcash({ | ||||||
|  | 						alg: 'SHA-256', | ||||||
|  | 						nonce: nonce, | ||||||
|  | 						needle: needle, | ||||||
|  | 						start: 0, | ||||||
|  | 						end: Math.ceil(needle.length / 2) | ||||||
|  | 					}) | ||||||
|  | 					.then(function(hashcash) { | ||||||
|  | 						var later = Date.now(); | ||||||
|  | 						var parts = hashcash.split(':'); | ||||||
|  | 						var answer = parts[1]; | ||||||
|  | 						if (parts[0] !== nonce) { | ||||||
|  | 							throw new Error('incorrect nonce'); | ||||||
|  | 						} | ||||||
|  | 						var haystack = crypto | ||||||
|  | 							.createHash('sha256') | ||||||
|  | 							.update(Buffer.from(nonce + answer, 'hex')) | ||||||
|  | 							.digest() | ||||||
|  | 							.slice(0, Math.ceil(needle.length / 2)); | ||||||
|  | 						if ( | ||||||
|  | 							-1 === haystack.indexOf(Buffer.from(needle, 'hex')) | ||||||
|  | 						) { | ||||||
|  | 							throw new Error('incorrect solution'); | ||||||
|  | 						} | ||||||
|  | 						if (later - now > 2000) { | ||||||
|  | 							throw new Error('took too long to solve'); | ||||||
|  | 						} | ||||||
|  | 						console.info( | ||||||
|  | 							'PASS: rando hash solves correctly (and in good time - %dms)', | ||||||
|  | 							later - now | ||||||
|  | 						); | ||||||
|  | 					}); | ||||||
|  | 			}); | ||||||
|  | 	}); | ||||||
							
								
								
									
										19
									
								
								utils.js
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								utils.js
									
									
									
									
									
								
							| @ -82,9 +82,14 @@ U._request = function(me, opts) { | |||||||
| 	if (ua && !opts.headers['User-Agent']) { | 	if (ua && !opts.headers['User-Agent']) { | ||||||
| 		opts.headers['User-Agent'] = ua; | 		opts.headers['User-Agent'] = ua; | ||||||
| 	} | 	} | ||||||
| 	if (opts.json && true !== opts.json) { | 	if (opts.json) { | ||||||
| 		opts.headers['Content-Type'] = 'application/jose+json'; | 		opts.headers.Accept = 'application/json'; | ||||||
| 		opts.body = JSON.stringify(opts.json); | 		if (true !== opts.json) { | ||||||
|  | 			opts.body = JSON.stringify(opts.json); | ||||||
|  | 		} | ||||||
|  | 		if (/*opts.jose ||*/ opts.json.protected) { | ||||||
|  | 			opts.headers['Content-Type'] = 'application/jose+json'; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	if (!opts.method) { | 	if (!opts.method) { | ||||||
| 		opts.method = 'GET'; | 		opts.method = 'GET'; | ||||||
| @ -92,16 +97,10 @@ U._request = function(me, opts) { | |||||||
| 			opts.method = 'POST'; | 			opts.method = 'POST'; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if (opts.json) { |  | ||||||
| 		opts.headers.Accept = 'application/json'; |  | ||||||
| 		if (true !== opts.json) { |  | ||||||
| 			opts.body = JSON.stringify(opts.json); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	//console.log('\n[debug] REQUEST');
 | 	//console.log('\n[debug] REQUEST');
 | ||||||
| 	//console.log(opts);
 | 	//console.log(opts);
 | ||||||
| 	return me.request(opts).then(function(resp) { | 	return me.__request(opts).then(function(resp) { | ||||||
| 		if (resp.toJSON) { | 		if (resp.toJSON) { | ||||||
| 			resp = resp.toJSON(); | 			resp = resp.toJSON(); | ||||||
| 		} | 		} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user