def _poll_challenges( self, chall_update, best_effort, min_sleep=3, max_rounds=15): """Wait for all challenge results to be determined.""" dom_to_check = set(chall_update.keys()) comp_domains = set() rounds = 0 while dom_to_check and rounds < max_rounds: # TODO: Use retry-after... time.sleep(min_sleep) for domain in dom_to_check: comp_challs, failed_challs = self._handle_check( domain, chall_update[domain]) if len(comp_challs) == len(chall_update[domain]): comp_domains.add(domain) elif not failed_challs: for chall in comp_challs: chall_update[domain].remove(chall) # We failed some challenges... damage control else: # Right now... just assume a loss and carry on... if best_effort: comp_domains.add(domain) else: raise errors.AuthorizationError( "Failed Authorization procedure for %s" % domain) dom_to_check -= comp_domains comp_domains.clear() rounds += 1
def verify_authzr_complete(self): """Verifies that all authorizations have been decided. :returns: Whether all authzr are complete :rtype: bool """ for authzr in self.authzr.values(): if (authzr.body.status != messages.STATUS_VALID and authzr.body.status != messages.STATUS_INVALID): raise errors.AuthorizationError("Incomplete authorizations")
def _find_updated_challb(self, authzr, achall): # pylint: disable=no-self-use """Find updated challenge body within Authorization Resource. .. warning:: This assumes only one instance of type of challenge in each challenge resource. :param .AuthorizationResource authzr: Authorization Resource :param .AnnotatedChallenge achall: Annotated challenge for which to get status """ for authzr_challb in authzr.body.challenges: if type(authzr_challb.chall) is type(achall.challb.chall): # noqa return authzr_challb raise errors.AuthorizationError( "Target challenge not found in authorization resource")
def _get_chall_status(self, authzr, achall): # pylint: disable=no-self-use """Get the status of the challenge. .. warning:: This assumes only one instance of type of challenge in each challenge resource. :param authzr: Authorization Resource :type authzr: :class:`acme.messages2.AuthorizationResource` :param achall: Annotated challenge for which to get status :type achall: :class:`letsencrypt.achallenges.AnnotatedChallenge` """ for authzr_challb in authzr.body.challenges: if type(authzr_challb.chall) is type(achall.challb.chall): return authzr_challb.status raise errors.AuthorizationError( "Target challenge not found in authorization resource")
def get_authorizations(self, domains, best_effort=False): """Retrieve all authorizations for challenges. :param list domains: Domains for authorization :param bool best_effort: Whether or not all authorizations are required (this is useful in renewal) :returns: List of authorization resources :rtype: list :raises .AuthorizationError: If unable to retrieve all authorizations """ for domain in domains: self.authzr[domain] = self.acme.request_domain_challenges( domain, self.account.regr.new_authzr_uri) self._choose_challenges(domains) # While there are still challenges remaining... while self.achalls: resp = self._solve_challenges() logger.info("Waiting for verification...") # Send all Responses - this modifies achalls self._respond(resp, best_effort) # Just make sure all decisions are complete. self.verify_authzr_complete() # Only return valid authorizations retVal = [ authzr for authzr in self.authzr.values() if authzr.body.status == messages.STATUS_VALID ] if not retVal: raise errors.AuthorizationError( "Challenges failed for all domains") return retVal
def _find_smart_path(challbs, preferences, combinations): """Find challenge path with server hints. Can be called if combinations is included. Function uses a simple ranking system to choose the combo with the lowest cost. """ chall_cost = {} max_cost = 1 for i, chall_cls in enumerate(preferences): chall_cost[chall_cls] = i max_cost += i # max_cost is now equal to sum(indices) + 1 best_combo = [] # Set above completing all of the available challenges best_combo_cost = max_cost combo_total = 0 for combo in combinations: for challenge_index in combo: combo_total += chall_cost.get( challbs[challenge_index].chall.__class__, max_cost) if combo_total < best_combo_cost: best_combo = combo best_combo_cost = combo_total combo_total = 0 if not best_combo: msg = ("Client does not support any combination of challenges that " "will satisfy the CA.") logger.fatal(msg) raise errors.AuthorizationError(msg) return best_combo
def _report_no_chall_path(): """Logs and raises an error that no satisfiable chall path exists.""" msg = ("Client with the currently selected authenticator does not support " "any combination of challenges that will satisfy the CA.") logger.fatal(msg) raise errors.AuthorizationError(msg)