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) | ||||
| 
 | ||||
| @ -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. | ||||
| 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 | ||||
| 
 | ||||
| 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 | ||||
| var certinfo = await acme.certificates.create({ | ||||
| 	agreeToTerms: function(tos) { | ||||
| 		return tos; | ||||
| 	}, | ||||
| 	account: account, | ||||
| 	accountKey: accountPrivateJwk, | ||||
| 	csr: csr, | ||||
|  | ||||
| @ -18,9 +18,11 @@ native._canCheck = function(me) { | ||||
| }; | ||||
| 
 | ||||
| native._dns01 = function(me, ch) { | ||||
| 	return new me.request({ | ||||
| 	return me | ||||
| 		.request({ | ||||
| 			url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT' | ||||
| 	}).then(function(resp) { | ||||
| 		}) | ||||
| 		.then(function(resp) { | ||||
| 			var err; | ||||
| 			if (!resp.body || !Array.isArray(resp.body.answer)) { | ||||
| 				err = new Error('failed to get DNS response'); | ||||
| @ -42,9 +44,11 @@ native._dns01 = function(me, ch) { | ||||
| 
 | ||||
| native._http01 = function(me, ch) { | ||||
| 	var url = encodeURIComponent(ch.challengeUrl); | ||||
| 	return new me.request({ | ||||
| 	return me | ||||
| 		.request({ | ||||
| 			url: me._baseUrl + '/api/http?url=' + url | ||||
| 	}).then(function(resp) { | ||||
| 		}) | ||||
| 		.then(function(resp) { | ||||
| 			return resp.body; | ||||
| 		}); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var UserAgent = module.exports; | ||||
| UserAgent.get = function () { | ||||
| UserAgent.get = function() { | ||||
| 	return false; | ||||
| }; | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| var native = module.exports; | ||||
| var promisify = require('util').promisify; | ||||
| var resolveTxt = promisify(require('dns').resolveTxt); | ||||
| var crypto = require('crypto'); | ||||
| 
 | ||||
| native._canCheck = function(me) { | ||||
| 	me._canCheck = {}; | ||||
| @ -31,3 +32,57 @@ native._http01 = function(me, ch) { | ||||
| 		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'; | ||||
| 
 | ||||
| var os = require('os'); | ||||
| var ver = require('../../package.json'); | ||||
| var ver = require('../../package.json').version; | ||||
| 
 | ||||
| var UserAgent = module.exports; | ||||
| UserAgent.get = function(me) { | ||||
|  | ||||
| @ -5,15 +5,5 @@ var promisify = require('util').promisify; | ||||
| var request = promisify(require('@root/request')); | ||||
| 
 | ||||
| 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); | ||||
| }; | ||||
|  | ||||
| @ -2,12 +2,14 @@ | ||||
| 
 | ||||
| require('dotenv').config(); | ||||
| 
 | ||||
| var pkg = require('../package.json'); | ||||
| 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'); | ||||
| var ecJwk = require('../fixtures/account.jwk.json'); | ||||
| 
 | ||||
| // TODO exec npm install --save-dev CHALLENGE_MODULE
 | ||||
| if (!process.env.CHALLENGE_OPTIONS) { | ||||
| @ -36,6 +38,7 @@ module.exports = function() { | ||||
| 	var acme = ACME.create({ | ||||
| 		// debug: true
 | ||||
| 		maintainerEmail: config.email, | ||||
| 		packageAgent: 'test-' + pkg.name + '/' + pkg.version, | ||||
| 		notify: function(ev, params) { | ||||
| 			console.info( | ||||
| 				'\t' + ev, | ||||
| @ -104,6 +107,10 @@ module.exports = function() { | ||||
| 		} | ||||
| 
 | ||||
| 		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; | ||||
| 		if (config.debug) { | ||||
| 			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 | ||||
| 						); | ||||
| 					}); | ||||
| 			}); | ||||
| 	}); | ||||
							
								
								
									
										17
									
								
								utils.js
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								utils.js
									
									
									
									
									
								
							| @ -82,26 +82,25 @@ U._request = function(me, opts) { | ||||
| 	if (ua && !opts.headers['User-Agent']) { | ||||
| 		opts.headers['User-Agent'] = ua; | ||||
| 	} | ||||
| 	if (opts.json && true !== opts.json) { | ||||
| 		opts.headers['Content-Type'] = 'application/jose+json'; | ||||
| 	if (opts.json) { | ||||
| 		opts.headers.Accept = 'application/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) { | ||||
| 		opts.method = 'GET'; | ||||
| 		if (opts.body) { | ||||
| 			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(opts);
 | ||||
| 	return me.request(opts).then(function(resp) { | ||||
| 	return me.__request(opts).then(function(resp) { | ||||
| 		if (resp.toJSON) { | ||||
| 			resp = resp.toJSON(); | ||||
| 		} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user