From efaec60e6b42e026bbee35478eb5162752f476fc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 18 Nov 2014 08:13:06 -0800 Subject: [PATCH 1/6] Switched from using urllib2 to requests. urllib2 is a security hazzard, it does not perform certificate checks against a trust root by default, nor does it perform service_identity checks. Also, requests has a prettier API. --- letsencrypt/client/client.py | 15 +++++++-------- letsencrypt/client/recovery_contact_challenge.py | 7 ++++--- setup.py | 1 + 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 6f17a42a8..894861b7c 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -1,10 +1,7 @@ #!/usr/bin/env python import M2Crypto -import urllib2, json -# XXX TODO: per https://docs.google.com/document/pub? -#id=1roBIeSJsYq3Ntpf6N0PIeeAAvu4ddn7mGo6Qb7aL7ew -# urllib2 is unsafe (!) and must be replaced +import json import os, grp, pwd, sys, time, random, sys, shutil # This line suppresses the no logging found for module 'jose' warning @@ -22,6 +19,8 @@ from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 +import requests + from letsencrypt.client.acme import acme_object_validate from letsencrypt.client.sni_challenge import SNI_Challenge from letsencrypt.client.payment_challenge import Payment_Challenge @@ -478,10 +477,10 @@ class Client(object): def send(self, json_obj): try: acme_object_validate(json.dumps(json_obj)) - response = urllib2.urlopen( - self.server_url, json.dumps(json_obj)).read() - acme_object_validate(response) - return json.loads(response) + response = requests.get(self.server_url, json=json_obj) + body = response.content + acme_object_validate(body) + return response.json() except: logger.fatal("Send() failed... may have lost connection to server") sys.exit(8) diff --git a/letsencrypt/client/recovery_contact_challenge.py b/letsencrypt/client/recovery_contact_challenge.py index 18a0d2816..d3160fd2e 100644 --- a/letsencrypt/client/recovery_contact_challenge.py +++ b/letsencrypt/client/recovery_contact_challenge.py @@ -1,8 +1,9 @@ +import requests + from letsencrypt.client.challenge import Challenge from letsencrypt.client import logger from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT -# TODO: Replace urllib2 because of lack of certificate validation checks -import dialog, urllib2 +import dialog class RecoveryContact(Challenge): @@ -48,7 +49,7 @@ class RecoveryContact(Challenge): def poll(self, rounds = 10, quiet = True): for i in range(rounds): - if urllib2.urlopen(self.successURL).getcode() != 200: + if requests.get(self.successURL).status_code != 200: time.sleep(self.poll_delay) else: return True diff --git a/setup.py b/setup.py index 2bc387f63..23584522f 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ setup( ], install_requires=[ #'dialog', + 'requests>=2.4.3', 'protobuf', 'python-augeas', 'pycrypto', From a9e0028007847048a7afa00e3968b022440124db Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 18 Nov 2014 09:09:56 -0800 Subject: [PATCH 2/6] Use the older requests API --- letsencrypt/client/client.py | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 894861b7c..47c6328a9 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -476,8 +476,9 @@ class Client(object): def send(self, json_obj): try: - acme_object_validate(json.dumps(json_obj)) - response = requests.get(self.server_url, json=json_obj) + json_encoded = json.dumps(json_obj) + acme_object_validate(json_encoded) + response = requests.get(self.server_url, data=json_encoded) body = response.content acme_object_validate(body) return response.json() diff --git a/setup.py b/setup.py index 23584522f..cb71e09d0 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( ], install_requires=[ #'dialog', - 'requests>=2.4.3', + 'requests', 'protobuf', 'python-augeas', 'pycrypto', From bcda03d94899cb2e90a40d51dc964043cf2421fc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 18 Nov 2014 09:15:36 -0800 Subject: [PATCH 3/6] Set the content-type header and use POST, which the ACME spec requires --- letsencrypt/client/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 47c6328a9..09dd9b1ab 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -478,7 +478,11 @@ class Client(object): try: json_encoded = json.dumps(json_obj) acme_object_validate(json_encoded) - response = requests.get(self.server_url, data=json_encoded) + response = requests.post( + self.server_url, + data=json_encoded, + headers={"Content-Type": "application/json"}, + ) body = response.content acme_object_validate(body) return response.json() From b8170a38eccb92400329e826fe1e73365d3dc1fe Mon Sep 17 00:00:00 2001 From: Seth Schoen Date: Tue, 18 Nov 2014 16:39:38 -0800 Subject: [PATCH 4/6] docstrings and add JSON pretty-printing code --- letsencrypt/client/acme.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 3a419c8f1..306ee9171 100755 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -12,6 +12,10 @@ schemata = {schema: json.load(open("letsencrypt/client/schemata/%s.json" % schem } def acme_object_validate(j): + """Validate a JSON object against the ACME protocol using JSON Schema. + Success will return None; failure to validate will raise a + jsonschema.ValidationError exception describing the reason that the + object could not be validated successfully.""" j = json.loads(j) if not isinstance(j, dict): raise jsonschema.ValidationError("this is not a dictionary object") @@ -21,3 +25,7 @@ def acme_object_validate(j): raise jsonschema.ValidationError("unknown type %s" % j["type"]) jsonschema.validate(j, schemata[j["type"]]) +def pretty(s): + """Return a pretty-printed version of any JSON string (useful when + printing out protocol messages for debugging purposes.""" + return json.dumps(json.loads(s), indent=4) From 0b75c5194f5278471130a3b3e81fe47cae70cb83 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 18 Nov 2014 16:41:13 -0800 Subject: [PATCH 5/6] Removed trailing whitespace as well as unused imports --- letsencrypt.py | 14 +- letsencrypt/client/challenge.py | 2 +- letsencrypt/client/client.py | 25 +-- letsencrypt/client/configurator.py | 182 +++++++++--------- letsencrypt/client/crypto_util.py | 5 +- letsencrypt/client/display.py | 26 +-- letsencrypt/client/interactive_challenge.py | 9 +- letsencrypt/client/logger.py | 19 +- letsencrypt/client/payment_challenge.py | 11 +- .../client/recovery_contact_challenge.py | 8 +- .../client/recovery_token_challenge.py | 8 +- letsencrypt/client/sni_challenge.py | 24 +-- 12 files changed, 157 insertions(+), 176 deletions(-) diff --git a/letsencrypt.py b/letsencrypt.py index da302af2b..5feedcb74 100755 --- a/letsencrypt.py +++ b/letsencrypt.py @@ -16,10 +16,10 @@ def main(): sys.exit("\nOnly root can run letsencrypt.\n") # Parse options try: - opts, args = getopt.getopt(sys.argv[1:], "", ["text", "test", - "view-checkpoints", - "privkey=", "csr=", - "server=", "rollback=", + opts, args = getopt.getopt(sys.argv[1:], "", ["text", "test", + "view-checkpoints", + "privkey=", "csr=", + "server=", "rollback=", "revoke", "agree-eula", "redirect", "no-redirect", @@ -82,10 +82,10 @@ def main(): display.setDisplay(display.NcursesDisplay()) else: display.setDisplay(display.FileDisplay(sys.stdout)) - + if not server: server = ACME_SERVER - + c = client.Client(server, csr, privkey, curses) if flag_revoke: c.list_certs_keys() @@ -108,7 +108,7 @@ def print_options(): "view-checkpoints (Used to view available checkpoints and " + "see what configuration changes have been made)", "rollback=X (Revert the configuration X number of checkpoints)", - "redirect (Automatically redirect all HTTP traffic to " + + "redirect (Automatically redirect all HTTP traffic to " + "HTTPS for the newly authenticated vhost)", "no-redirect (Skip the HTTPS redirect question, " + "allowing both HTTP and HTTPS)", diff --git a/letsencrypt/client/challenge.py b/letsencrypt/client/challenge.py index f55183d24..a25e60be0 100644 --- a/letsencrypt/client/challenge.py +++ b/letsencrypt/client/challenge.py @@ -10,4 +10,4 @@ class Challenge(object): logger.error("Error - base class challenge.generate_response()") def clean(self): logger.error("Error - base class challenge.clean()") - + diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index a9f2c6ea4..0f625804d 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -2,7 +2,7 @@ import M2Crypto import json -import os, grp, pwd, sys, time, random, sys, shutil +import os, time, sys, shutil # This line suppresses the no logging found for module 'jose' warning # TODO: Check out this module and see if we should be using it for our @@ -12,22 +12,15 @@ logging.basicConfig(filename="/dev/null", level=logging.ERROR) import jose, csv -import subprocess -from M2Crypto import EVP, X509, RSA -from Crypto.Random import get_random_bytes -from Crypto.PublicKey import RSA -from Crypto.Signature import PKCS1_v1_5 -from Crypto.Hash import SHA256 import requests from letsencrypt.client.acme import acme_object_validate from letsencrypt.client.sni_challenge import SNI_Challenge -from letsencrypt.client.payment_challenge import Payment_Challenge from letsencrypt.client import configurator from letsencrypt.client import logger, display -from letsencrypt.client import le_util, crypto_util, display -from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE, CERT_PATH +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 @@ -50,7 +43,7 @@ class Client(object): self.config = configurator.Configurator(SERVER_ROOT) self.server = ca_server - + self.csr_file = cert_signing_request self.key_file = private_key @@ -70,7 +63,7 @@ class Client(object): sys.exit(1) self.redirect = redirect - + # Display preview warning if not eula: with open('EULA') as f: @@ -202,7 +195,7 @@ class Client(object): os.remove(c['backup_cert_file']) os.remove(c['backup_key_file']) - + def list_certs_keys(self): list_file = CERT_KEY_BACKUP + "LIST" certs = [] @@ -214,10 +207,10 @@ class Client(object): c_sha1_vh = {} for x in self.config.get_all_certs_keys(): try: - c_sha1_vh[M2Crypto.X509.load_cert(x[0]).get_fingerprint(md='sha1')] = x[2] + c_sha1_vh[M2Crypto.X509.load_cert(x[0]).get_fingerprint(md='sha1')] = x[2] except: continue - + with open(list_file, 'rb') as csvfile: csvreader = csv.reader(csvfile) for row in csvreader: @@ -315,7 +308,7 @@ class Client(object): #if self.ocsp_stapling: # TODO enable OCSP Stapling # continue - + def certificate_request(self, csr_der, key): logger.info("Preparing and sending CSR..") diff --git a/letsencrypt/client/configurator.py b/letsencrypt/client/configurator.py index bf10b90d6..6f5aabf58 100644 --- a/letsencrypt/client/configurator.py +++ b/letsencrypt/client/configurator.py @@ -3,18 +3,16 @@ import subprocess import re import os import sys -import stat import socket import time import shutil -import errno from letsencrypt.client.CONFIG import SERVER_ROOT, BACKUP_DIR from letsencrypt.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, LE_VHOST_EXT from letsencrypt.client import logger, le_util -#from CONFIG import SERVER_ROOT, BACKUP_DIR, REWRITE_HTTPS_ARGS, CONFIG_DIR, +#from CONFIG import SERVER_ROOT, BACKUP_DIR, REWRITE_HTTPS_ARGS, CONFIG_DIR, #from CONFIG import WORK_DIR, TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR, OPTIONS_SSL_CONF, TRUSTIFY_VHOST_EXT #import logger, le_util @@ -38,7 +36,7 @@ from letsencrypt.client import logger, le_util # Note: This protocol works for filenames with spaces in it, the sites are # properly set up and directives are changed appropriately, but Apache won't # recognize names in sites-enabled that have spaces. These are not added to the -# Apache configuration. It may be wise to warn the user if they are trying +# Apache configuration. It may be wise to warn the user if they are trying # to use vhost filenames that contain spaces and offer to change ' ' to '_' # Note: FILEPATHS and changes to files are transactional. They are copied @@ -69,24 +67,24 @@ class Configurator(object): This class was originally developed for Apache 2.2 and has not seen a an overhaul to include proper setup of new Apache configurations. - The biggest changes have been the IncludeOptional directive, the - deprecation of the NameVirtualHost directive, and the name change of + The biggest changes have been the IncludeOptional directive, the + deprecation of the NameVirtualHost directive, and the name change of mod_ssl.c to ssl_module. Although these changes have not been implemented yet, they will be shortly. That being said, this class can still adequately configure most typical Apache 2.4 servers as the deprecated NameVirtualHost has no effect and the typical directories are parsed by the Augeas configuration parser automatically. - + The API of this class will change in the coming weeks as the exact needs of client's are clarified with the new and developing protocol. - + This class will eventually derive from a generic Configurator class so that other Configurators (like Nginx) can be developed and interoperate with the client. """ def __init__(self, server_root=SERVER_ROOT): - # TODO: this instantiation can be optimized to only load Httd + # TODO: this instantiation can be optimized to only load Httd # relevant files - I believe -> NO_MODL_AUTOLOAD self.server_root = server_root @@ -103,7 +101,7 @@ class Configurator(object): self.check_parsing_errors() # This problem has been fixed in Augeas 1.0 self.standardize_excl() - + # Determine user's main config file self.__set_user_config_file() @@ -114,20 +112,20 @@ class Configurator(object): self.assoc = dict() # Verify that all directories and files exist with proper permissions self.verify_setup() - + # Note: initialization doesn't check to see if the config is correct # by Apache's standards. This should be done by the client (client.py) - # if it is desired. There may be instances where correct configuration + # if it is desired. There may be instances where correct configuration # isn't required on startup. - # TODO: This function can be improved to ensure that the final directives - # are being modified whether that be in the include files or in the + # TODO: This function can be improved to ensure that the final directives + # are being modified whether that be in the include files or in the # virtualhost declaration - these directives can be overwritten def deploy_cert(self, vhost, cert, key, cert_chain=None): """ Currently tries to find the last directives to deploy the cert in the given virtualhost. If it can't find the directives, it searches - the "included" confs. The function verifies that it has located + the "included" confs. The function verifies that it has located the three directives and finally modifies them to point to the correct destination TODO: Make sure last directive is changed @@ -136,23 +134,23 @@ class Configurator(object): """ search = {} path = {} - + path["cert_file"] = self.find_directive(self.case_i("SSLCertificateFile"), None, vhost.path) path["cert_key"] = self.find_directive(self.case_i("SSLCertificateKeyFile"), None, vhost.path) # Only include if a certificate chain is specified if cert_chain is not None: path["cert_chain"] = self.find_directive(self.case_i("SSLCertificateChainFile"), None, vhost.path) - + if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0: # Throw some "can't find all of the directives error" logger.warn("Warn: cannot find a cert or key directive in " + vhost.path) logger.warn("VirtualHost was not modified") # Presumably break here so that the virtualhost is not modified return False - + logger.info("Deploying Certificate to VirtualHost %s" % vhost.file) - + self.aug.set(path["cert_file"][0], cert) self.aug.set(path["cert_key"][0], key) if cert_chain is not None: @@ -160,7 +158,7 @@ class Configurator(object): self.add_dir(vhost.path, "SSLCertificateChainFile", cert_chain) else: self.aug.set(path["cert_chain"][0], cert_chain) - + self.save_notes += "Changed vhost at %s with addresses of %s\n" % (vhost.file, vhost.addrs) self.save_notes += "\tSSLCertificateFile %s\n" % cert self.save_notes += "\tSSLCertificateKeyFile %s\n" % key @@ -218,7 +216,7 @@ class Configurator(object): """ self.assoc[dn] = vh return - + def get_all_names(self): """ Returns all names found in the Apache Configuration @@ -259,7 +257,7 @@ class Configurator(object): #def __is_private_ip(ipaddr): # re.compile() - + def __add_servernames(self, host): """ @@ -270,7 +268,7 @@ class Configurator(object): args = self.aug.match(name + "/*") for arg in args: host.add_name(self.aug.get(arg)) - + def __create_vhost(self, path): """ @@ -307,13 +305,13 @@ class Configurator(object): addr: string """ # search for NameVirtualHost directive for ip_addr - # check httpd.conf, ports.conf, + # check httpd.conf, ports.conf, # note ip_addr can be FQDN although Apache does not recommend it paths = self.find_directive(self.case_i("NameVirtualHost"), None) name_vh = [] for p in paths: name_vh.append(self.aug.get(p)) - + # Mixed and matched wildcard NameVirtualHost with VirtualHost # behavior is undefined. Make sure that an exact match exists @@ -321,7 +319,7 @@ class Configurator(object): for vh in name_vh: if vh == addr: return True - + return False def add_name_vhost(self, addr): @@ -332,17 +330,17 @@ class Configurator(object): """ aug_file_path = "/files%sports.conf" % self.server_root self.add_dir_to_ifmodssl(aug_file_path, "NameVirtualHost", addr) - + if len(self.find_directive(self.case_i("NameVirtualHost"), self.case_i(addr))) == 0: logger.warn("ports.conf is not included in your Apache config...") logger.warn("Adding NameVirtualHost directive to httpd.conf") self.add_dir_to_ifmodssl("/files" + self.server_root + "httpd.conf", "NameVirtualHost", addr) - + self.save_notes += 'Setting %s to be NameBasedVirtualHost\n' % addr def add_dir_to_ifmodssl(self, aug_conf_path, directive, val): """ - Adds given directived and value along configuration path within + Adds given directived and value along configuration path within an IfMod mod_ssl.c block. If the IfMod block does not exist in the file, it is created. """ @@ -377,7 +375,7 @@ class Configurator(object): # Check for NameVirtualHost # First see if any of the vhost addresses is a _default_ addr for addr in vhost.addrs: - tup = addr.partition(":") + tup = addr.partition(":") if tup[0] == "_default_": if not self.is_name_vhost(default_addr): logger.debug("Setting all VirtualHosts on " + default_addr + " to be name based virtual hosts") @@ -389,7 +387,7 @@ class Configurator(object): if not self.is_name_vhost(addr): logger.debug("Setting VirtualHost at" + addr + "to be a name based virtual host") self.add_name_vhost(addr) - + return True def get_ifmod(self, aug_conf_path, mod): @@ -404,7 +402,7 @@ class Configurator(object): ifMods = self.aug.match("%s/IfModule/*[self::arg='%s']" % (aug_conf_path, mod)) # Strip off "arg" at end of first ifmod path return ifMods[0][:len(ifMods[0]) - 3] - + def add_dir(self, aug_conf_path, directive, arg): """ Appends directive to end of file given by aug_conf_path @@ -414,9 +412,9 @@ class Configurator(object): self.aug.set(aug_conf_path + "/directive[last()]/arg", arg) else: for i in range(len(arg)): - self.aug.set(aug_conf_path + "/directive[last()]/arg["+str(i+1)+"]", arg[i]) - - + self.aug.set(aug_conf_path + "/directive[last()]/arg["+str(i+1)+"]", arg[i]) + + def find_directive(self, directive, arg=None, start=""): """ Recursively searches through config files to find directives @@ -424,17 +422,17 @@ class Configurator(object): TODO: arg should probably be a list Note: Augeas is inherently case sensitive while Apache is case - insensitive. Augeas 1.0 allows case insensitive regexes like + insensitive. Augeas 1.0 allows case insensitive regexes like regexp(/Listen/, 'i'), however the version currently supported by Ubuntu 0.10 does not. Thus I have included my own case insensitive transformation by calling case_i() on everything to maintain compatibility. """ - + # Cannot place member variable in the definition of the function so... if not start: start = "/files%sapache2.conf" % self.server_root - + #Debug code #print "find_dir:", directive, "arg:", arg, " | Looking in:", start # No regexp code @@ -442,20 +440,20 @@ class Configurator(object): # matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg") # else: # matches = self.aug.match(start + "//*[self::directive='" + directive+"']/* [self::arg='" + arg + "']") - + # includes = self.aug.match(start + "//* [self::directive='Include']/* [label()='arg']") if arg is None: matches = self.aug.match(start + "//*[self::directive=~regexp('%s')]/arg" % directive) else: matches = self.aug.match(start + "//*[self::directive=~regexp('%s')]/*[self::arg=~regexp('%s')]" % (directive, arg)) - + includes = self.aug.match(start + "//* [self::directive=~regexp('%s')]/* [label()='arg']" % self.case_i('Include')) - + for include in includes: # start[6:] to strip off /files matches.extend(self.find_directive(directive, arg, self.get_include_path(self.strip_dir(start[6:]), self.aug.get(include)))) - + return matches def case_i(self, string): @@ -463,15 +461,15 @@ class Configurator(object): Returns a sloppy, but necessary version of a case insensitive regex. Any string should be able to be submitted and the string is escaped and then made case insensitive. - May be replaced by a more proper /i once augeas 1.0 is widely + May be replaced by a more proper /i once augeas 1.0 is widely supported. """ - + return "".join(["["+c.upper()+c.lower()+"]" if c.isalpha() else c for c in re.escape(string)]) - + def strip_dir(self, path): """ - Precondition: file_path is a file path, ie. not an augeas section + Precondition: file_path is a file path, ie. not an augeas section or directive path Returns the current directory from a file_path along with the file """ @@ -483,7 +481,7 @@ class Configurator(object): def get_include_path(self, cur_dir, arg): """ - Converts an Apache Include directive argument into an Augeas + Converts an Apache Include directive argument into an Augeas searchable path Returns path string """ @@ -493,11 +491,11 @@ class Configurator(object): # If the attacker can Include anything though... and this function # only operates on Apache real config data... then the attacker has # already won. - # Perhaps it is better to simply check the permissions on all + # Perhaps it is better to simply check the permissions on all # included files? # check_config to validate apache config doesn't work because it # would create a race condition between the check and this input - + # TODO: Fix this # Check to make sure only expected characters are used <- maybe remove # validChars = re.compile("[a-zA-Z0-9.*?_-/]*") @@ -513,10 +511,10 @@ class Configurator(object): elif arg.startswith("conf/"): arg = self.server_root + arg[5:] # TODO: Test if Apache allows ../ or ~/ for Includes - + # Attempts to add a transform to the file if one does not already exist self.parse_file(arg) - + # Argument represents an fnmatch regular expression, convert it # Split up the path and convert each into an Augeas accepted regex # then reassemble @@ -524,14 +522,14 @@ class Configurator(object): postfix = "" splitArg = arg.split("/") for idx, split in enumerate(splitArg): - # * and ? are the two special fnmatch characters + # * and ? are the two special fnmatch characters if "*" in split or "?" in split: # Turn it into a augeas regex # TODO: Can this instead be an augeas glob instead of regex splitArg[idx] = "* [label()=~regexp('%s')]" % self.fnmatch_to_re(split) # Reassemble the argument arg = "/".join(splitArg) - + # If the include is a directory, just return the directory as a file if arg.endswith("/"): return "/files" + arg[:len(arg)-1] @@ -563,7 +561,7 @@ class Configurator(object): ssl_fp = avail_fp[:-(len(".conf"))] + LE_VHOST_EXT else: ssl_fp = avail_fp + LE_VHOST_EXT - + # First register the creation so that it is properly removed if # configuration is rolled back self.register_file_creation(False, ssl_fp) @@ -617,7 +615,7 @@ class Configurator(object): logger.info("Created an SSL vhost at %s" % ssl_fp) self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp self.save() - + # We know the length is one because of the assertion above ssl_vhost = self.__create_vhost(vh_p[0]) self.vhosts.append(ssl_vhost) @@ -633,7 +631,7 @@ class Configurator(object): self.add_name_vhost(ssl_addrs[i]) logger.info("Enabling NameVirtualHosts on " + ssl_addrs[i]) need_to_save = True - + if need_to_save: self.save() @@ -699,14 +697,14 @@ class Configurator(object): return True, 0 # Rewrite path exists but is not a letsencrypt https rule return True, 2 - + def create_redirect_vhost(self, ssl_vhost): # Consider changing this to a dictionary check # Make sure adding the vhost will be safe conflict, hostOrAddrs = self.__conflicting_host(ssl_vhost) if conflict: return False, hostOrAddrs - + redirect_addrs = hostOrAddrs # get servernames and serveraliases @@ -729,7 +727,7 @@ RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=permanent]\n\ ErrorLog /var/log/apache2/redirect.error.log \n\ LogLevel warn \n\ \n" - + # Write out the file # This is the default name redirect_filename = "letsencrypt-redirect.conf" @@ -758,12 +756,12 @@ LogLevel warn \n\ new_fp = self.server_root + "sites-available/" + redirect_filename new_vhost = self.__create_vhost("/files" + new_fp) self.vhosts.append(new_vhost) - + # Finally create documentation for the change self.save_notes += 'Created a port 80 vhost, %s, for redirection to ssl vhost %s\n' % (new_vhost.file, ssl_vhost.file) return True, new_vhost - + def __conflicting_host(self, ssl_vhost): ''' Checks for a conflicting host, such that a new port 80 host could not @@ -798,7 +796,7 @@ LogLevel warn \n\ redirect_addrs = redirect_addrs + ssl_a_vhttp return False, redirect_addrs - + def __general_vhost(self, ssl_vhost): """ Function needs to be thoroughly tested and perhaps improved @@ -811,7 +809,7 @@ LogLevel warn \n\ ssl_addrs = ssl_vhost.addrs if ssl_addrs == ["_default_:443"]: ssl_addrs = ["*:443"] - + for vh in self.vhosts: found = 0 # Not the same vhost, and same number of addresses @@ -826,7 +824,7 @@ LogLevel warn \n\ if test_tup[2] == "80" or test_tup[2] == "" or test_tup[2] == "*": found += 1 break - # Check to make sure all addresses were found + # Check to make sure all addresses were found # and names are equal if found == len(ssl_vhost.addrs) and set(vh.names) == set(ssl_vhost.names): return vh @@ -875,7 +873,7 @@ LogLevel warn \n\ continue break return avail_fp - + def is_site_enabled(self, avail_fp): """ Checks to see if the given site is enabled @@ -903,15 +901,15 @@ LogLevel warn \n\ self.save_notes += 'Enabled site %s\n' % vhost.file return True return False - + def enable_mod(self, mod_name): """ Enables mod_ssl """ try: - # Use check_output so the command will finish before reloading + # Use check_output so the command will finish before reloading subprocess.check_call(["sudo", "a2enmod", mod_name], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) - # Hopefully this waits for output + # Hopefully this waits for output subprocess.check_call(["sudo", "/etc/init.d/apache2", "restart"], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) except: logger.error("Error enabling mod_" + mod_name) @@ -949,12 +947,12 @@ LogLevel warn \n\ #self.aug.add_transform("Httpd.lns", self.httpd_incl, None, self.httpd_excl) self.__add_httpd_transform(file_path) self.aug.load() - + def save_apache_config(self): # Not currently used # Should be safe because it is a protected directory shutil.copytree(self.server_root, BACKUP_DIR + "apache2-" + str(time.time())) - + def recovery_routine(self): """ Revert all previously modified files. First, any changes found in @@ -997,13 +995,13 @@ LogLevel warn \n\ except IOError: logger.fatal("Unable to remove filepaths contained within %s" % file_list) sys.exit(41) - + return True def verify_setup(self): ''' Make sure that files/directories are setup with appropriate permissions - Aim for defensive coding... make sure all input files + Aim for defensive coding... make sure all input files have permissions of root ''' le_util.make_or_verify_dir(CONFIG_DIR, 0755) @@ -1025,7 +1023,7 @@ LogLevel warn \n\ # This is a hack... work around... submit to augeas if still not fixed excl = ["*.augnew", "*.augsave", "*.dpkg-dist", "*.dpkg-bak", "*.dpkg-new", "*.dpkg-old", "*.rpmsave", "*.rpmnew", "*~", self.server_root + "*.augsave", self.server_root + "*~", self.server_root + "*/*augsave", self.server_root + "*/*~", self.server_root + "*/*/*.augsave", self.server_root + "*/*/*~"] - + for i in range(len(excl)): self.aug.set("/augeas/load/Httpd/excl[%d]" % (i+1), excl[i]) @@ -1040,7 +1038,7 @@ LogLevel warn \n\ for e in error_files: # Check to see if it was an error resulting from the use of - # the httpd lens + # the httpd lens lens_path = self.aug.get(e + '/lens') # As aug.get may return null if lens_path and 'httpd.aug' in lens_path: @@ -1072,7 +1070,7 @@ LogLevel warn \n\ try: p = subprocess.Popen(['/etc/init.d/apache2', 'restart'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) text = p.communicate() - + if p.returncode != 0: # Enter recovery routine... @@ -1118,7 +1116,7 @@ LogLevel warn \n\ Saves all changes to the configuration files This function is not transactional TODO: Instead rely on challenge to backup all files before modifications - + title: string - The title of the save. If a title is given, the configuration will be saved as a new checkpoint and put in a timestamped directory. @@ -1148,7 +1146,7 @@ LogLevel warn \n\ return False # Retrieve list of modified files - # Note: Noop saves can cause the file to be listed twice, I used a + # Note: Noop saves can cause the file to be listed twice, I used a # set to remove this possibility. This is a known augeas 0.10 error. save_paths = self.aug.match("/augeas/events/saved") @@ -1172,7 +1170,7 @@ LogLevel warn \n\ self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files) else: self.add_to_checkpoint(IN_PROGRESS_DIR, save_files) - + if title and not temporary and os.path.isdir(IN_PROGRESS_DIR): success = self.__finalize_checkpoint(IN_PROGRESS_DIR, title) @@ -1181,7 +1179,7 @@ LogLevel warn \n\ # This will be hopefully be cleaned up on the recovery # routine startup sys.exit(9) - + self.aug.set("/augeas/save", save_state) self.save_notes = "" @@ -1213,7 +1211,7 @@ LogLevel warn \n\ def add_to_checkpoint(self, cp_dir, save_files): le_util.make_or_verify_dir(cp_dir, 0755) - + existing_filepaths = [] op_fd = None # Open up FILEPATHS differently depending on if it already exists @@ -1226,7 +1224,7 @@ LogLevel warn \n\ idx = len(existing_filepaths) for filename in save_files: if filename not in existing_filepaths: - # Tag files with index so multiple files can + # Tag files with index so multiple files can # have the same filename logger.debug("Creating backup of %s" % filename) shutil.copy2(filename, cp_dir + os.path.basename(filename) + "_" + str(idx)) @@ -1252,7 +1250,7 @@ LogLevel warn \n\ if len(backups) < rollback: logger.error("Unable to rollback %d checkpoints, only %d exist" % (rollback, len(backups))) - + while rollback > 0 and backups: cp_dir = BACKUP_DIR + backups.pop() result = self.__recover_checkpoint(cp_dir) @@ -1266,11 +1264,11 @@ LogLevel warn \n\ def __recover_checkpoint(self, cp_dir): """ Recover a specific checkpoint provided by cp_dir - Note: this function does not reload augeas. + Note: this function does not reload augeas. returns: 0 success, 1 Unable to revert, -1 Unable to delete """ - + if os.path.isfile(cp_dir + "/FILEPATHS"): try: with open(cp_dir + "/FILEPATHS") as f: @@ -1328,13 +1326,13 @@ LogLevel warn \n\ print time.ctime(float(bu)) with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f: print f.read() - + print "Affected files:" with open(BACKUP_DIR + bu + "/FILEPATHS") as f: filepaths = f.read().splitlines() for fp in filepaths: print " %s" % fp - + try: with open(BACKUP_DIR + bu + "/NEW_FILES") as f: print "New Configuration Files:" @@ -1356,7 +1354,7 @@ LogLevel warn \n\ cp_dir = TEMP_CHECKPOINT_DIR else: cp_dir = IN_PROGRESS_DIR - + le_util.make_or_verify_dir(cp_dir) try: with open(cp_dir + "NEW_FILES", 'a') as fd: @@ -1364,8 +1362,8 @@ LogLevel warn \n\ fd.write("%s\n" % f) except: logger.error("ERROR: Unable to register file creation") - - + + def main(): config = Configurator() @@ -1379,7 +1377,7 @@ def main(): print name """ print config.find_directive(config.case_i("NameVirtualHost"), config.case_i("holla:443")) - + """ for m in config.find_directive("Listen", "443"): print "Directive Path:", m, "Value:", config.aug.get(m) @@ -1417,7 +1415,7 @@ def main(): if vh.addrs[0] == "23.20.47.131:80": print "Here we go" ssl_vh = config.make_vhost_ssl(vh) - + config.redirect_all_ssl(ssl_vh) """ """ @@ -1425,6 +1423,6 @@ def main(): if len(vh.names) > 0: config.deploy_cert(vh, "/home/james/Documents/apache_choc/req.pem", "/home/james/Documents/apache_choc/key.pem", "/home/james/Downloads/sub.class1.server.ca.pem") """ - + if __name__ == "__main__": main() diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 886fbb353..6924fbe0c 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -8,7 +8,6 @@ from Crypto.Hash import SHA256 from M2Crypto import EVP, X509, ASN1 -from letsencrypt.client import logger from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE @@ -59,7 +58,7 @@ def make_key(bits=RSA_KEY_SIZE): #rsa = M2Crypto.RSA.gen_key(bits, 65537) #key_pem = rsa.as_pem(cipher=None) #rsa = None # should not be freed here - + return key.exportKey(format='PEM') @@ -147,7 +146,7 @@ def get_cert_info(filename): d["san"] = x.get_ext("subjectAltName").get_value() except: d["san"] = "" - + d["serial"] = x.get_serial_number() d["pub_key"] = "RSA " + str(x.get_pubkey().size() * 8) return d diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index f29aa7153..6c137e667 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -25,7 +25,7 @@ class Display(SingletonD): raise Exception("Error no display defined") def success_installation(self, domains): raise Exception("Error no display defined") - + def gen_https_names(self, domains): """ Returns a string of the domains formatted nicely with https:// prepended @@ -94,12 +94,12 @@ class NcursesDisplay(Display): def generic_input(self, message): return self.d.inputbox(message) - + def generic_yesno(self, message, yes = "Yes", no = "No"): a = self.d.yesno(message, HEIGHT, WIDTH) return a == self.d.DIALOG_OK - + def filter_names(self, names): choices = [(n, "", 0) for n in names] c, s = self.d.checklist("Which names would you like to activate \ @@ -115,12 +115,12 @@ class NcursesDisplay(Display): def display_certs(self, certs): list_choices = [ (str(i+1), - "%s | %s | %s" % - (str(c["cn"].ljust(WIDTH - 39)), - c["not_before"].strftime("%m-%d-%y"), - "Installed" if c["installed"] else "")) + "%s | %s | %s" % + (str(c["cn"].ljust(WIDTH - 39)), + c["not_before"].strftime("%m-%d-%y"), + "Installed" if c["installed"] else "")) for i, c in enumerate(certs)] - + code, s = self.d.menu("Which certificates would you like to revoke?", choices = list_choices, help_button=True, help_label="More Info", ok_label="Revoke", @@ -204,7 +204,7 @@ class FileDisplay(Display): menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] + " - " + str(c["not_before"])[:-6]) for i, c in enumerate(certs)] - + self.outfile.write("Which certificate would you like to revoke?\n") for c in menu_choices: wm = textwrap.fill("%s: %s - %s Signed (UTC): %s\n" % @@ -243,9 +243,9 @@ class FileDisplay(Display): s_f = '*' * (79) wm = textwrap.fill(("Congratulations! You have successfully " + "enabled %s!") % self.gen_https_names(domains)) - msg = "%s\n%s\n%s\n" + msg = "%s\n%s\n%s\n" self.outfile.write(msg % (s_f, wm, s_f)) - + def confirm_revocation(self, cert): self.outfile.write("Are you sure you would like to revoke \ the following certificate:\n") @@ -300,10 +300,10 @@ def redirect_by_default(): choices = [ ("Easy", "Allow both HTTP and HTTPS access to these sites"), ("Secure", "Make all requests redirect to secure HTTPS access")] - + result = display.generic_menu("Please choose whether HTTPS access " + "is required or optional.", - choices, + choices, "Please enter the appropriate number", width = WIDTH) diff --git a/letsencrypt/client/interactive_challenge.py b/letsencrypt/client/interactive_challenge.py index 2543f7635..52aa331f1 100644 --- a/letsencrypt/client/interactive_challenge.py +++ b/letsencrypt/client/interactive_challenge.py @@ -1,13 +1,12 @@ from letsencrypt.client.challenge import Challenge -from letsencrypt.client import logger import textwrap ############################################################ # Possible addition to challenge structure: priority parameter # If only DVSNI and Payment are required, the user might want # to be validated before submitting payment, allowing the user -# to gain confidence in the system. If things do go poorly the -# user has less invested in that particular session/transaction. +# to gain confidence in the system. If things do go poorly the +# user has less invested in that particular session/transaction. ############################################################# ########################################################### @@ -22,7 +21,7 @@ class Interactive_Challenge(Challenge): def __init__(self, string): self.string = string - + def perform(self, quiet=True): if quiet: dialog.Dialog().msgbox(get_display_string(), width=BOX_SIZE) @@ -31,7 +30,7 @@ class Interactive_Challenge(Challenge): raw_input('') return True - + def get_display_string(self): return textwrap.fill(self.string, width=BOX_SIZE) + "\n\nPlease Press Enter to Continue" diff --git a/letsencrypt/client/logger.py b/letsencrypt/client/logger.py index 5d42be284..b285c2359 100644 --- a/letsencrypt/client/logger.py +++ b/letsencrypt/client/logger.py @@ -1,4 +1,3 @@ -import sys import time from letsencrypt.client import display @@ -55,20 +54,20 @@ class FileLogger(Logger): import dialog class NcursesLogger(Logger): - def __init__(self, - firstmessage="", - height = display.HEIGHT, + def __init__(self, + firstmessage="", + height = display.HEIGHT, width = display.WIDTH - 4): self.lines = [] self.all_content = "" self.d = dialog.Dialog() self.height = height self.width = width - self.add(firstmessage) + self.add(firstmessage) ''' Only show the last (self.height) lines; - note that lines can wrap at self.width, so + note that lines can wrap at self.width, so a single line could actually be multiple lines ''' def add(self, s): @@ -93,7 +92,7 @@ class NcursesLogger(Logger): if cur_out != '': self.lines.append(cur_out) - + # show last 16 lines self.content = '\n'.join(self.lines[-self.height:]) self.show() @@ -110,7 +109,7 @@ log_instance = None def setLogger(log_inst): global log_instance log_instance = log_inst - + def setLogLevel(log_level): global log_instance log_instance.level = log_level @@ -140,7 +139,7 @@ def fatal(data): def none(data): # Uh...what? - pass + pass if __name__ == "__main__": # Unit test/example usage: @@ -165,7 +164,7 @@ if __name__ == "__main__": time.sleep(0.3) - # Alternatively, use + # Alternatively, use logger.error("errrrr") logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows')) diff --git a/letsencrypt/client/payment_challenge.py b/letsencrypt/client/payment_challenge.py index d00aa44e2..773975d68 100644 --- a/letsencrypt/client/payment_challenge.py +++ b/letsencrypt/client/payment_challenge.py @@ -1,13 +1,12 @@ from letsencrypt.client.challenge import Challenge -from letsencrypt.client import logger import dialog ############################################################ # Possible addition to challenge structure: priority parameter # If only DVSNI and Payment are required, the user might want # to be validated before submitting payment, allowing the user -# to gain confidence in the system. If things do go poorly the -# user has less invested in that particular session/transaction. +# to gain confidence in the system. If things do go poorly the +# user has less invested in that particular session/transaction. ############################################################# class Payment_Challenge(Challenge): @@ -21,7 +20,7 @@ class Payment_Challenge(Challenge): def cleanup(self): # Currently, payment challenges do not appear to require any cleanup. pass - + def perform(self, quiet=True): if quiet: dialog.Dialog().msgbox(self.get_display_string(), width=70) @@ -31,7 +30,7 @@ class Payment_Challenge(Challenge): self.times_performed += 1 return True - + def get_display_string(self): if self.times_performed == 0: @@ -40,7 +39,7 @@ class Payment_Challenge(Challenge): # The user has tried at least once... display a different message else: return "The CA did not record your payment, please visit " + self.url + " for more information or to finish processing your transaction.\nPress Enter to continue" - + def formatted_reasons(self): return "\n\t* %s\n" % self.reason diff --git a/letsencrypt/client/recovery_contact_challenge.py b/letsencrypt/client/recovery_contact_challenge.py index d3160fd2e..b84bc3412 100644 --- a/letsencrypt/client/recovery_contact_challenge.py +++ b/letsencrypt/client/recovery_contact_challenge.py @@ -6,7 +6,7 @@ from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT import dialog class RecoveryContact(Challenge): - + def __init__(self, activationURL = "", successURL = "", contact = "", poll_delay = 3): self.token = "" self.activationURL = activationURL @@ -24,14 +24,14 @@ class RecoveryContact(Challenge): exit, self.token = d.inputbox(self.get_display_string())) if exit != d.OK: return False - + else: print self.get_display_string() if successURL: return self.poll(10, quiet) else: self.token = raw_input("Enter the recovery token:") - + return True def cleanup(self): @@ -46,7 +46,7 @@ class RecoveryContact(Challenge): string += " or respond to the recovery email sent to " + self.contact elif self.contact: string += "Recovery email sent to" + self.contact - + def poll(self, rounds = 10, quiet = True): for i in range(rounds): if requests.get(self.successURL).status_code != 200: diff --git a/letsencrypt/client/recovery_token_challenge.py b/letsencrypt/client/recovery_token_challenge.py index abd233c3f..1e81bd775 100644 --- a/letsencrypt/client/recovery_token_challenge.py +++ b/letsencrypt/client/recovery_token_challenge.py @@ -1,18 +1,16 @@ from letsencrypt.client.challenge import Challenge -from letsencrypt.client import logger -from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT class RecoveryToken(Challenge): - + def __init__(self): self.token = "" def perform(self, quiet = True): - + cancel, self.token = dialog.generic_input("Please Input Recovery Token: ") if cancel == 1: return False - + return True def cleanup(self): diff --git a/letsencrypt/client/sni_challenge.py b/letsencrypt/client/sni_challenge.py index 7da001aae..6947cef75 100755 --- a/letsencrypt/client/sni_challenge.py +++ b/letsencrypt/client/sni_challenge.py @@ -1,22 +1,18 @@ #!/usr/bin/env python -import subprocess import M2Crypto from Crypto import Random -import hmac import hashlib -from shutil import move -from os import remove, close, path +from os import path import sys import binascii -import augeas import jose from letsencrypt.client import configurator from letsencrypt.client.CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF, INVALID_EXT -from letsencrypt.client.CONFIG import S_SIZE, NONCE_SIZE +from letsencrypt.client.CONFIG import S_SIZE from letsencrypt.client import logger, crypto_util from letsencrypt.client.challenge import Challenge @@ -41,7 +37,7 @@ class SNI_Challenge(Challenge): self.key = key_filepath self.configurator = config self.s = None - + def getDvsniCertFile(self, nonce): """ @@ -117,7 +113,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ def checkForApacheConfInclude(self, mainConfig): """ - Adds DVSNI challenge include file if it does not already exist + Adds DVSNI challenge include file if it does not already exist within mainConfig mainConfig: string - file path to main user apache config file @@ -147,7 +143,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ #print ["openssl", "x509", "-req", "-days", "21", "-extfile", CHOC_CERT_CONF, "-extensions", "v3_ca", "-signkey", key, "-out", self.getDvsniCertFile(nonce), "-in", csr] - + #subprocess.call(["openssl", "x509", "-req", "-days", "21", "-extfile", CHOC_CERT_CONF, "-extensions", "v3_ca", "-signkey", key, "-out", self.getDvsniCertFile(nonce), "-in", csr], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) @@ -179,7 +175,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ h = hashlib.new('sha256') h.update(r) h.update(s) - + return h.hexdigest() + INVALID_EXT def byteToHex(self, byteStr): @@ -205,7 +201,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ """ self.configurator.revert_challenge_config() self.configurator.restart(True) - + def generate_response(self): """ Generates a response for a completed challenge @@ -275,14 +271,14 @@ def main(): logger.setLogLevel(logger.INFO) testkey = M2Crypto.RSA.load_key(key) - + #r = Random.get_random_bytes(S_SIZE) r = "testValueForR" #nonce = Random.get_random_bytes(NONCE_SIZE) nonce = "nonce" r2 = "testValueForR2" nonce2 = "nonce2" - + r = jose.b64encode_url(r) r2 = jose.b64encode_url(r2) @@ -296,7 +292,7 @@ def main(): nonce = binascii.hexlify(nonce) nonce2 = binascii.hexlify(nonce2) - + config = configurator.Configurator() challenges = [("client.theobroma.info", r, nonce), ("foo.theobroma.info",r2, nonce2)] From 6011453a14d3035a72d8fb7afa055a6b5ec2f24c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 18 Nov 2014 17:01:31 -0800 Subject: [PATCH 6/6] Replaced tabs with spaces and removed usage of several deprecated features: * dict.has_key -> `in` operator * backticks -> repr Also removed trailing newlines from a few files. flake8 --select='W' no longer reports any issues --- letsencrypt/client/CONFIG.py | 4 ++-- letsencrypt/client/acme.py | 4 ++-- letsencrypt/client/challenge.py | 1 - letsencrypt/client/client.py | 2 +- letsencrypt/client/configurator.py | 4 ++-- letsencrypt/client/logger.py | 5 ----- letsencrypt/client/payment_challenge.py | 1 - 7 files changed, 7 insertions(+), 14 deletions(-) diff --git a/letsencrypt/client/CONFIG.py b/letsencrypt/client/CONFIG.py index 832b5b4ce..8c7c4b6d4 100644 --- a/letsencrypt/client/CONFIG.py +++ b/letsencrypt/client/CONFIG.py @@ -40,13 +40,13 @@ NONCE_SIZE = 16 RSA_KEY_SIZE = 2048 # bits of hashcash to generate -difficulty = 23 +difficulty = 23 # Let's Encrypt cert and chain files CERT_PATH = CERT_DIR + "cert-letsencrypt.pem" CHAIN_PATH = CERT_DIR + "chain-letsencrypt.pem" -#Invalid Extension +#Invalid Extension INVALID_EXT = ".acme.invalid" # Challenge Preferences Dict for currently supported challenges diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 306ee9171..70a5982ad 100755 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -19,9 +19,9 @@ def acme_object_validate(j): j = json.loads(j) if not isinstance(j, dict): raise jsonschema.ValidationError("this is not a dictionary object") - if not j.has_key("type"): + if "type" not in j: raise jsonschema.ValidationError("missing type field") - if not schemata.has_key(j["type"]): + if j["type"] not in schemata: raise jsonschema.ValidationError("unknown type %s" % j["type"]) jsonschema.validate(j, schemata[j["type"]]) diff --git a/letsencrypt/client/challenge.py b/letsencrypt/client/challenge.py index a25e60be0..2ccf9406f 100644 --- a/letsencrypt/client/challenge.py +++ b/letsencrypt/client/challenge.py @@ -10,4 +10,3 @@ class Challenge(object): logger.error("Error - base class challenge.generate_response()") def clean(self): logger.error("Error - base class challenge.clean()") - diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 0f625804d..198c4139c 100755 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -688,7 +688,7 @@ class Client(object): def sanity_check_names(self, names): for name in names: if not self.is_hostname_sane(name): - logger.fatal(`name` + " is an impossible hostname") + logger.fatal(repr(name) + " is an impossible hostname") sys.exit(81) def is_hostname_sane(self, hostname): diff --git a/letsencrypt/client/configurator.py b/letsencrypt/client/configurator.py index 6f5aabf58..c5b0e1f92 100644 --- a/letsencrypt/client/configurator.py +++ b/letsencrypt/client/configurator.py @@ -907,12 +907,12 @@ LogLevel warn \n\ Enables mod_ssl """ try: - # Use check_output so the command will finish before reloading + # Use check_output so the command will finish before reloading subprocess.check_call(["sudo", "a2enmod", mod_name], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) # Hopefully this waits for output subprocess.check_call(["sudo", "/etc/init.d/apache2", "restart"], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) except: - logger.error("Error enabling mod_" + mod_name) + logger.error("Error enabling mod_" + mod_name) sys.exit(1) def fnmatch_to_re(self, cleanFNmatch): diff --git a/letsencrypt/client/logger.py b/letsencrypt/client/logger.py index b285c2359..86a8da846 100644 --- a/letsencrypt/client/logger.py +++ b/letsencrypt/client/logger.py @@ -168,8 +168,3 @@ if __name__ == "__main__": logger.error("errrrr") logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows')) - - - - - diff --git a/letsencrypt/client/payment_challenge.py b/letsencrypt/client/payment_challenge.py index 773975d68..e3c4494d5 100644 --- a/letsencrypt/client/payment_challenge.py +++ b/letsencrypt/client/payment_challenge.py @@ -43,4 +43,3 @@ class Payment_Challenge(Challenge): def formatted_reasons(self): return "\n\t* %s\n" % self.reason -