refactor
This commit is contained in:
		
							förälder
							
								
									f56dafad5d
								
							
						
					
					
						incheckning
						352e334b5d
					
				| @ -1067,10 +1067,6 @@ Keypairs.signJws = function (opts) { | ||||
|         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)
 | ||||
|       if (payload && ('string' !== typeof payload) | ||||
|         && ('undefined' === typeof payload.byteLength) | ||||
| @ -1832,104 +1828,87 @@ ACME._setNonce = function (me, nonce) { | ||||
|  } | ||||
| */ | ||||
| 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) { | ||||
|     var err; | ||||
|     if (me._tos !== tosUrl) { | ||||
|       err = new Error("You must agree to the ToS at '" + me._tos + "'"); | ||||
|       err.code = "E_AGREE_TOS"; | ||||
|       throw err; | ||||
|     } | ||||
| 
 | ||||
|     function agree(tosUrl) { | ||||
|       var err; | ||||
|       if (me._tos !== tosUrl) { | ||||
|         err = new Error("You must agree to the ToS at '" + me._tos + "'"); | ||||
|         err.code = "E_AGREE_TOS"; | ||||
|         reject(err); | ||||
|         return; | ||||
|     return ACME._importKeypair(me, options.accountKey || options.accountKeypair).then(function (pair) { | ||||
|       var contact; | ||||
|       if (options.contact) { | ||||
|         contact = options.contact.slice(0); | ||||
|       } else if (options.email) { | ||||
|         contact = [ 'mailto:' + options.email ]; | ||||
|       } | ||||
| 
 | ||||
|       return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { | ||||
|         var contact; | ||||
|         if (options.contact) { | ||||
|           contact = options.contact.slice(0); | ||||
|         } else if (options.email) { | ||||
|           contact = [ 'mailto:' + options.email ]; | ||||
|         } | ||||
|         var body = { | ||||
|           termsOfServiceAgreed: tosUrl === me._tos | ||||
|         , onlyReturnExisting: false | ||||
|         , contact: contact | ||||
|         }; | ||||
|         var pExt; | ||||
|         if (options.externalAccount) { | ||||
|           pExt = me.Keypairs.signJws({ | ||||
|             // TODO is HMAC the standard, or is this arbitrary?
 | ||||
|             secret: options.externalAccount.secret | ||||
|           , protected: { | ||||
|               alg: options.externalAccount.alg || "HS256" | ||||
|             , kid: options.externalAccount.id | ||||
|             , url: me._directoryUrls.newAccount | ||||
|             } | ||||
|           , payload: Enc.binToBuf(JSON.stringify(pair.public)) | ||||
|           }).then(function (jws) { | ||||
|             body.externalAccountBinding = jws; | ||||
|             return body; | ||||
|           }); | ||||
|         } else { | ||||
|           pExt = Promise.resolve(body); | ||||
|         } | ||||
|         return pExt.then(function (body) { | ||||
|           var payload = JSON.stringify(body); | ||||
|           return ACME._jwsRequest(me, { | ||||
|             options: options | ||||
|       var body = { | ||||
|         termsOfServiceAgreed: tosUrl === me._tos | ||||
|       , onlyReturnExisting: false | ||||
|       , contact: contact | ||||
|       }; | ||||
|       var pExt; | ||||
|       if (options.externalAccount) { | ||||
|         pExt = me.Keypairs.signJws({ | ||||
|           // TODO is HMAC the standard, or is this arbitrary?
 | ||||
|           secret: options.externalAccount.secret | ||||
|         , protected: { | ||||
|             alg: options.externalAccount.alg || "HS256" | ||||
|           , kid: options.externalAccount.id | ||||
|           , url: me._directoryUrls.newAccount | ||||
|           , protected: { kid: false, jwk: pair.public } | ||||
|           , payload: Enc.binToBuf(payload) | ||||
|           }).then(function (resp) { | ||||
|             var account = resp.body; | ||||
|           } | ||||
|         , payload: Enc.binToBuf(JSON.stringify(pair.public)) | ||||
|         }).then(function (jws) { | ||||
|           body.externalAccountBinding = jws; | ||||
|           return body; | ||||
|         }); | ||||
|       } else { | ||||
|         pExt = Promise.resolve(body); | ||||
|       } | ||||
|       return pExt.then(function (body) { | ||||
|         var payload = JSON.stringify(body); | ||||
|         return ACME._jwsRequest(me, { | ||||
|           options: options | ||||
|         , url: me._directoryUrls.newAccount | ||||
|         , protected: { kid: false, jwk: pair.public } | ||||
|         , payload: Enc.binToBuf(payload) | ||||
|         }).then(function (resp) { | ||||
|           var account = resp.body; | ||||
| 
 | ||||
|             if (2 !== Math.floor(resp.statusCode / 100)) { | ||||
|               throw new Error('account error: ' + JSON.stringify(resp.body)); | ||||
|             } | ||||
|           if (2 !== Math.floor(resp.statusCode / 100)) { | ||||
|             throw new Error('account error: ' + JSON.stringify(resp.body)); | ||||
|           } | ||||
| 
 | ||||
|             var location = resp.headers.location; | ||||
|             // the account id url
 | ||||
|             options._kid = location; | ||||
|             if (me.debug) { console.debug('[DEBUG] new account location:'); } | ||||
|             if (me.debug) { console.debug(location); } | ||||
|             if (me.debug) { console.debug(resp); } | ||||
|           var location = resp.headers.location; | ||||
|           // the account id url
 | ||||
|           options._kid = location; | ||||
|           //#console.debug('[DEBUG] new account location:');
 | ||||
|           //#console.debug(location);
 | ||||
|           //#console.debug(resp);
 | ||||
| 
 | ||||
|             /* | ||||
|             { | ||||
|               contact: ["mailto:jon@example.com"], | ||||
|               orders: "https://some-url", | ||||
|               status: 'valid' | ||||
|             } | ||||
|             */ | ||||
|             if (!account) { account = { _emptyResponse: true }; } | ||||
|             // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8
 | ||||
|             if (!account.key) { account.key = {}; } | ||||
|             account.key.kid = options._kid; | ||||
|             return account; | ||||
|           }).then(resolve, reject); | ||||
|           /* | ||||
|           { | ||||
|             contact: ["mailto:jon@example.com"], | ||||
|             orders: "https://some-url", | ||||
|             status: 'valid' | ||||
|           } | ||||
|           */ | ||||
|           if (!account) { account = { _emptyResponse: true }; } | ||||
|           // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8
 | ||||
|           if (!account.key) { account.key = {}; } | ||||
|           account.key.kid = options._kid; | ||||
|           return account; | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|     if (me.debug) { console.debug('[acme-v2] agreeToTerms'); } | ||||
|     if (1 === options.agreeToTerms.length) { | ||||
|       // newer promise API
 | ||||
|       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>; }')); | ||||
|     } | ||||
|   }); | ||||
|   return Promise.resolve().then(function () { | ||||
|     return options.agreeToTerms(me._tos); | ||||
|   }).then(agree); | ||||
| }; | ||||
| /* | ||||
|  POST /acme/new-order HTTP/1.1 | ||||
| @ -1952,9 +1931,7 @@ ACME._registerAccount = function (me, options) { | ||||
|  } | ||||
| */ | ||||
| ACME._getChallenges = function (me, options, authUrl) { | ||||
|   if (me.debug) { console.debug('\n[DEBUG] getChallenges\n'); } | ||||
|   // TODO POST-as-GET
 | ||||
| 
 | ||||
|   //#console.debug('\n[DEBUG] getChallenges\n');
 | ||||
|   return ACME._jwsRequest(me, { | ||||
|     options: options | ||||
|   , protected: { kid: options._kid } | ||||
| @ -1962,7 +1939,7 @@ ACME._getChallenges = function (me, options, authUrl) { | ||||
|   , url: authUrl | ||||
|   }).then(function (resp) { | ||||
|     // 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.challenges = auths; | ||||
|       return resp.body; | ||||
| @ -1999,78 +1976,53 @@ ACME._testChallengeOptions = function () { | ||||
|     } | ||||
|   ]; | ||||
| }; | ||||
| ACME._testChallenges = function (me, options) { | ||||
|   var CHECK_DELAY = 0; | ||||
|   return Promise.all(options.domains.map(function (identifierValue) { | ||||
|     // TODO we really only need one to pass, not all to pass
 | ||||
| ACME._testChallenges = function (me, reals) { | ||||
|   console.log('[DEBUG] testChallenges'); | ||||
|   if (me.skipDryRun || me.skipChallengeTest) { | ||||
|     return Promise.resolve(); | ||||
|   } | ||||
| 
 | ||||
|   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(); | ||||
|     if (identifierValue.includes("*")) { | ||||
|     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"
 | ||||
|     // (and protecting against challenge failure rate limits)
 | ||||
|     var dryrun = true; | ||||
|     var resp = { | ||||
|       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; | ||||
|     return ACME._computeAuths(me, nopts, resp.body, dryrun).then(function (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
 | ||||
|   var auth; | ||||
|   var challengeTypes = Object.keys(options.challenges); | ||||
|   var challengeTypes = Object.keys(options.challenges || ACME._challengesMap); | ||||
|   // ordered from most to least preferred
 | ||||
|   challengeTypes = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).filter(function (chType) { | ||||
|     return challengeTypes.includes(chType); | ||||
| @ -2089,15 +2041,17 @@ ACME._chooseAuth = function(options, auths) { | ||||
| 
 | ||||
|   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
 | ||||
|   var dnsPrefix = ACME.challengePrefixes['dns-01']; | ||||
|   if (dryrun) { | ||||
|     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 Promise.all(request.challenges.map(function (challenge) { | ||||
|         // Don't do extra work for challenges that we can't satisfy
 | ||||
| @ -2188,15 +2142,15 @@ ACME._postChallenge = function (me, options, auth) { | ||||
|    } | ||||
|    */ | ||||
|   function deactivate() { | ||||
|     if (me.debug) { console.debug('[acme-v2.js] deactivate:'); } | ||||
|     //#console.debug('[acme-v2.js] deactivate:');
 | ||||
|     return ACME._jwsRequest(me, { | ||||
|       options: options | ||||
|     , url: auth.url | ||||
|     , protected: { kid: options._kid } | ||||
|     , payload: Enc.binToBuf(JSON.stringify({ "status": "deactivated" })) | ||||
|     }).then(function (resp) { | ||||
|       if (me.debug) { console.debug('deactivate challenge: resp.body:'); } | ||||
|       if (me.debug) { console.debug(resp.body); } | ||||
|     }).then(function (/*#resp*/) { | ||||
|       //#console.debug('deactivate challenge: resp.body:');
 | ||||
|       //#console.debug(resp.body);
 | ||||
|       return ACME._wait(DEAUTH_INTERVAL); | ||||
|     }); | ||||
|   } | ||||
| @ -2210,11 +2164,10 @@ ACME._postChallenge = function (me, options, auth) { | ||||
| 
 | ||||
|     count += 1; | ||||
| 
 | ||||
|     if (me.debug) { console.debug('\n[DEBUG] statusChallenge\n'); } | ||||
|     // TODO POST-as-GET
 | ||||
|     //#console.debug('\n[DEBUG] statusChallenge\n');
 | ||||
|     return me.request({ method: 'GET', url: auth.url, json: true }).then(function (resp) { | ||||
|       if ('processing' === resp.body.status) { | ||||
|         if (me.debug) { console.debug('poll: again'); } | ||||
|         //#console.debug('poll: again');
 | ||||
|         return ACME._wait(RETRY_INTERVAL).then(pollStatus); | ||||
|       } | ||||
| 
 | ||||
| @ -2223,12 +2176,12 @@ ACME._postChallenge = function (me, options, auth) { | ||||
|         if (count >= MAX_PEND) { | ||||
|           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); | ||||
|       } | ||||
| 
 | ||||
|       if ('valid' === resp.body.status) { | ||||
|         if (me.debug) { console.debug('poll: valid'); } | ||||
|         //#console.debug('poll: valid');
 | ||||
| 
 | ||||
|         try { | ||||
|           ACME._removeChallenge(me, options, auth); | ||||
| @ -2255,226 +2208,227 @@ ACME._postChallenge = function (me, options, auth) { | ||||
|   } | ||||
| 
 | ||||
|   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, { | ||||
|       options: options | ||||
|     , url: auth.url | ||||
|     , protected: { kid: options._kid } | ||||
|     , payload: Enc.binToBuf(JSON.stringify({})) | ||||
|     }).then(function (resp) { | ||||
|       if (me.debug) { console.debug('respond to challenge: resp.body:'); } | ||||
|       if (me.debug) { console.debug(resp.body); } | ||||
|     }).then(function (/*#resp*/) { | ||||
|       //#console.debug('respond to challenge: resp.body:');
 | ||||
|       //#console.debug(resp.body);
 | ||||
|       return ACME._wait(RETRY_INTERVAL).then(pollStatus); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return respondToChallenge(); | ||||
| }; | ||||
| ACME._setChallenge = function (me, options, auth) { | ||||
|   return new Promise(function (resolve, reject) { | ||||
|     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); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // options = { domains, claims, challenges, challengePriority }
 | ||||
| ACME._setChallengesAll = function (me, options) { | ||||
|   var order = options.order; | ||||
|   var setAuths = order.authorizations.slice(0); | ||||
|   var claims = order.claims.slice(0); | ||||
|   var validAuths = []; | ||||
|   console.log("[DEBUG] setChallengesAll"); | ||||
|   var claims = options.order.claims.slice(0); | ||||
|   var valids = []; | ||||
|   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() { | ||||
|     var authUrl = setAuths.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,
 | ||||
|       // add to the list and skip any checks.
 | ||||
|       if (claim.challenges.some(function (ch) { | ||||
|         if ('valid' === ch.status) { | ||||
|           valids.push(ch); | ||||
|           return true; | ||||
|         } | ||||
|       })) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|     // If it's already valid, we're golden it regardless
 | ||||
|     if (claim.challenges.some(function (ch) { return 'valid' === ch.status; })) { | ||||
|       return setNext(); | ||||
|     } | ||||
|       // Get the list of challenge types we can validate.
 | ||||
|       // Then order that list by preference
 | ||||
|       // Select the first matching offered challenge type
 | ||||
|       var usable = Object.keys(options.challenges || ACME._challengesMap); | ||||
|       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; | ||||
| 
 | ||||
|     var auth = ACME._chooseAuth(options, claim.challenges); | ||||
|     if (!auth) { | ||||
|       // For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | ||||
|       return Promise.reject(new Error( | ||||
|         "Server didn't offer any challenge we can handle for '" + options.domains.join() + "'." | ||||
|       )); | ||||
|     } | ||||
|       // 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); | ||||
| 
 | ||||
|     auths.push(auth); | ||||
|     return ACME._setChallenge(me, options, auth).then(setNext); | ||||
|       // Give the nameservers a moment to propagate
 | ||||
|       if ('dns-01' === selected.type) { | ||||
|         DELAY = 1.5 * 1000; | ||||
|       } | ||||
| 
 | ||||
|       if (false === options.challenges) { return; } | ||||
|       ch = options.challenges[selected.type] || {}; | ||||
|       if (!ch.set) { | ||||
|         throw new Error("no handler for setting challenge"); | ||||
|       } | ||||
|       return ch.set(selected); | ||||
|     }).then(setNext); | ||||
|   } | ||||
| 
 | ||||
|   function checkNext() { | ||||
|     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) { | ||||
|       // not so much "valid" as "not invalid"
 | ||||
|       // but in this case we can't confirm either way
 | ||||
|       validAuths.push(auth); | ||||
|       return Promise.resolve(); | ||||
|       valids.push(auth); | ||||
|       return checkNext(); | ||||
|     } | ||||
| 
 | ||||
|     return ACME.challengeTests[auth.type](me, auth).then(function () { | ||||
|       validAuths.push(auth); | ||||
|       valids.push(auth); | ||||
|     }).then(checkNext); | ||||
|   } | ||||
| 
 | ||||
|   // Actually sets the challenge via ACME
 | ||||
|   function challengeNext() { | ||||
|     var auth = validAuths.shift(); | ||||
|     if (!auth) { return; } | ||||
|     return ACME._postChallenge(me, options, auth).then(challengeNext); | ||||
|   } | ||||
| 
 | ||||
|   // 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; | ||||
|     }); | ||||
|   }); | ||||
|   // The reason we set every challenge in a batch first before checking any
 | ||||
|   // is so that we don't poison our own DNS cache with misses.
 | ||||
|   return setNext().then(function () { | ||||
|     //#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
 | ||||
|     return ACME._wait(DELAY); | ||||
|   }).then(checkNext); | ||||
| }; | ||||
| ACME._finalizeOrder = function (me, options) { | ||||
|   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
 | ||||
|       if (me.debug) { console.debug('finalizeOrder:'); } | ||||
|       //#console.debug('finalizeOrder:');
 | ||||
|       var order = options.order; | ||||
|       var validatedDomains = options.order.identifiers.map(function (ident) { | ||||
|         return ident.value; | ||||
|       }); | ||||
|       return ACME._getCsrWeb64(me, options, validatedDomains).then(function (csr) { | ||||
|         var body = { csr: csr }; | ||||
|         var payload = JSON.stringify(body); | ||||
| 
 | ||||
|         function pollCert() { | ||||
|           if (me.debug) { console.debug('[acme-v2.js] pollCert:'); } | ||||
|           return ACME._jwsRequest(me, { | ||||
|             options: options | ||||
|           , url: options.order.finalizeUrl | ||||
|           , protected: { kid: options._kid } | ||||
|           , payload: Enc.binToBuf(payload) | ||||
|           }).then(function (resp) { | ||||
|             if (me.debug) { console.debug('order finalized: resp.body:'); } | ||||
|             if (me.debug) { console.debug(resp.body); } | ||||
|       // 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) { | ||||
|           var body = { csr: csr }; | ||||
|           var payload = JSON.stringify(body); | ||||
| 
 | ||||
|             // https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.3
 | ||||
|             // Possible values are: "pending" => ("invalid" || "ready") => "processing" => "valid"
 | ||||
|             if ('valid' === resp.body.status) { | ||||
|               options._expires = resp.body.expires; | ||||
|               options._certificate = resp.body.certificate; | ||||
|           function pollCert() { | ||||
|             //#console.debug('[acme-v2.js] pollCert:');
 | ||||
|             return ACME._jwsRequest(me, { | ||||
|               options: options | ||||
|             , url: options.order.finalizeUrl | ||||
|             , protected: { kid: options._kid } | ||||
|             , payload: Enc.binToBuf(payload) | ||||
|             }).then(function (resp) { | ||||
|               //#console.debug('order finalized: resp.body:');
 | ||||
|               //#console.debug(resp.body);
 | ||||
| 
 | ||||
|               return resp.body; // return order
 | ||||
|             } | ||||
|               // https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.3
 | ||||
|               // Possible values are: "pending" => ("invalid" || "ready") => "processing" => "valid"
 | ||||
|               if ('valid' === resp.body.status) { | ||||
|                 options._expires = resp.body.expires; | ||||
|                 options._certificate = resp.body.certificate; | ||||
| 
 | ||||
|             if ('processing' === resp.body.status) { | ||||
|               return ACME._wait().then(pollCert); | ||||
|             } | ||||
|                 return resp.body; // return order
 | ||||
|               } | ||||
| 
 | ||||
|             if (me.debug) { console.debug("Error: bad status:\n" + JSON.stringify(resp.body, null, 2)); } | ||||
|               if ('processing' === resp.body.status) { | ||||
|                 return ACME._wait().then(pollCert); | ||||
|               } | ||||
| 
 | ||||
|               //#console.debug("Error: bad status:\n" + JSON.stringify(resp.body, null, 2));
 | ||||
| 
 | ||||
|               if ('pending' === resp.body.status) { | ||||
|                 return Promise.reject(new Error( | ||||
|                   "Did not finalize order: status 'pending'." | ||||
|                 + " Best guess: You have not accepted at least one challenge for each domain:\n" | ||||
|                 + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|                 + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|                 + JSON.stringify(resp.body, null, 2) | ||||
|                 )); | ||||
|               } | ||||
| 
 | ||||
|               if ('invalid' === resp.body.status) { | ||||
|                 return Promise.reject(new Error( | ||||
|                   "Did not finalize order: status 'invalid'." | ||||
|                 + " Best guess: One or more of the domain challenges could not be verified" | ||||
|                 + " (or the order was canceled).\n" | ||||
|                 + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|                 + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|                 + JSON.stringify(resp.body, null, 2) | ||||
|                 )); | ||||
|               } | ||||
| 
 | ||||
|               if ('ready' === resp.body.status) { | ||||
|                 return Promise.reject(new Error( | ||||
|                   "Did not finalize order: status 'ready'." | ||||
|                 + " Hmmm... this state shouldn't be possible here. That was the last state." | ||||
|                 + " This one should at least be 'processing'.\n" | ||||
|                 + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|                 + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|                 + JSON.stringify(resp.body, null, 2) + "\n\n" | ||||
|                 + "Please open an issue at https://git.coolaj86.com/coolaj86/acme-v2.js" | ||||
|                 )); | ||||
|               } | ||||
| 
 | ||||
|             if ('pending' === resp.body.status) { | ||||
|               return Promise.reject(new Error( | ||||
|                 "Did not finalize order: status 'pending'." | ||||
|               + " Best guess: You have not accepted at least one challenge for each domain:\n" | ||||
|               + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|               + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|               + JSON.stringify(resp.body, null, 2) | ||||
|               )); | ||||
|             } | ||||
| 
 | ||||
|             if ('invalid' === resp.body.status) { | ||||
|               return Promise.reject(new Error( | ||||
|                 "Did not finalize order: status 'invalid'." | ||||
|               + " Best guess: One or more of the domain challenges could not be verified" | ||||
|               + " (or the order was canceled).\n" | ||||
|               + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|               + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|               + JSON.stringify(resp.body, null, 2) | ||||
|               )); | ||||
|             } | ||||
| 
 | ||||
|             if ('ready' === resp.body.status) { | ||||
|               return Promise.reject(new Error( | ||||
|                 "Did not finalize order: status 'ready'." | ||||
|               + " Hmmm... this state shouldn't be possible here. That was the last state." | ||||
|               + " This one should at least be 'processing'.\n" | ||||
|                 "Didn't finalize order: Unhandled status '" + resp.body.status + "'." | ||||
|               + " This is not one of the known statuses...\n" | ||||
|               + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|               + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|               + JSON.stringify(resp.body, null, 2) + "\n\n" | ||||
|               + "Please open an issue at https://git.coolaj86.com/coolaj86/acme-v2.js" | ||||
|               )); | ||||
|             } | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|             return Promise.reject(new Error( | ||||
|               "Didn't finalize order: Unhandled status '" + resp.body.status + "'." | ||||
|             + " This is not one of the known statuses...\n" | ||||
|             + "Requested: '" + options.domains.join(', ') + "'\n" | ||||
|             + "Validated: '" + validatedDomains.join(', ') + "'\n" | ||||
|             + JSON.stringify(resp.body, null, 2) + "\n\n" | ||||
|             + "Please open an issue at https://git.coolaj86.com/coolaj86/acme-v2.js" | ||||
|             )); | ||||
|           return pollCert(); | ||||
|         }).then(function () { | ||||
|           //#console.debug('acme-v2: order was finalized');
 | ||||
|           return me.request({ method: 'GET', url: options._certificate, json: true }).then(function (resp) { | ||||
|             //#console.debug('acme-v2: csr submitted and cert received:');
 | ||||
|             // https://github.com/certbot/certbot/issues/5721
 | ||||
|             var certsarr = ACME.splitPemChain(ACME.formatPemChain((resp.body||''))); | ||||
|             // cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
 | ||||
|             // TODO CSR.info
 | ||||
|             var certs = { | ||||
|               expires: order.expires | ||||
|             , identifiers: order.identifiers | ||||
|             , cert: certsarr.shift() | ||||
|             , chain: certsarr.join('\n') | ||||
|             }; | ||||
|             //#console.debug(certs);
 | ||||
|             return certs; | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         return pollCert(); | ||||
|       }).then(function () { | ||||
|         if (me.debug) { console.debug('acme-v2: order was finalized'); } | ||||
|         // TODO POST-as-GET
 | ||||
|         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:'); } | ||||
|           // https://github.com/certbot/certbot/issues/5721
 | ||||
|           var certsarr = ACME.splitPemChain(ACME.formatPemChain((resp.body||''))); | ||||
|           //  cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
 | ||||
|           var certs = { | ||||
|             expires: order.expires | ||||
|           , identifiers: order.identifiers | ||||
|           //, authorizations: order.authorizations
 | ||||
|           , cert: certsarr.shift() | ||||
|           //, privkey: privkeyPem
 | ||||
|           , chain: certsarr.join('\n') | ||||
|           }; | ||||
|           if (me.debug) { console.debug(certs); } | ||||
|           return certs; | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| @ -2482,7 +2436,7 @@ ACME._finalizeOrder = function (me, options) { | ||||
| }; | ||||
| ACME._createOrder = function (me, options) { | ||||
|   return ACME._getAccountKid(me, options).then(function () { | ||||
|       // options._kid added
 | ||||
|     // options._kid added
 | ||||
|     var body = { | ||||
|       // raw wildcard syntax MUST be used here
 | ||||
|       identifiers: options.domains.sort(function (a, b) { | ||||
| @ -2499,7 +2453,7 @@ ACME._createOrder = function (me, options) { | ||||
|     }; | ||||
| 
 | ||||
|     var payload = JSON.stringify(body); | ||||
|     if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); } | ||||
|     //#console.debug('\n[DEBUG] newOrder\n');
 | ||||
|     return ACME._jwsRequest(me, { | ||||
|       options: options | ||||
|     , url: me._directoryUrls.newOrder | ||||
| @ -2513,8 +2467,8 @@ ACME._createOrder = function (me, options) { | ||||
|       , identifiers: body.identifiers | ||||
|       , _response: resp.body | ||||
|       }; | ||||
|       if (me.debug) { console.debug('[ordered]', location); } // the account id url
 | ||||
|       if (me.debug) { console.debug(resp); } | ||||
|       //#console.debug('[ordered]', location); // the account id url
 | ||||
|       //#console.debug(resp);
 | ||||
| 
 | ||||
|       if (!order.authorizations) { | ||||
|         return Promise.reject(new Error( | ||||
| @ -2526,7 +2480,7 @@ ACME._createOrder = function (me, options) { | ||||
|       return order; | ||||
|     }).then(function (order) { | ||||
|       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); | ||||
| 
 | ||||
|       function getNext() { | ||||
| @ -2565,13 +2519,12 @@ ACME._getAccountKid = function (me, options) { | ||||
|     return options._kid; | ||||
|   }); | ||||
| }; | ||||
| // _kid
 | ||||
| // registerAccount
 | ||||
| // postChallenge
 | ||||
| // finalizeOrder
 | ||||
| // getCertificate
 | ||||
| 
 | ||||
| //
 | ||||
| // Helper Methods
 | ||||
| //
 | ||||
| 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) { | ||||
|     // 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," | ||||
|     + " 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
 | ||||
|   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*/) { | ||||
|       // options.order = order;
 | ||||
|       return ACME._finalizeOrder(me, options); | ||||
| @ -2607,7 +2563,7 @@ ACME._getCsrWeb64 = function (me, options, validatedDomains) { | ||||
|     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 Enc.bufToUrlBase64(der); | ||||
|     }); | ||||
| @ -2708,13 +2664,13 @@ ACME._jwsRequest = function (me, bigopts) { | ||||
|       if (!bigopts.protected.kid) { bigopts.protected.kid = bigopts.options._kid; } | ||||
|     } | ||||
|     return me.Keypairs.signJws( | ||||
|       { jwk: bigopts.options.accountKeypair.privateKeyJwk | ||||
|       { jwk: bigopts.accountKey || bigopts.options.accountKeypair.privateKeyJwk | ||||
|       , protected: bigopts.protected | ||||
|       , payload: bigopts.payload | ||||
|       } | ||||
|     ).then(function (jws) { | ||||
|       if (me.debug) { console.debug('[acme-v2] ' + bigopts.url + ':'); } | ||||
|       if (me.debug) { console.debug(jws); } | ||||
|       //#console.debug('[acme-v2] ' + bigopts.url + ':');
 | ||||
|       //#console.debug(jws);
 | ||||
|       return ACME._request(me, { url: bigopts.url, json: jws }); | ||||
|     }); | ||||
|   }); | ||||
| @ -2769,7 +2725,7 @@ ACME._defaultRequest = function (opts) { | ||||
| }; | ||||
| 
 | ||||
| ACME._importKeypair = function (me, kp) { | ||||
|   var jwk = kp.privateKeyJwk; | ||||
|   var jwk = kp.privateKeyJwk || kp.kty && kp; | ||||
|   var p; | ||||
|   if (jwk) { | ||||
|     // 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) { | ||||
|   return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,""); | ||||
| }; | ||||
| @ -2850,20 +2794,14 @@ ACME._http01 = function (me, auth) { | ||||
|   }); | ||||
| }; | ||||
| ACME._removeChallenge = function (me, options, auth) { | ||||
|   var challengers = options.challenges || {}; | ||||
|   var removeChallenge = (challengers[auth.type] && challengers[auth.type].remove) || options.removeChallenge; | ||||
|   if (1 === removeChallenge.length) { | ||||
|     removeChallenge(auth).then(function () {}, function () {}); | ||||
|   } else if (2 === removeChallenge.length) { | ||||
|     removeChallenge(auth, function (err) { return err; }); | ||||
|   } 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 () {}); | ||||
|   } | ||||
|   return Promise.resolve().then(function () { | ||||
|     if (!options.challenges) { return; } | ||||
|     var ch = options.challenges[auth.type]; | ||||
|     ch.remove(auth).catch(function (e) { | ||||
|       console.warn("challenge.remove error:"); | ||||
|       console.warn(e); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| Enc.bufToUrlBase64 = function (u8) { | ||||
|  | ||||
		Laddar…
	
	
			
			x
			
			
		
	
		Referens i nytt ärende
	
	Block a user