forked from root/acme.js
v1.5.0: perform full test challenge first
This commit is contained in:
parent
85a38f7b54
commit
d802fb4957
|
@ -193,6 +193,8 @@ ACME.challengePrefixes['dns-01'] // '_acme-challenge'
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
* v1.5
|
||||||
|
* perform full test challenge first (even before nonce)
|
||||||
* v1.3
|
* v1.3
|
||||||
* Use node RSA keygen by default
|
* Use node RSA keygen by default
|
||||||
* No non-optional external deps!
|
* No non-optional external deps!
|
||||||
|
|
83
node.js
83
node.js
|
@ -16,6 +16,9 @@ ACME.splitPemChain = function splitPemChain(str) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}}
|
||||||
|
// dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}"
|
||||||
ACME.challengePrefixes = {
|
ACME.challengePrefixes = {
|
||||||
'http-01': '/.well-known/acme-challenge'
|
'http-01': '/.well-known/acme-challenge'
|
||||||
, 'dns-01': '_acme-challenge'
|
, 'dns-01': '_acme-challenge'
|
||||||
|
@ -255,6 +258,37 @@ ACME._wait = function wait(ms) {
|
||||||
setTimeout(resolve, (ms || 1100));
|
setTimeout(resolve, (ms || 1100));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ACME._testChallenges = function (me, options) {
|
||||||
|
if (me.skipChallengeTest) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(options.domains.map(function (identifierValue) {
|
||||||
|
// TODO we really only need one to pass, not all to pass
|
||||||
|
return Promise.all(options.challengeTypes.map(function (chType) {
|
||||||
|
var chToken = require('crypto').randomBytes(16).toString('hex');
|
||||||
|
var thumbprint = me.RSA.thumbprint(options.accountKeypair);
|
||||||
|
var keyAuthorization = chToken + '.' + thumbprint;
|
||||||
|
var auth = {
|
||||||
|
identifier: { type: "dns", value: identifierValue }
|
||||||
|
, hostname: identifierValue
|
||||||
|
, type: chType
|
||||||
|
, token: chToken
|
||||||
|
, thumbprint: thumbprint
|
||||||
|
, keyAuthorization: keyAuthorization
|
||||||
|
, dnsAuthorization: me.RSA.utils.toWebsafeBase64(
|
||||||
|
require('crypto').createHash('sha256').update(keyAuthorization).digest('base64')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
return ACME._setChallenge(me, options, auth).then(function () {
|
||||||
|
return ACME.challengeTests[chType](me, auth);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
|
||||||
ACME._postChallenge = function (me, options, identifier, ch) {
|
ACME._postChallenge = function (me, options, identifier, ch) {
|
||||||
var RETRY_INTERVAL = me.retryInterval || 1000;
|
var RETRY_INTERVAL = me.retryInterval || 1000;
|
||||||
|
@ -279,7 +313,6 @@ ACME._postChallenge = function (me, options, identifier, ch) {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
/*
|
/*
|
||||||
POST /acme/authz/1234 HTTP/1.1
|
POST /acme/authz/1234 HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
|
@ -333,7 +366,6 @@ ACME._postChallenge = function (me, options, identifier, ch) {
|
||||||
|
|
||||||
if (me.debug) { console.debug('\n[DEBUG] statusChallenge\n'); }
|
if (me.debug) { console.debug('\n[DEBUG] statusChallenge\n'); }
|
||||||
return me._request({ method: 'GET', url: ch.url, json: true }).then(function (resp) {
|
return me._request({ method: 'GET', url: ch.url, json: true }).then(function (resp) {
|
||||||
|
|
||||||
if ('processing' === resp.body.status) {
|
if ('processing' === resp.body.status) {
|
||||||
if (me.debug) { console.debug('poll: again'); }
|
if (me.debug) { console.debug('poll: again'); }
|
||||||
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
||||||
|
@ -342,10 +374,10 @@ ACME._postChallenge = function (me, options, identifier, ch) {
|
||||||
// This state should never occur
|
// This state should never occur
|
||||||
if ('pending' === resp.body.status) {
|
if ('pending' === resp.body.status) {
|
||||||
if (count >= MAX_PEND) {
|
if (count >= MAX_PEND) {
|
||||||
return ACME._wait(RETRY_INTERVAL).then(deactivate).then(testChallenge);
|
return ACME._wait(RETRY_INTERVAL).then(deactivate).then(respondToChallenge);
|
||||||
}
|
}
|
||||||
if (me.debug) { console.debug('poll: again'); }
|
if (me.debug) { console.debug('poll: again'); }
|
||||||
return ACME._wait(RETRY_INTERVAL).then(testChallenge);
|
return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('valid' === resp.body.status) {
|
if ('valid' === resp.body.status) {
|
||||||
|
@ -403,48 +435,35 @@ ACME._postChallenge = function (me, options, identifier, ch) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function testChallenge() {
|
return ACME._setChallenge(me, options, auth).then(respondToChallenge);
|
||||||
// TODO put check dns / http checks here?
|
};
|
||||||
// http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}}
|
ACME._setChallenge = function (me, options, auth) {
|
||||||
// dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}"
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
if (me.debug) {console.debug('\n[DEBUG] postChallenge\n'); }
|
|
||||||
//if (me.debug) console.debug('\n[DEBUG] stop to fix things\n'); return;
|
|
||||||
|
|
||||||
return ACME._wait(RETRY_INTERVAL).then(function () {
|
|
||||||
if (!me.skipChallengeTest) {
|
|
||||||
return ACME.challengeTests[ch.type](me, auth);
|
|
||||||
}
|
|
||||||
}).then(respondToChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (1 === options.setChallenge.length) {
|
if (1 === options.setChallenge.length) {
|
||||||
options.setChallenge(auth).then(testChallenge).then(resolve, reject);
|
options.setChallenge(auth).then(resolve).catch(reject);
|
||||||
} else if (2 === options.setChallenge.length) {
|
} else if (2 === options.setChallenge.length) {
|
||||||
options.setChallenge(auth, function (err) {
|
options.setChallenge(auth, function (err) {
|
||||||
if(err) {
|
if(err) { reject(err); } else { resolve(); }
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
testChallenge().then(resolve, reject);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var challengeCb = function(err) {
|
var challengeCb = function(err) {
|
||||||
if(err) {
|
if(err) { reject(err); } else { resolve(); }
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
testChallenge().then(resolve, reject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
// for backwards compat adding extra keys without changing params length
|
||||||
Object.keys(auth).forEach(function (key) {
|
Object.keys(auth).forEach(function (key) {
|
||||||
challengeCb[key] = auth[key];
|
challengeCb[key] = auth[key];
|
||||||
});
|
});
|
||||||
options.setChallenge(identifier.value, ch.token, keyAuthorization, challengeCb);
|
options.setChallenge(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
}).then(function () {
|
||||||
|
// TODO: Do we still need this delay? Or shall we leave it to plugins to account for themselves?
|
||||||
|
var DELAY = me.setChallengeWait || 500;
|
||||||
|
if (me.debug) { console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY); }
|
||||||
|
return ACME._wait(DELAY);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
ACME._finalizeOrder = function (me, options, validatedDomains) {
|
ACME._finalizeOrder = function (me, options, validatedDomains) {
|
||||||
|
@ -548,6 +567,7 @@ ACME._getCertificate = function (me, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ACME._testChallenges(me, options).then(function () {
|
||||||
if (me.debug) { console.debug('[acme-v2] certificates.create'); }
|
if (me.debug) { console.debug('[acme-v2] certificates.create'); }
|
||||||
return ACME._getNonce(me).then(function () {
|
return ACME._getNonce(me).then(function () {
|
||||||
var body = {
|
var body = {
|
||||||
|
@ -650,6 +670,7 @@ ACME._getCertificate = function (me, options) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ACME.create = function create(me) {
|
ACME.create = function create(me) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "acme-v2",
|
"name": "acme-v2",
|
||||||
"version": "1.3.1",
|
"version": "1.5.0",
|
||||||
"description": "Free SSL. A framework for building Let's Encrypt v2 clients, and other ACME v2 (draft 11) clients. Successor to le-acme-core.js",
|
"description": "Free SSL. A framework for building Let's Encrypt v2 clients, and other ACME v2 (draft 11) clients. Successor to le-acme-core.js",
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/acme-v2.js",
|
"homepage": "https://git.coolaj86.com/coolaj86/acme-v2.js",
|
||||||
"main": "node.js",
|
"main": "node.js",
|
||||||
|
|
Loading…
Reference in New Issue