forked from root/acme.js
		
	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