From bf393f78abe858dc5da940dbcdd3ff7a32d1cdb6 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Thu, 20 Nov 2014 12:23:44 -0800 Subject: [PATCH] Changed client to use new form of challenges --- letsencrypt/client/CONFIG.py | 3 + letsencrypt/client/augeas_configurator.py | 8 +++ letsencrypt/client/client.py | 88 +++++++++++++---------- letsencrypt/client/configurator.py | 14 ++++ 4 files changed, 74 insertions(+), 39 deletions(-) diff --git a/letsencrypt/client/CONFIG.py b/letsencrypt/client/CONFIG.py index 8c7c4b6d4..12bb5a9c5 100644 --- a/letsencrypt/client/CONFIG.py +++ b/letsencrypt/client/CONFIG.py @@ -55,5 +55,8 @@ CHALLENGE_PREFERENCES = ["dvsni", "recoveryToken"] # Mutually Exclusive Challenges - only solve 1 EXCLUSIVE_CHALLENGES = [set(["dvsni", "simpleHttps"])] +# These are challenges that must be solved by a Configurator object +CONFIG_CHALLENGES = {"dvsni", "simpleHttps"} + # Rewrite rule arguments used for redirections to https vhost REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"] diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index af12dfffe..fbcabf788 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -411,3 +411,11 @@ class AugeasConfigurator(Configurator): Restart or refresh the server content """ raise Exception("Error: augeas Configurator class") + + def perform(self, challenge): + """ Perform the challenge """ + raise Exception("Error: augeas Configurator class") + + def cleanup(self): + """ Clean up any challenge configurations """ + raise Exception("Error: augeas Configurator class") diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 738f9d243..942dcb67e 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -15,8 +15,8 @@ from letsencrypt.client import logger, display from letsencrypt.client import le_util, crypto_util from letsencrypt.client.CONFIG import RSA_KEY_SIZE, CERT_PATH from letsencrypt.client.CONFIG import CHAIN_PATH, SERVER_ROOT, KEY_DIR, CERT_DIR -from letsencrypt.client.CONFIG import CERT_KEY_BACKUP -from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES +from letsencrypt.client.CONFIG import CERT_KEY_BACKUP, EXCLUSIVE_CHALLENGES +from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, CONFIG_CHALLENGES # it's weird to point to chocolate servers via raw IPv6 addresses, and such # addresses can be %SCARY in some contexts, so out of paranoia let's disable # them by default @@ -309,7 +309,11 @@ class Client(object): def cleanup_challenges(self, challenge_objs): logger.info("Cleaning up challenges...") for c in challenge_objs: - c.cleanup() + if c["type"] in CONFIG_CHALLENGES: + self.config.cleanup() + else: + #Handle other cleanup if needed + pass def is_expected_msg(self, msg_dict, expected, delay=3, rounds = 20): for i in range(rounds): @@ -369,17 +373,20 @@ class Client(object): challenge_objs, indicies = self.challenge_factory( self.names[0], c["challenges"], path) + responses = ["null"] * len(c["challenges"]) - responses = [None] * len(c["challenges"]) + # Perform challenges + for i, c_obj in challenge_objs: + response = "null" + if c_obj["type"] in CONFIG_CHALLENGES: + response = self.config.perform(c_obj) + else: + # Handle RecoveryToken type challenges + pass - # Perform challenges and populate responses - for i, c_obj in enumerate(challenge_objs): - if not c_obj.perform(): - logger.fatal("Challenge Failed") - sys.exit(1) for index in indicies[i]: - responses[index] = c_obj.generate_response() - + responses[index] = response + logger.info("Configured Apache for challenges; " + "waiting for verification...") @@ -389,7 +396,7 @@ class Client(object): """ Generate a plan to get authority over the identity TODO: Make sure that the challenges are feasible... - TODO Example: Do you have the recovery key? + Example: Do you have the recovery key? """ if combos: @@ -546,7 +553,7 @@ class Client(object): elif challenges[c]["type"] == "recoveryToken": logger.info("\tRecovery Token Challenge for name: %s." % name) challenge_objs_indicies.append(c) - challenge_objs.append(RecoveryToken()) + challenge_objs.append({type:"recoveryToken"}) else: logger.fatal("Challenge not currently supported") @@ -555,8 +562,8 @@ class Client(object): if sni_todo: # SNI_Challenge can satisfy many sni challenges at once so only # one "challenge object" is issued for all sni_challenges - challenge_objs.append(SNI_Challenge( - sni_todo, os.path.abspath(self.key_file), self.config)) + challenge_objs.append({"type":"dvsni", "listSNITuple":snitodo + "dvsni_key":os.path.abspath(self.key_file)}) challenge_obj_indicies.append(sni_satisfies) logger.debug(sni_todo) @@ -622,33 +629,36 @@ class Client(object): return selection - def get_cas(self): - DV_choices = [] - OV_choices = [] - EV_choices = [] - choices = [] - try: - with open("/etc/letsencrypt/.ca_offerings") as f: - for line in f: - choice = line.split(";", 1) - if 'DV' in choice[0]: - DV_choices.append(choice) - elif 'OV' in choice[0]: - OV_choices.append(choice) - else: - EV_choices.append(choice) + # Legacy Code: Although I would like to see a free and open marketplace + # in the future. The Let's Encrypt Client will not have this feature at + # launch + # def get_cas(self): + # DV_choices = [] + # OV_choices = [] + # EV_choices = [] + # choices = [] + # try: + # with open("/etc/letsencrypt/.ca_offerings") as f: + # for line in f: + # choice = line.split(";", 1) + # if 'DV' in choice[0]: + # DV_choices.append(choice) + # elif 'OV' in choice[0]: + # OV_choices.append(choice) + # else: + # EV_choices.append(choice) - # random.shuffle(DV_choices) - # random.shuffle(OV_choices) - # random.shuffle(EV_choices) - choices = DV_choices + OV_choices + EV_choices - choices = [(l[0], l[1]) for l in choices] + # # random.shuffle(DV_choices) + # # random.shuffle(OV_choices) + # # random.shuffle(EV_choices) + # choices = DV_choices + OV_choices + EV_choices + # choices = [(l[0], l[1]) for l in choices] - except IOError as e: - logger.fatal("Unable to find .ca_offerings file") - sys.exit(1) + # except IOError as e: + # logger.fatal("Unable to find .ca_offerings file") + # sys.exit(1) - return choices + # return choices def get_all_names(self): """ diff --git a/letsencrypt/client/configurator.py b/letsencrypt/client/configurator.py index ed4b9b980..0fefa64fe 100644 --- a/letsencrypt/client/configurator.py +++ b/letsencrypt/client/configurator.py @@ -1,3 +1,7 @@ +# Note: abc requires python 2.6 so we may remove this before +# launch. This should help in the creation of other configurators while +# we develop though + import abc class Configurator(object): @@ -115,3 +119,13 @@ class Configurator(object): Restart or refresh the server content """ return + + @abc.abstractmethod + def perform(self, chall_type, tup): + """ Perform the given challenge""" + return + + @abc.abstractmethod + def cleanup(self): + """ Cleanup configuration changes from challenge """ + return