refactor
This commit is contained in:
parent
330e0e7832
commit
49d5346615
|
@ -1067,10 +1067,6 @@ Keypairs.signJws = function (opts) {
|
||||||
protectedHeader = JSON.stringify(protect);
|
protectedHeader = JSON.stringify(protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not sure how to handle the empty case since ACME POST-as-GET must be empty
|
|
||||||
//if (!payload) {
|
|
||||||
// throw new Error("opts.payload should be JSON, string, or ArrayBuffer (it may be empty, but that must be explicit)");
|
|
||||||
//}
|
|
||||||
// Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
|
// Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
|
||||||
if (payload && ('string' !== typeof payload)
|
if (payload && ('string' !== typeof payload)
|
||||||
&& ('undefined' === typeof payload.byteLength)
|
&& ('undefined' === typeof payload.byteLength)
|
||||||
|
@ -1832,20 +1828,17 @@ ACME._setNonce = function (me, nonce) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
ACME._registerAccount = function (me, options) {
|
ACME._registerAccount = function (me, options) {
|
||||||
if (me.debug) { console.debug('[acme-v2] accounts.create'); }
|
//#console.debug('[acme-v2] accounts.create');
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
function agree(tosUrl) {
|
function agree(tosUrl) {
|
||||||
var err;
|
var err;
|
||||||
if (me._tos !== tosUrl) {
|
if (me._tos !== tosUrl) {
|
||||||
err = new Error("You must agree to the ToS at '" + me._tos + "'");
|
err = new Error("You must agree to the ToS at '" + me._tos + "'");
|
||||||
err.code = "E_AGREE_TOS";
|
err.code = "E_AGREE_TOS";
|
||||||
reject(err);
|
throw err;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ACME._importKeypair(me, options.accountKeypair).then(function (pair) {
|
return ACME._importKeypair(me, options.accountKey || options.accountKeypair).then(function (pair) {
|
||||||
var contact;
|
var contact;
|
||||||
if (options.contact) {
|
if (options.contact) {
|
||||||
contact = options.contact.slice(0);
|
contact = options.contact.slice(0);
|
||||||
|
@ -1892,9 +1885,9 @@ ACME._registerAccount = function (me, options) {
|
||||||
var location = resp.headers.location;
|
var location = resp.headers.location;
|
||||||
// the account id url
|
// the account id url
|
||||||
options._kid = location;
|
options._kid = location;
|
||||||
if (me.debug) { console.debug('[DEBUG] new account location:'); }
|
//#console.debug('[DEBUG] new account location:');
|
||||||
if (me.debug) { console.debug(location); }
|
//#console.debug(location);
|
||||||
if (me.debug) { console.debug(resp); }
|
//#console.debug(resp);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
|
@ -1908,28 +1901,14 @@ ACME._registerAccount = function (me, options) {
|
||||||
if (!account.key) { account.key = {}; }
|
if (!account.key) { account.key = {}; }
|
||||||
account.key.kid = options._kid;
|
account.key.kid = options._kid;
|
||||||
return account;
|
return account;
|
||||||
}).then(resolve, reject);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me.debug) { console.debug('[acme-v2] agreeToTerms'); }
|
return Promise.resolve().then(function () {
|
||||||
if (1 === options.agreeToTerms.length) {
|
return options.agreeToTerms(me._tos);
|
||||||
// newer promise API
|
}).then(agree);
|
||||||
return Promise.resolve(options.agreeToTerms(me._tos)).then(agree, reject);
|
|
||||||
}
|
|
||||||
else if (2 === options.agreeToTerms.length) {
|
|
||||||
// backwards compat cb API
|
|
||||||
return options.agreeToTerms(me._tos, function (err, tosUrl) {
|
|
||||||
if (!err) { agree(tosUrl); return; }
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reject(new Error('agreeToTerms has incorrect function signature.'
|
|
||||||
+ ' Should be fn(tos) { return Promise<tos>; }'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
POST /acme/new-order HTTP/1.1
|
POST /acme/new-order HTTP/1.1
|
||||||
|
@ -1952,9 +1931,7 @@ ACME._registerAccount = function (me, options) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
ACME._getChallenges = function (me, options, authUrl) {
|
ACME._getChallenges = function (me, options, authUrl) {
|
||||||
if (me.debug) { console.debug('\n[DEBUG] getChallenges\n'); }
|
//#console.debug('\n[DEBUG] getChallenges\n');
|
||||||
// TODO POST-as-GET
|
|
||||||
|
|
||||||
return ACME._jwsRequest(me, {
|
return ACME._jwsRequest(me, {
|
||||||
options: options
|
options: options
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
|
@ -1962,7 +1939,7 @@ ACME._getChallenges = function (me, options, authUrl) {
|
||||||
, url: authUrl
|
, url: authUrl
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
// Pre-emptive rather than lazy for interfaces that need to show the challenges to the user first
|
// Pre-emptive rather than lazy for interfaces that need to show the challenges to the user first
|
||||||
return ACME._challengesToAuth(me, options, resp.body, false).then(function (auths) {
|
return ACME._computeAuths(me, options, resp.body, false).then(function (auths) {
|
||||||
resp.body._rawChallenges = resp.body.challenges;
|
resp.body._rawChallenges = resp.body.challenges;
|
||||||
resp.body.challenges = auths;
|
resp.body.challenges = auths;
|
||||||
return resp.body;
|
return resp.body;
|
||||||
|
@ -1999,78 +1976,53 @@ ACME._testChallengeOptions = function () {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
ACME._testChallenges = function (me, options) {
|
ACME._testChallenges = function (me, reals) {
|
||||||
var CHECK_DELAY = 0;
|
console.log('[DEBUG] testChallenges');
|
||||||
return Promise.all(options.domains.map(function (identifierValue) {
|
if (me.skipDryRun || me.skipChallengeTest) {
|
||||||
// TODO we really only need one to pass, not all to pass
|
return Promise.resolve();
|
||||||
var challenges = ACME._testChallengeOptions();
|
|
||||||
if (identifierValue.includes("*")) {
|
|
||||||
challenges = challenges.filter(function (ch) { return ch._wildcard; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nopts = {};
|
||||||
|
Object.keys(reals).forEach(function (key) {
|
||||||
|
nopts[key] = reals[key];
|
||||||
|
});
|
||||||
|
nopts.order = {};
|
||||||
|
|
||||||
|
return Promise.all(nopts.domains.map(function (name) {
|
||||||
|
var challenges = ACME._testChallengeOptions();
|
||||||
|
var wild = '*.' === name.slice(0, 2);
|
||||||
|
if (wild) {
|
||||||
|
challenges = challenges.filter(function (ch) { return ch._wildcard; });
|
||||||
|
}
|
||||||
|
var resp = {
|
||||||
|
body: {
|
||||||
|
identifier: { type: 'dns' , value: name.replace('*.', '') }
|
||||||
|
, challenges: challenges
|
||||||
|
, expires: new Date(Date.now() + (60 * 1000)).toISOString()
|
||||||
|
, wildcard: name.includes('*.') || undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
// The dry-run comes first in the spirit of "fail fast"
|
// The dry-run comes first in the spirit of "fail fast"
|
||||||
// (and protecting against challenge failure rate limits)
|
// (and protecting against challenge failure rate limits)
|
||||||
var dryrun = true;
|
var dryrun = true;
|
||||||
var resp = {
|
return ACME._computeAuths(me, nopts, resp.body, dryrun).then(function (auths) {
|
||||||
body: {
|
|
||||||
identifier: {
|
|
||||||
type: "dns"
|
|
||||||
, value: identifierValue.replace(/^\*\./, '')
|
|
||||||
}
|
|
||||||
, challenges: challenges
|
|
||||||
, expires: new Date(Date.now() + (60 * 1000)).toISOString()
|
|
||||||
, wildcard: identifierValue.includes('*.') || undefined
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return ACME._challengesToAuth(me, options, resp.body, dryrun).then(function (auths) {
|
|
||||||
resp.body._rawChallenges = resp.body.challenges;
|
|
||||||
resp.body.challenges = auths;
|
resp.body.challenges = auths;
|
||||||
|
|
||||||
var auth = ACME._chooseAuth(options, resp.body.challenges);
|
|
||||||
if (!auth) {
|
|
||||||
// For example, wildcards require dns-01 and, if we don't have that, we have to bail
|
|
||||||
var enabled = Object.keys(options.challenges).join(', ') || 'none';
|
|
||||||
var suitable = resp.body.challenges.map(function (r) { return r.type; }).join(', ') || 'none';
|
|
||||||
return Promise.reject(new Error(
|
|
||||||
"None of the challenge types that you've enabled ( " + enabled + " )"
|
|
||||||
+ " are suitable for validating the domain you've selected (" + identifierValue + ")."
|
|
||||||
+ " You must enable one of ( " + suitable + " )."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove skipChallengeTest
|
|
||||||
if (me.skipDryRun || me.skipChallengeTest) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('dns-01' === auth.type) {
|
|
||||||
// Give the nameservers a moment to propagate
|
|
||||||
CHECK_DELAY = 1.5 * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!me._canUse[auth.type]) { return; }
|
|
||||||
return ACME._setChallenge(me, options, auth).then(function () {
|
|
||||||
return auth;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})).then(function (auths) {
|
|
||||||
auths = auths.filter(Boolean);
|
|
||||||
if (!auths.length) { /*skip actual test*/ return; }
|
|
||||||
return ACME._wait(CHECK_DELAY).then(function () {
|
|
||||||
return Promise.all(auths.map(function (auth) {
|
|
||||||
return ACME.challengeTests[auth.type](me, auth).then(function (result) {
|
|
||||||
// not a blocker
|
|
||||||
ACME._removeChallenge(me, options, auth);
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
})).then(function (claims) {
|
||||||
|
nopts.order.claims = claims;
|
||||||
|
nopts.setChallengeWait = 0;
|
||||||
|
|
||||||
|
return ACME._setChallengesAll(me, nopts).then(function (valids) {
|
||||||
|
return Promise.all(valids.map(function (auth) {
|
||||||
|
ACME._removeChallenge(me, nopts, auth);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
ACME._chooseAuth = function(options, auths) {
|
ACME._chooseType = function(options, auths) {
|
||||||
// For each of the challenge types that we support
|
// For each of the challenge types that we support
|
||||||
var auth;
|
var auth;
|
||||||
var challengeTypes = Object.keys(options.challenges);
|
var challengeTypes = Object.keys(options.challenges || ACME._challengesMap);
|
||||||
// ordered from most to least preferred
|
// ordered from most to least preferred
|
||||||
challengeTypes = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).filter(function (chType) {
|
challengeTypes = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).filter(function (chType) {
|
||||||
return challengeTypes.includes(chType);
|
return challengeTypes.includes(chType);
|
||||||
|
@ -2089,15 +2041,17 @@ ACME._chooseAuth = function(options, auths) {
|
||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
};
|
};
|
||||||
ACME._challengesToAuth = function (me, options, request, dryrun) {
|
ACME._challengesMap = {'http-01':0,'dns-01':0,'tls-alpn-01':0};
|
||||||
|
ACME._computeAuths = function (me, options, request, dryrun) {
|
||||||
|
console.log('[DEBUG] computeAuths');
|
||||||
// we don't poison the dns cache with our dummy request
|
// we don't poison the dns cache with our dummy request
|
||||||
var dnsPrefix = ACME.challengePrefixes['dns-01'];
|
var dnsPrefix = ACME.challengePrefixes['dns-01'];
|
||||||
if (dryrun) {
|
if (dryrun) {
|
||||||
dnsPrefix = dnsPrefix.replace('acme-challenge', 'greenlock-dryrun-' + ACME._prnd(4));
|
dnsPrefix = dnsPrefix.replace('acme-challenge', 'greenlock-dryrun-' + ACME._prnd(4));
|
||||||
}
|
}
|
||||||
var challengeTypes = Object.keys(options.challenges);
|
var challengeTypes = Object.keys(options.challenges || ACME._challengesMap);
|
||||||
|
|
||||||
return ACME._importKeypair(me, options.accountKeypair).then(function (pair) {
|
return ACME._importKeypair(me, options.accountKey || options.accountKeypair).then(function (pair) {
|
||||||
return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) {
|
return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) {
|
||||||
return Promise.all(request.challenges.map(function (challenge) {
|
return Promise.all(request.challenges.map(function (challenge) {
|
||||||
// Don't do extra work for challenges that we can't satisfy
|
// Don't do extra work for challenges that we can't satisfy
|
||||||
|
@ -2188,15 +2142,15 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
function deactivate() {
|
function deactivate() {
|
||||||
if (me.debug) { console.debug('[acme-v2.js] deactivate:'); }
|
//#console.debug('[acme-v2.js] deactivate:');
|
||||||
return ACME._jwsRequest(me, {
|
return ACME._jwsRequest(me, {
|
||||||
options: options
|
options: options
|
||||||
, url: auth.url
|
, url: auth.url
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.binToBuf(JSON.stringify({ "status": "deactivated" }))
|
, payload: Enc.binToBuf(JSON.stringify({ "status": "deactivated" }))
|
||||||
}).then(function (resp) {
|
}).then(function (/*#resp*/) {
|
||||||
if (me.debug) { console.debug('deactivate challenge: resp.body:'); }
|
//#console.debug('deactivate challenge: resp.body:');
|
||||||
if (me.debug) { console.debug(resp.body); }
|
//#console.debug(resp.body);
|
||||||
return ACME._wait(DEAUTH_INTERVAL);
|
return ACME._wait(DEAUTH_INTERVAL);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2210,11 +2164,10 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
|
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
if (me.debug) { console.debug('\n[DEBUG] statusChallenge\n'); }
|
//#console.debug('\n[DEBUG] statusChallenge\n');
|
||||||
// TODO POST-as-GET
|
|
||||||
return me.request({ method: 'GET', url: auth.url, json: true }).then(function (resp) {
|
return me.request({ method: 'GET', url: auth.url, json: true }).then(function (resp) {
|
||||||
if ('processing' === resp.body.status) {
|
if ('processing' === resp.body.status) {
|
||||||
if (me.debug) { console.debug('poll: again'); }
|
//#console.debug('poll: again');
|
||||||
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2223,12 +2176,12 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
if (count >= MAX_PEND) {
|
if (count >= MAX_PEND) {
|
||||||
return ACME._wait(RETRY_INTERVAL).then(deactivate).then(respondToChallenge);
|
return ACME._wait(RETRY_INTERVAL).then(deactivate).then(respondToChallenge);
|
||||||
}
|
}
|
||||||
if (me.debug) { console.debug('poll: again'); }
|
//#console.debug('poll: again');
|
||||||
return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
|
return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('valid' === resp.body.status) {
|
if ('valid' === resp.body.status) {
|
||||||
if (me.debug) { console.debug('poll: valid'); }
|
//#console.debug('poll: valid');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ACME._removeChallenge(me, options, auth);
|
ACME._removeChallenge(me, options, auth);
|
||||||
|
@ -2255,147 +2208,149 @@ ACME._postChallenge = function (me, options, auth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function respondToChallenge() {
|
function respondToChallenge() {
|
||||||
if (me.debug) { console.debug('[acme-v2.js] responding to accept challenge:'); }
|
//#console.debug('[acme-v2.js] responding to accept challenge:');
|
||||||
return ACME._jwsRequest(me, {
|
return ACME._jwsRequest(me, {
|
||||||
options: options
|
options: options
|
||||||
, url: auth.url
|
, url: auth.url
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.binToBuf(JSON.stringify({}))
|
, payload: Enc.binToBuf(JSON.stringify({}))
|
||||||
}).then(function (resp) {
|
}).then(function (/*#resp*/) {
|
||||||
if (me.debug) { console.debug('respond to challenge: resp.body:'); }
|
//#console.debug('respond to challenge: resp.body:');
|
||||||
if (me.debug) { console.debug(resp.body); }
|
//#console.debug(resp.body);
|
||||||
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return respondToChallenge();
|
return respondToChallenge();
|
||||||
};
|
};
|
||||||
ACME._setChallenge = function (me, options, auth) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
// options = { domains, claims, challenges, challengePriority }
|
||||||
var challengers = options.challenges || {};
|
|
||||||
var challenger = (challengers[auth.type] && challengers[auth.type].set) || options.setChallenge;
|
|
||||||
try {
|
|
||||||
if (1 === challenger.length) {
|
|
||||||
challenger(auth).then(resolve).catch(reject);
|
|
||||||
} else if (2 === challenger.length) {
|
|
||||||
challenger(auth, function (err) {
|
|
||||||
if(err) { reject(err); } else { resolve(); }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO remove this old backwards-compat
|
|
||||||
var challengeCb = function(err) {
|
|
||||||
if(err) { reject(err); } else { resolve(); }
|
|
||||||
};
|
|
||||||
// for backwards compat adding extra keys without changing params length
|
|
||||||
Object.keys(auth).forEach(function (key) {
|
|
||||||
challengeCb[key] = auth[key];
|
|
||||||
});
|
|
||||||
if (!ACME._setChallengeWarn) {
|
|
||||||
console.warn("Please update to acme-v2 setChallenge(options) <Promise> or setChallenge(options, cb).");
|
|
||||||
console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types.");
|
|
||||||
ACME._setChallengeWarn = true;
|
|
||||||
}
|
|
||||||
challenger(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb);
|
|
||||||
}
|
|
||||||
} catch(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._setChallengesAll = function (me, options) {
|
ACME._setChallengesAll = function (me, options) {
|
||||||
var order = options.order;
|
console.log("[DEBUG] setChallengesAll");
|
||||||
var setAuths = order.authorizations.slice(0);
|
var claims = options.order.claims.slice(0);
|
||||||
var claims = order.claims.slice(0);
|
var valids = [];
|
||||||
var validAuths = [];
|
|
||||||
var auths = [];
|
var auths = [];
|
||||||
|
// TODO: Do we still need this delay? Or shall we leave it to plugins to account for themselves?
|
||||||
|
var DELAY = options.setChallengeWait || me.setChallengeWait || 500;
|
||||||
|
|
||||||
|
// Set any challenges, excpting ones that have already been validated
|
||||||
function setNext() {
|
function setNext() {
|
||||||
var authUrl = setAuths.shift();
|
|
||||||
var claim = claims.shift();
|
var claim = claims.shift();
|
||||||
if (!authUrl) { return Promise.resolve(); }
|
if (!claim) { return Promise.resolve(); }
|
||||||
|
|
||||||
// var domain = options.domains[i]; // claim.identifier.value
|
return Promise.resolve().then(function () {
|
||||||
|
// For any challenges that are already valid,
|
||||||
// If it's already valid, we're golden it regardless
|
// add to the list and skip any checks.
|
||||||
if (claim.challenges.some(function (ch) { return 'valid' === ch.status; })) {
|
if (claim.challenges.some(function (ch) {
|
||||||
return setNext();
|
if ('valid' === ch.status) {
|
||||||
|
valids.push(ch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = ACME._chooseAuth(options, claim.challenges);
|
// Get the list of challenge types we can validate.
|
||||||
if (!auth) {
|
// Then order that list by preference
|
||||||
// For example, wildcards require dns-01 and, if we don't have that, we have to bail
|
// Select the first matching offered challenge type
|
||||||
return Promise.reject(new Error(
|
var usable = Object.keys(options.challenges || ACME._challengesMap);
|
||||||
"Server didn't offer any challenge we can handle for '" + options.domains.join() + "'."
|
var selected = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).map(function (chType) {
|
||||||
));
|
if (!usable.includes(chType)) { return; }
|
||||||
|
return claim.challenges.filter(function (ch) {
|
||||||
|
return ch.type === chType;
|
||||||
|
})[0];
|
||||||
|
}).filter(Boolean)[0];
|
||||||
|
var ch;
|
||||||
|
|
||||||
|
// Bail with a descriptive message if no usable challenge could be selected
|
||||||
|
if (!selected) {
|
||||||
|
var enabled = usable.join(', ') || 'none';
|
||||||
|
var suitable = claim.challenges.map(function (r) { return r.type; }).join(', ') || 'none';
|
||||||
|
throw new Error(
|
||||||
|
"None of the challenge types that you've enabled ( " + enabled + " )"
|
||||||
|
+ " are suitable for validating the domain you've selected (" + claim.altname + ")."
|
||||||
|
+ " You must enable one of ( " + suitable + " )."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
auths.push(selected);
|
||||||
|
|
||||||
|
// Give the nameservers a moment to propagate
|
||||||
|
if ('dns-01' === selected.type) {
|
||||||
|
DELAY = 1.5 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
auths.push(auth);
|
if (false === options.challenges) { return; }
|
||||||
return ACME._setChallenge(me, options, auth).then(setNext);
|
ch = options.challenges[selected.type] || {};
|
||||||
|
if (!ch.set) {
|
||||||
|
throw new Error("no handler for setting challenge");
|
||||||
|
}
|
||||||
|
return ch.set(selected);
|
||||||
|
}).then(setNext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkNext() {
|
function checkNext() {
|
||||||
var auth = auths.shift();
|
var auth = auths.shift();
|
||||||
if (!auth) { return; }
|
if (!auth) { return Promise.resolve(valids); }
|
||||||
|
|
||||||
|
// These are not as much "valids" as they are "not invalids"
|
||||||
if (!me._canUse[auth.type] || me.skipChallengeTest) {
|
if (!me._canUse[auth.type] || me.skipChallengeTest) {
|
||||||
// not so much "valid" as "not invalid"
|
valids.push(auth);
|
||||||
// but in this case we can't confirm either way
|
return checkNext();
|
||||||
validAuths.push(auth);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ACME.challengeTests[auth.type](me, auth).then(function () {
|
return ACME.challengeTests[auth.type](me, auth).then(function () {
|
||||||
validAuths.push(auth);
|
valids.push(auth);
|
||||||
}).then(checkNext);
|
}).then(checkNext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually sets the challenge via ACME
|
// The reason we set every challenge in a batch first before checking any
|
||||||
function challengeNext() {
|
// is so that we don't poison our own DNS cache with misses.
|
||||||
var auth = validAuths.shift();
|
return setNext().then(function () {
|
||||||
if (!auth) { return; }
|
//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
|
||||||
return ACME._postChallenge(me, options, auth).then(challengeNext);
|
return ACME._wait(DELAY);
|
||||||
}
|
}).then(checkNext);
|
||||||
|
|
||||||
// First we set every challenge
|
|
||||||
// Then we ask for each challenge to be checked
|
|
||||||
// Doing otherwise would potentially cause us to poison our own DNS cache with misses
|
|
||||||
return setNext().then(checkNext).then(challengeNext).then(function () {
|
|
||||||
if (me.debug) { console.debug("[getCertificate] next.then"); }
|
|
||||||
console.log('DEBUG 1 order:');
|
|
||||||
console.log(order);
|
|
||||||
return order.identifiers.map(function (ident) {
|
|
||||||
return ident.value;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
ACME._finalizeOrder = function (me, options) {
|
ACME._finalizeOrder = function (me, options) {
|
||||||
return ACME._getAccountKid(me, options).then(function () {
|
return ACME._getAccountKid(me, options).then(function () {
|
||||||
return ACME._setChallengesAll(me, options).then(function () {
|
if (!options.challenges && !options.challengePriority) {
|
||||||
|
throw new Error("You must set either challenges or challengePrority");
|
||||||
|
}
|
||||||
|
return ACME._setChallengesAll(me, options).then(function (valids) {
|
||||||
// options._kid added
|
// options._kid added
|
||||||
if (me.debug) { console.debug('finalizeOrder:'); }
|
//#console.debug('finalizeOrder:');
|
||||||
var order = options.order;
|
var order = options.order;
|
||||||
var validatedDomains = options.order.identifiers.map(function (ident) {
|
var validatedDomains = options.order.identifiers.map(function (ident) {
|
||||||
return ident.value;
|
return ident.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Actually sets the challenge via ACME
|
||||||
|
function challengeNext() {
|
||||||
|
var auth = valids.shift();
|
||||||
|
if (!auth) { return Promise.resolve(); }
|
||||||
|
return ACME._postChallenge(me, options, auth).then(challengeNext);
|
||||||
|
}
|
||||||
|
return challengeNext().then(function () {
|
||||||
|
//#console.debug("[getCertificate] next.then");
|
||||||
|
console.log('DEBUG 1 order:');
|
||||||
|
console.log(options.order);
|
||||||
|
return options.order.identifiers.map(function (ident) {
|
||||||
|
return ident.value;
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
return ACME._getCsrWeb64(me, options, validatedDomains).then(function (csr) {
|
return ACME._getCsrWeb64(me, options, validatedDomains).then(function (csr) {
|
||||||
var body = { csr: csr };
|
var body = { csr: csr };
|
||||||
var payload = JSON.stringify(body);
|
var payload = JSON.stringify(body);
|
||||||
|
|
||||||
function pollCert() {
|
function pollCert() {
|
||||||
if (me.debug) { console.debug('[acme-v2.js] pollCert:'); }
|
//#console.debug('[acme-v2.js] pollCert:');
|
||||||
return ACME._jwsRequest(me, {
|
return ACME._jwsRequest(me, {
|
||||||
options: options
|
options: options
|
||||||
, url: options.order.finalizeUrl
|
, url: options.order.finalizeUrl
|
||||||
, protected: { kid: options._kid }
|
, protected: { kid: options._kid }
|
||||||
, payload: Enc.binToBuf(payload)
|
, payload: Enc.binToBuf(payload)
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
if (me.debug) { console.debug('order finalized: resp.body:'); }
|
//#console.debug('order finalized: resp.body:');
|
||||||
if (me.debug) { console.debug(resp.body); }
|
//#console.debug(resp.body);
|
||||||
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.3
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.3
|
||||||
// Possible values are: "pending" => ("invalid" || "ready") => "processing" => "valid"
|
// Possible values are: "pending" => ("invalid" || "ready") => "processing" => "valid"
|
||||||
|
@ -2410,7 +2365,7 @@ ACME._finalizeOrder = function (me, options) {
|
||||||
return ACME._wait().then(pollCert);
|
return ACME._wait().then(pollCert);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me.debug) { console.debug("Error: bad status:\n" + JSON.stringify(resp.body, null, 2)); }
|
//#console.debug("Error: bad status:\n" + JSON.stringify(resp.body, null, 2));
|
||||||
|
|
||||||
if ('pending' === resp.body.status) {
|
if ('pending' === resp.body.status) {
|
||||||
return Promise.reject(new Error(
|
return Promise.reject(new Error(
|
||||||
|
@ -2458,27 +2413,26 @@ ACME._finalizeOrder = function (me, options) {
|
||||||
|
|
||||||
return pollCert();
|
return pollCert();
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
if (me.debug) { console.debug('acme-v2: order was finalized'); }
|
//#console.debug('acme-v2: order was finalized');
|
||||||
// TODO POST-as-GET
|
|
||||||
return me.request({ method: 'GET', url: options._certificate, json: true }).then(function (resp) {
|
return me.request({ method: 'GET', url: options._certificate, json: true }).then(function (resp) {
|
||||||
if (me.debug) { console.debug('acme-v2: csr submitted and cert received:'); }
|
//#console.debug('acme-v2: csr submitted and cert received:');
|
||||||
// https://github.com/certbot/certbot/issues/5721
|
// https://github.com/certbot/certbot/issues/5721
|
||||||
var certsarr = ACME.splitPemChain(ACME.formatPemChain((resp.body||'')));
|
var certsarr = ACME.splitPemChain(ACME.formatPemChain((resp.body||'')));
|
||||||
// cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
|
// cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
|
||||||
|
// TODO CSR.info
|
||||||
var certs = {
|
var certs = {
|
||||||
expires: order.expires
|
expires: order.expires
|
||||||
, identifiers: order.identifiers
|
, identifiers: order.identifiers
|
||||||
//, authorizations: order.authorizations
|
|
||||||
, cert: certsarr.shift()
|
, cert: certsarr.shift()
|
||||||
//, privkey: privkeyPem
|
|
||||||
, chain: certsarr.join('\n')
|
, chain: certsarr.join('\n')
|
||||||
};
|
};
|
||||||
if (me.debug) { console.debug(certs); }
|
//#console.debug(certs);
|
||||||
return certs;
|
return certs;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
ACME._createOrder = function (me, options) {
|
ACME._createOrder = function (me, options) {
|
||||||
return ACME._getAccountKid(me, options).then(function () {
|
return ACME._getAccountKid(me, options).then(function () {
|
||||||
|
@ -2499,7 +2453,7 @@ ACME._createOrder = function (me, options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var payload = JSON.stringify(body);
|
var payload = JSON.stringify(body);
|
||||||
if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); }
|
//#console.debug('\n[DEBUG] newOrder\n');
|
||||||
return ACME._jwsRequest(me, {
|
return ACME._jwsRequest(me, {
|
||||||
options: options
|
options: options
|
||||||
, url: me._directoryUrls.newOrder
|
, url: me._directoryUrls.newOrder
|
||||||
|
@ -2513,8 +2467,8 @@ ACME._createOrder = function (me, options) {
|
||||||
, identifiers: body.identifiers
|
, identifiers: body.identifiers
|
||||||
, _response: resp.body
|
, _response: resp.body
|
||||||
};
|
};
|
||||||
if (me.debug) { console.debug('[ordered]', location); } // the account id url
|
//#console.debug('[ordered]', location); // the account id url
|
||||||
if (me.debug) { console.debug(resp); }
|
//#console.debug(resp);
|
||||||
|
|
||||||
if (!order.authorizations) {
|
if (!order.authorizations) {
|
||||||
return Promise.reject(new Error(
|
return Promise.reject(new Error(
|
||||||
|
@ -2526,7 +2480,7 @@ ACME._createOrder = function (me, options) {
|
||||||
return order;
|
return order;
|
||||||
}).then(function (order) {
|
}).then(function (order) {
|
||||||
var claims = [];
|
var claims = [];
|
||||||
if (me.debug) { console.debug("[acme-v2] POST newOrder has authorizations"); }
|
//#console.debug("[acme-v2] POST newOrder has authorizations");
|
||||||
var challengeAuths = order.authorizations.slice(0);
|
var challengeAuths = order.authorizations.slice(0);
|
||||||
|
|
||||||
function getNext() {
|
function getNext() {
|
||||||
|
@ -2565,13 +2519,12 @@ ACME._getAccountKid = function (me, options) {
|
||||||
return options._kid;
|
return options._kid;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// _kid
|
|
||||||
// registerAccount
|
//
|
||||||
// postChallenge
|
// Helper Methods
|
||||||
// finalizeOrder
|
//
|
||||||
// getCertificate
|
|
||||||
ACME._getCertificate = function (me, options) {
|
ACME._getCertificate = function (me, options) {
|
||||||
if (me.debug) { console.debug('[acme-v2] DEBUG get cert 1'); }
|
//#console.debug('[acme-v2] DEBUG get cert 1');
|
||||||
|
|
||||||
if (options.csr) {
|
if (options.csr) {
|
||||||
// TODO validate csr signature
|
// TODO validate csr signature
|
||||||
|
@ -2585,10 +2538,13 @@ ACME._getCertificate = function (me, options) {
|
||||||
return Promise.reject(new Error("options.domains must be a list of string domain names,"
|
return Promise.reject(new Error("options.domains must be a list of string domain names,"
|
||||||
+ " with the first being the subject of the certificate (or options.subject must specified)."));
|
+ " with the first being the subject of the certificate (or options.subject must specified)."));
|
||||||
}
|
}
|
||||||
|
if (!options.challenges) {
|
||||||
|
return Promise.reject(new Error("You must specify challenge handlers."));
|
||||||
|
}
|
||||||
|
|
||||||
// Do a little dry-run / self-test
|
// Do a little dry-run / self-test
|
||||||
return ACME._testChallenges(me, options).then(function () {
|
return ACME._testChallenges(me, options).then(function () {
|
||||||
if (me.debug) { console.debug('[acme-v2] certificates.create'); }
|
//#console.debug('[acme-v2] certificates.create');
|
||||||
return ACME._createOrder(me, options).then(function (/*order*/) {
|
return ACME._createOrder(me, options).then(function (/*order*/) {
|
||||||
// options.order = order;
|
// options.order = order;
|
||||||
return ACME._finalizeOrder(me, options);
|
return ACME._finalizeOrder(me, options);
|
||||||
|
@ -2607,7 +2563,7 @@ ACME._getCsrWeb64 = function (me, options, validatedDomains) {
|
||||||
return Promise.resolve(csr);
|
return Promise.resolve(csr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ACME._importKeypair(me, options.serverKeypair || options.domainKeypair).then(function (pair) {
|
return ACME._importKeypair(me, options.serverKey || options.serverKeypair || options.domainKeypair).then(function (pair) {
|
||||||
return me.CSR({ jwk: pair.private, domains: validatedDomains, encoding: 'der' }).then(function (der) {
|
return me.CSR({ jwk: pair.private, domains: validatedDomains, encoding: 'der' }).then(function (der) {
|
||||||
return Enc.bufToUrlBase64(der);
|
return Enc.bufToUrlBase64(der);
|
||||||
});
|
});
|
||||||
|
@ -2708,13 +2664,13 @@ ACME._jwsRequest = function (me, bigopts) {
|
||||||
if (!bigopts.protected.kid) { bigopts.protected.kid = bigopts.options._kid; }
|
if (!bigopts.protected.kid) { bigopts.protected.kid = bigopts.options._kid; }
|
||||||
}
|
}
|
||||||
return me.Keypairs.signJws(
|
return me.Keypairs.signJws(
|
||||||
{ jwk: bigopts.options.accountKeypair.privateKeyJwk
|
{ jwk: bigopts.accountKey || bigopts.options.accountKeypair.privateKeyJwk
|
||||||
, protected: bigopts.protected
|
, protected: bigopts.protected
|
||||||
, payload: bigopts.payload
|
, payload: bigopts.payload
|
||||||
}
|
}
|
||||||
).then(function (jws) {
|
).then(function (jws) {
|
||||||
if (me.debug) { console.debug('[acme-v2] ' + bigopts.url + ':'); }
|
//#console.debug('[acme-v2] ' + bigopts.url + ':');
|
||||||
if (me.debug) { console.debug(jws); }
|
//#console.debug(jws);
|
||||||
return ACME._request(me, { url: bigopts.url, json: jws });
|
return ACME._request(me, { url: bigopts.url, json: jws });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2769,7 +2725,7 @@ ACME._defaultRequest = function (opts) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ACME._importKeypair = function (me, kp) {
|
ACME._importKeypair = function (me, kp) {
|
||||||
var jwk = kp.privateKeyJwk;
|
var jwk = kp.privateKeyJwk || kp.kty && kp;
|
||||||
var p;
|
var p;
|
||||||
if (jwk) {
|
if (jwk) {
|
||||||
// nix the browser jwk extras
|
// nix the browser jwk extras
|
||||||
|
@ -2791,18 +2747,6 @@ ACME._importKeypair = function (me, kp) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
TODO
|
|
||||||
Per-Order State Params
|
|
||||||
_kty
|
|
||||||
_alg
|
|
||||||
_finalize
|
|
||||||
_expires
|
|
||||||
_certificate
|
|
||||||
_order
|
|
||||||
_authorizations
|
|
||||||
*/
|
|
||||||
|
|
||||||
ACME._toWebsafeBase64 = function (b64) {
|
ACME._toWebsafeBase64 = function (b64) {
|
||||||
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,"");
|
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,"");
|
||||||
};
|
};
|
||||||
|
@ -2850,20 +2794,14 @@ ACME._http01 = function (me, auth) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
ACME._removeChallenge = function (me, options, auth) {
|
ACME._removeChallenge = function (me, options, auth) {
|
||||||
var challengers = options.challenges || {};
|
return Promise.resolve().then(function () {
|
||||||
var removeChallenge = (challengers[auth.type] && challengers[auth.type].remove) || options.removeChallenge;
|
if (!options.challenges) { return; }
|
||||||
if (1 === removeChallenge.length) {
|
var ch = options.challenges[auth.type];
|
||||||
removeChallenge(auth).then(function () {}, function () {});
|
ch.remove(auth).catch(function (e) {
|
||||||
} else if (2 === removeChallenge.length) {
|
console.warn("challenge.remove error:");
|
||||||
removeChallenge(auth, function (err) { return err; });
|
console.warn(e);
|
||||||
} else {
|
});
|
||||||
if (!ACME._removeChallengeWarn) {
|
});
|
||||||
console.warn("Please update to acme-v2 removeChallenge(options) <Promise> or removeChallenge(options, cb).");
|
|
||||||
console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types.");
|
|
||||||
ACME._removeChallengeWarn = true;
|
|
||||||
}
|
|
||||||
removeChallenge(auth.request.identifier, auth.token, function () {});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Enc.bufToUrlBase64 = function (u8) {
|
Enc.bufToUrlBase64 = function (u8) {
|
||||||
|
|
Loading…
Reference in New Issue