diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 633b8c2b6..e8ffeeb74 100644 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python - -# acme.py -# validate JSON objects as ACME protocol messages - -import json, jsonschema +"""Validate JSON objects as ACME protocol messages.""" +import json import pkg_resources +import jsonschema + + schemata = {schema: json.load(open(pkg_resources.resource_filename( __name__, "schemata/%s.json" % schema))) for schema in [ "authorization", "authorizationRequest", "certificate", "certificateRequest", diff --git a/letsencrypt/client/apache_configurator.py b/letsencrypt/client/apache_configurator.py index 068c6c099..1a3cd73b7 100644 --- a/letsencrypt/client/apache_configurator.py +++ b/letsencrypt/client/apache_configurator.py @@ -1,30 +1,23 @@ -import augeas -import subprocess -import re +import hashlib import os -import sys -import socket -import time +import pkg_resources +import re import shutil -from pkg_resources import Requirement, resource_filename +import socket +import subprocess +import sys +import time -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, augeas_configurator -from letsencrypt.client import crypto_util - -# Challenge specific imports -import binascii, hashlib from Crypto import Random -from letsencrypt.client.CONFIG import S_SIZE, APACHE_CHALLENGE_CONF, INVALID_EXT -options_ssl_conf = resource_filename(__name__, os.path.basename(OPTIONS_SSL_CONF)) +from letsencrypt.client import augeas_configurator +from letsencrypt.client import CONFIG +from letsencrypt.client import crypto_util +from letsencrypt.client import le_util +from letsencrypt.client import logger -#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, LE_VHOST_EXT -#import logger, le_util + +options_ssl_conf = pkg_resources.resource_filename(__name__, os.path.basename(CONFIG.OPTIONS_SSL_CONF)) # Configurator should be turned into a Singleton @@ -92,7 +85,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): so that other Configurators (like Nginx) can be developed and interoperate with the client. """ - def __init__(self, server_root=SERVER_ROOT): + def __init__(self, server_root=CONFIG.SERVER_ROOT): super(ApacheConfigurator, self).__init__() self.server_root = server_root @@ -566,14 +559,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def make_vhost_ssl(self, nonssl_vhost): """ Duplicates vhost and adds default ssl options - New vhost will reside as (nonssl_vhost.path) + LE_VHOST_EXT + New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT """ avail_fp = nonssl_vhost.file # Copy file if avail_fp.endswith(".conf"): - ssl_fp = avail_fp[:-(len(".conf"))] + LE_VHOST_EXT + ssl_fp = avail_fp[:-(len(".conf"))] + CONFIG.LE_VHOST_EXT else: - ssl_fp = avail_fp + LE_VHOST_EXT + ssl_fp = avail_fp + CONFIG.LE_VHOST_EXT # First register the creation so that it is properly removed if # configuration is rolled back @@ -678,7 +671,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): return False, general_v #Add directives to server self.add_dir(general_v.path, "RewriteEngine", "On") - self.add_dir(general_v.path, "RewriteRule", REWRITE_HTTPS_ARGS) + self.add_dir(general_v.path, "RewriteRule", CONFIG.REWRITE_HTTPS_ARGS) self.save_notes += 'Redirecting host in %s to ssl vhost in %s\n' % (general_v.file, ssl_vhost.file) self.save() return True, general_v @@ -704,9 +697,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if not rewrite_path: # "No existing redirection for virtualhost" return False, -1 - if len(rewrite_path) == len(REWRITE_HTTPS_ARGS): + if len(rewrite_path) == len(CONFIG.REWRITE_HTTPS_ARGS): for idx, m in enumerate(rewrite_path): - if self.aug.get(m) != REWRITE_HTTPS_ARGS[idx]: + if self.aug.get(m) != CONFIG.REWRITE_HTTPS_ARGS[idx]: # Not a letsencrypt https rewrite return True, 2 # Existing letsencrypt https rewrite rule is in place @@ -979,7 +972,7 @@ LogLevel warn \n\ 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())) + shutil.copytree(self.server_root, CONFIG.BACKUP_DIR + "apache2-" + str(time.time())) def verify_setup(self): @@ -988,9 +981,9 @@ LogLevel warn \n\ Aim for defensive coding... make sure all input files have permissions of root ''' - le_util.make_or_verify_dir(CONFIG_DIR, 0755) - le_util.make_or_verify_dir(WORK_DIR, 0755) - le_util.make_or_verify_dir(BACKUP_DIR, 0755) + le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755) + le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755) + le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755) def standardize_excl(self): """ @@ -1112,7 +1105,7 @@ LogLevel warn \n\ addresses.append(vhost.addrs) # Generate S - s = Random.get_random_bytes(S_SIZE) + s = Random.get_random_bytes(CONFIG.S_SIZE) # Create all of the challenge certs for t in chall_dict["listSNITuple"]: # Need to decode from base64 @@ -1140,7 +1133,7 @@ LogLevel warn \n\ nonce: string - hex result: returns certificate file name """ - return WORK_DIR + nonce + ".crt" + return CONFIG.WORK_DIR + nonce + ".crt" def __getConfigText(self, nonce, ip_addrs, key): """ @@ -1153,7 +1146,7 @@ LogLevel warn \n\ result: returns virtual host configuration text """ configText = " \n \ -ServerName " + nonce + INVALID_EXT + " \n \ +ServerName " + nonce + CONFIG.INVALID_EXT + " \n \ UseCanonicalName on \n \ SSLStrictSNIVHostCheck on \n \ \n \ @@ -1163,7 +1156,7 @@ Include " + options_ssl_conf + " \n \ SSLCertificateFile " + self.dvsni_get_cert_file(nonce) + " \n \ SSLCertificateKeyFile " + key + " \n \ \n \ -DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ +DocumentRoot " + CONFIG.CONFIG_DIR + "challenge_page/ \n \ \n\n " return configText @@ -1187,8 +1180,8 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ configText += " \n" self.dvsni_conf_include_check(mainConfig) - self.register_file_creation(True, APACHE_CHALLENGE_CONF) - newConf = open(APACHE_CHALLENGE_CONF, 'w') + self.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF) + newConf = open(CONFIG.APACHE_CHALLENGE_CONF, 'w') newConf.write(configText) newConf.close() @@ -1203,9 +1196,9 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ result: User Apache configuration includes chocolate sni challenge file """ - if len(self.find_directive(self.case_i("Include"), APACHE_CHALLENGE_CONF)) == 0: + if len(self.find_directive(self.case_i("Include"), CONFIG.APACHE_CHALLENGE_CONF)) == 0: #print "Including challenge virtual host(s)" - self.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF) + self.add_dir("/files" + mainConfig, "Include", CONFIG.APACHE_CHALLENGE_CONF) def dvsni_create_chall_cert(self, name, ext, nonce, key): """ @@ -1219,7 +1212,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ """ self.register_file_creation(True, self.dvsni_get_cert_file(nonce)) - cert_pem = crypto_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext]) + cert_pem = crypto_util.make_ss_cert(key, [nonce + CONFIG.INVALID_EXT, name, ext]) with open(self.dvsni_get_cert_file(nonce), 'w') as f: f.write(cert_pem) @@ -1230,16 +1223,16 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ r: byte array s: byte array - result: returns z + INVALID_EXT + result: returns z + CONFIG.INVALID_EXT """ h = hashlib.new('sha256') h.update(r) h.update(s) - return h.hexdigest() + INVALID_EXT + return h.hexdigest() + CONFIG.INVALID_EXT def main(): - config = Configurator() + config = ApacheConfigurator() logger.setLogger(logger.FileLogger(sys.stdout)) logger.setLogLevel(logger.DEBUG) """ diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index 6e4d54fb1..d2f6c6b44 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -1,12 +1,17 @@ -import os, sys, shutil, time -from letsencrypt.client.configurator import Configurator +import os +import sys +import shutil +import time import augeas -from letsencrypt.client import le_util, logger -from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR -from letsencrypt.client.CONFIG import BACKUP_DIR -class AugeasConfigurator(Configurator): +from letsencrypt.client import CONFIG +from letsencrypt.client import configurator +from letsencrypt.client import le_util +from letsencrypt.client import logger + + +class AugeasConfigurator(configurator.Configurator): def __init__(self): super(AugeasConfigurator, self).__init__() @@ -90,13 +95,13 @@ class AugeasConfigurator(Configurator): # Create Checkpoint if temporary: - self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files) + self.add_to_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR, save_files) else: - self.add_to_checkpoint(IN_PROGRESS_DIR, save_files) + self.add_to_checkpoint(CONFIG.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) + if title and not temporary and os.path.isdir(CONFIG.IN_PROGRESS_DIR): + success = self.__finalize_checkpoint(CONFIG.IN_PROGRESS_DIR, title) if not success: # This should never happen # This will be hopefully be cleaned up on the recovery @@ -115,12 +120,12 @@ class AugeasConfigurator(Configurator): This function should reload the users original configuration files for all saves with reversible=True """ - if os.path.isdir(TEMP_CHECKPOINT_DIR): - result = self.__recover_checkpoint(TEMP_CHECKPOINT_DIR) + if os.path.isdir(CONFIG.TEMP_CHECKPOINT_DIR): + result = self.__recover_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR) changes = True if result != 0: # We have a partial or incomplete recovery - logger.fatal("Incomplete or failed recovery for %s" % TEMP_CHECKPOINT_DIR) + logger.fatal("Incomplete or failed recovery for %s" % CONFIG.TEMP_CHECKPOINT_DIR) sys.exit(67) # Remember to reload Augeas self.aug.load() @@ -137,14 +142,14 @@ class AugeasConfigurator(Configurator): logger.error("Rollback argument must be a positive integer") return - backups = os.listdir(BACKUP_DIR) + backups = os.listdir(CONFIG.BACKUP_DIR) backups.sort() 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() + cp_dir = CONFIG.BACKUP_DIR + backups.pop() result = self.__recover_checkpoint(cp_dir) if result != 0: logger.fatal("Failed to load checkpoint during rollback") @@ -160,7 +165,7 @@ class AugeasConfigurator(Configurator): script found in the constructor, before this function would ever be called """ - backups = os.listdir(BACKUP_DIR) + backups = os.listdir(CONFIG.BACKUP_DIR) backups.sort(reverse=True) if not backups: @@ -171,21 +176,21 @@ class AugeasConfigurator(Configurator): for bu in backups: float(bu) except: - assert False, "Invalid files in %s" % BACKUP_DIR + assert False, "Invalid files in %s" % CONFIG.BACKUP_DIR for bu in backups: print time.ctime(float(bu)) - with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f: + with open(CONFIG.BACKUP_DIR + bu + "/CHANGES_SINCE") as f: print f.read() print "Affected files:" - with open(BACKUP_DIR + bu + "/FILEPATHS") as f: + with open(CONFIG.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: + with open(CONFIG.BACKUP_DIR + bu + "/NEW_FILES") as f: print "New Configuration Files:" filepaths = f.read().splitlines() for fp in filepaths: @@ -199,7 +204,7 @@ class AugeasConfigurator(Configurator): Add title to cp_dir CHANGES_SINCE Move cp_dir to Backups directory and rename with timestamp """ - final_dir = BACKUP_DIR + str(time.time()) + final_dir = CONFIG.BACKUP_DIR + str(time.time()) try: with open(cp_dir + "CHANGES_SINCE.tmp", 'w') as ft: ft.write("-- %s --\n" % title) @@ -275,7 +280,7 @@ class AugeasConfigurator(Configurator): return 0 def check_tempfile_saves(self, save_files, temporary): - temp_path = "%sFILEPATHS" % TEMP_CHECKPOINT_DIR + temp_path = "%sFILEPATHS" % CONFIG.TEMP_CHECKPOINT_DIR if os.path.isfile(temp_path): with open(temp_path, 'r') as protected_fd: protected_files = protected_fd.read().splitlines() @@ -294,9 +299,9 @@ class AugeasConfigurator(Configurator): (Before a save occurs) """ if temporary: - cp_dir = TEMP_CHECKPOINT_DIR + cp_dir = CONFIG.TEMP_CHECKPOINT_DIR else: - cp_dir = IN_PROGRESS_DIR + cp_dir = CONFIG.IN_PROGRESS_DIR le_util.make_or_verify_dir(cp_dir) try: @@ -310,19 +315,19 @@ class AugeasConfigurator(Configurator): def recovery_routine(self): """ Revert all previously modified files. First, any changes found in - TEMP_CHECKPOINT_DIR are removed, then IN_PROGRESS changes are removed + CONFIG.TEMP_CHECKPOINT_DIR are removed, then IN_PROGRESS changes are removed The order is important. IN_PROGRESS is unable to add files that are already added by a TEMP change. Thus TEMP must be rolled back first because that will be the 'latest' occurrence of the file. """ self.revert_challenge_config() - if os.path.isdir(IN_PROGRESS_DIR): - result = self.__recover_checkpoint(IN_PROGRESS_DIR) + if os.path.isdir(CONFIG.IN_PROGRESS_DIR): + result = self.__recover_checkpoint(CONFIG.IN_PROGRESS_DIR) if result != 0: # We have a partial or incomplete recovery # Not as egregious # TODO: Additional tests? recovery - logger.fatal("Incomplete or failed recovery for %s" % IN_PROGRESS_DIR) + logger.fatal("Incomplete or failed recovery for %s" % CONFIG.IN_PROGRESS_DIR) sys.exit(68) # Need to reload configuration after these changes take effect diff --git a/letsencrypt/client/challenge.py b/letsencrypt/client/challenge.py index 2ccf9406f..0ddd6ab51 100644 --- a/letsencrypt/client/challenge.py +++ b/letsencrypt/client/challenge.py @@ -1,5 +1,5 @@ from letsencrypt.client import logger -#import logger + class Challenge(object): def __init__(self, configurator): diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 0788800b2..41c966100 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -1,21 +1,25 @@ -#!/usr/bin/env python +import csv +import datetime +import json +import os +import shutil +import socket +import string +import sys +import time import M2Crypto -import json -import os, time, sys, shutil - -import csv - import requests -from letsencrypt.client.acme import acme_object_validate -from letsencrypt.client import configurator, apache_configurator -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, EXCLUSIVE_CHALLENGES -from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, CONFIG_CHALLENGES +from letsencrypt.client import acme +from letsencrypt.client import apache_configurator +from letsencrypt.client import CONFIG +from letsencrypt.client import crypto_util +from letsencrypt.client import display +from letsencrypt.client import le_util +from letsencrypt.client import logger + + # 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 @@ -35,7 +39,7 @@ class Client(object): # TODO: Can probably figure out which configurator to use without # special packaging based on system info # Command line arg or client function to discover - self.config = apache_configurator.ApacheConfigurator(SERVER_ROOT) + self.config = apache_configurator.ApacheConfigurator(CONFIG.SERVER_ROOT) self.server = ca_server @@ -168,8 +172,8 @@ class Client(object): self.list_certs_keys() def remove_cert_key(self, c): - list_file = CERT_KEY_BACKUP + "LIST" - list_file2 = CERT_KEY_BACKUP + "LIST.tmp" + list_file = CONFIG.CERT_KEY_BACKUP + "LIST" + list_file2 = CONFIG.CERT_KEY_BACKUP + "LIST.tmp" with open(list_file, 'rb') as orgfile: csvreader = csv.reader(orgfile) with open(list_file2, 'wb') as newfile: @@ -187,10 +191,10 @@ class Client(object): def list_certs_keys(self): - list_file = CERT_KEY_BACKUP + "LIST" + list_file = CONFIG.CERT_KEY_BACKUP + "LIST" certs = [] - if not os.path.isfile(CERT_KEY_BACKUP + "LIST"): + if not os.path.isfile(CONFIG.CERT_KEY_BACKUP + "LIST"): logger.info("You don't have any certificates saved from letsencrypt") return @@ -206,8 +210,8 @@ class Client(object): for row in csvreader: c = crypto_util.get_cert_info(row[1]) - b_k = CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0] - b_c = CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0] + b_k = CONFIG.CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0] + b_c = CONFIG.CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0] c["orig_key_file"] = row[2] c["orig_cert_file"] = row[1] @@ -246,7 +250,7 @@ class Client(object): def install_certificate(self, certificate_dict, vhost): cert_chain_abspath = None - cert_fd, self.cert_file = le_util.unique_file(CERT_PATH, 644) + cert_fd, self.cert_file = le_util.unique_file(CONFIG.CERT_PATH, 644) cert_fd.write( crypto_util.b64_cert_to_pem(certificate_dict["certificate"])) cert_fd.close() @@ -254,7 +258,7 @@ class Client(object): self.cert_file) if certificate_dict.get("chain", None): - chain_fd, chain_fn = le_util.unique_file(CHAIN_PATH, 644) + chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 644) for c in certificate_dict.get("chain", []): chain_fd.write(crypto_util.b64_cert_to_pem(c)) chain_fd.close() @@ -308,7 +312,7 @@ class Client(object): def cleanup_challenges(self, challenge_objs): logger.info("Cleaning up challenges...") for c in challenge_objs: - if c["type"] in CONFIG_CHALLENGES: + if c["type"] in CONFIG.CONFIG_CHALLENGES: self.config.cleanup() else: #Handle other cleanup if needed @@ -377,7 +381,7 @@ class Client(object): # Perform challenges for i, c_obj in enumerate(challenge_objs): response = "null" - if c_obj["type"] in CONFIG_CHALLENGES: + if c_obj["type"] in CONFIG.CONFIG_CHALLENGES: response = self.config.perform(c_obj) else: # Handle RecoveryToken type challenges @@ -411,7 +415,7 @@ class Client(object): """ chall_cost = {} max_cost = 0 - for i, chall in enumerate(CHALLENGE_PREFERENCES): + for i, chall in enumerate(CONFIG.CHALLENGE_PREFERENCES): chall_cost[chall] = i max_cost += i @@ -443,7 +447,7 @@ class Client(object): # Add logic for a crappy server # Choose a DV path = [] - for pref_c in CHALLENGE_PREFERENCES: + for pref_c in CONFIG.CHALLENGE_PREFERENCES: for i, offered_c in enumerate(challenges): if (pref_c == offered_c["type"] and self.is_preferred(offered_c["type"], path)): @@ -454,7 +458,7 @@ class Client(object): def is_preferred(self, offered_c_type, path): for tup in path: - for s in EXCLUSIVE_CHALLENGES: + for s in CONFIG.EXCLUSIVE_CHALLENGES: # Second part is in case we eventually allow multiple names # to be challenges at the same time if (tup[1] in s and offered_c_type in s and @@ -466,14 +470,14 @@ class Client(object): def send(self, json_obj): try: json_encoded = json.dumps(json_obj) - acme_object_validate(json_encoded) + acme.acme_object_validate(json_encoded) response = requests.post( self.server_url, data=json_encoded, headers={"Content-Type": "application/json"}, ) body = response.content - acme_object_validate(body) + acme.acme_object_validate(body) return response.json() except Exception as e: logger.fatal("Send() failed... may have lost connection to server") @@ -488,8 +492,8 @@ class Client(object): def store_cert_key(self, encrypt = False): - list_file = CERT_KEY_BACKUP + "LIST" - le_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700) + list_file = CONFIG.CERT_KEY_BACKUP + "LIST" + le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0700) idx = 0 if encrypt: @@ -511,10 +515,10 @@ class Client(object): csvwriter.writerow(["0", self.cert_file, self.key_file]) shutil.copy2(self.key_file, - CERT_KEY_BACKUP + os.path.basename(self.key_file) + + CONFIG.CERT_KEY_BACKUP + os.path.basename(self.key_file) + "_" + str(idx)) shutil.copy2(self.cert_file, - CERT_KEY_BACKUP + os.path.basename(self.cert_file) + + CONFIG.CERT_KEY_BACKUP + os.path.basename(self.cert_file) + "_" + str(idx)) @@ -581,11 +585,11 @@ class Client(object): key_pem = None csr_pem = None if not self.key_file: - key_pem = crypto_util.make_key(RSA_KEY_SIZE) + key_pem = crypto_util.make_key(CONFIG.RSA_KEY_SIZE) # Save file - le_util.make_or_verify_dir(KEY_DIR, 0700) + le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0700) key_f, self.key_file = le_util.unique_file( - KEY_DIR + "key-letsencrypt.pem", 0600) + CONFIG.KEY_DIR + "key-letsencrypt.pem", 0600) key_f.write(key_pem) key_f.close() logger.info("Generating key: %s" % self.key_file) @@ -599,9 +603,9 @@ class Client(object): if not self.csr_file: csr_pem, csr_der = crypto_util.make_csr(self.key_file, self.names) # Save CSR - le_util.make_or_verify_dir(CERT_DIR, 0755) + le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0755) csr_f, self.csr_file = le_util.unique_file( - CERT_DIR + "csr-letsencrypt.pem", 0644) + CONFIG.CERT_DIR + "csr-letsencrypt.pem", 0644) csr_f.write(csr_pem) csr_f.close() logger.info("Creating CSR: %s" % self.csr_file) @@ -695,15 +699,13 @@ class Client(object): Do enough to avoid shellcode from the environment. There's no need to do more. """ - import string as s - allowed = s.ascii_letters + s.digits + "-." # hostnames & IPv4 + allowed = string.ascii_letters + string.digits + "-." # hostnames & IPv4 if all([c in allowed for c in hostname]): return True if not allow_raw_ipv6_server: return False # ipv6 is messy and complicated, can contain %zoneindex etc. - import socket try: # is this a valid IPv6 address? socket.getaddrinfo(hostname,443,socket.AF_INET6) diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 3ad7ff6a3..875858351 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -1,29 +1,32 @@ -import M2Crypto -import time, binascii +import binascii import hashlib -from Crypto.Random import get_random_bytes -from Crypto.PublicKey import RSA -from Crypto.Signature import PKCS1_v1_5 -from Crypto.Hash import SHA256 -from M2Crypto import EVP, X509, ASN1 +import time -from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE +from Crypto import Random +import Crypto.Hash.SHA256 +import Crypto.PublicKey.RSA +import Crypto.Signature.PKCS1_v1_5 + +import M2Crypto + +from letsencrypt.client import CONFIG from letsencrypt.client import le_util + def b64_cert_to_pem(b64_der_cert): x = M2Crypto.X509.load_cert_der_string(le_util.b64_url_dec(b64_der_cert)) return x.as_pem() -def create_sig(msg, key_file, signer_nonce = None, signer_nonce_len = NONCE_SIZE): +def create_sig(msg, key_file, signer_nonce = None, signer_nonce_len = CONFIG.NONCE_SIZE): # DOES prepend signer_nonce to message # TODO: Change this over to M2Crypto... PKey # Protect against crypto unicode errors... is this sufficient? Do I need to escape? msg = str(msg) - key = RSA.importKey(open(key_file).read()) + key = Crypto.PublicKey.RSA.importKey(open(key_file).read()) if signer_nonce is None: - signer_nonce = get_random_bytes(signer_nonce_len) - h = SHA256.new(signer_nonce + msg) - signer = PKCS1_v1_5.new(key) + signer_nonce = Random.get_random_bytes(signer_nonce_len) + h = Crypto.Hash.SHA256.new(signer_nonce + msg) + signer = Crypto.Signature.PKCS1_v1_5.new(key) signature = signer.sign(h) #print "signing:", signer_nonce + msg #print "signature:", signature @@ -48,12 +51,12 @@ def sha256(m): return hashlib.sha256(m).hexdigest() # based on M2Crypto unit test written by Toby Allsopp -def make_key(bits=RSA_KEY_SIZE): +def make_key(bits=CONFIG.RSA_KEY_SIZE): """ Returns new RSA key in PEM form with specified bits """ #Python Crypto module doesn't produce any stdout - key = RSA.generate(bits) + key = Crypto.PublicKey.RSA.generate(bits) #rsa = M2Crypto.RSA.gen_key(bits, 65537) #key_pem = rsa.as_pem(cipher=None) #rsa = None # should not be freed here @@ -67,10 +70,10 @@ def make_csr(key_file, domains): """ assert domains, "Must provide one or more hostnames for the CSR." rsa_key = M2Crypto.RSA.load_key(key_file) - pk = EVP.PKey() + pk = M2Crypto.EVP.PKey() pk.assign_rsa(rsa_key) - x = X509.Request() + x = M2Crypto.X509.Request() x.set_pubkey(pk) name = x.get_subject() name.C = "US" @@ -80,8 +83,8 @@ def make_csr(key_file, domains): name.OU = "University of Michigan" name.CN = domains[0] - extstack = X509.X509_Extension_Stack() - ext = X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains])) + extstack = M2Crypto.X509.X509_Extension_Stack() + ext = M2Crypto.X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains])) extstack.push(ext) x.add_extensions(extstack) @@ -97,18 +100,18 @@ def make_ss_cert(key_file, domains): """ assert domains, "Must provide one or more hostnames for the CSR." rsa_key = M2Crypto.RSA.load_key(key_file) - pk = EVP.PKey() + pk = M2Crypto.EVP.PKey() pk.assign_rsa(rsa_key) - x = X509.X509() + x = M2Crypto.X509.X509() x.set_pubkey(pk) x.set_serial_number(1337) x.set_version(2) t = long(time.time()) - current = ASN1.ASN1_UTCTIME() + current = M2Crypto.ASN1.ASN1_UTCTIME() current.set_time(t) - expire = ASN1.ASN1_UTCTIME() + expire = M2Crypto.ASN1.ASN1_UTCTIME() expire.set_time((7 * 24 * 60 * 60) + t) x.set_not_before(current) x.set_not_after(expire) @@ -121,9 +124,9 @@ def make_ss_cert(key_file, domains): name.CN = domains[0] x.set_issuer(x.get_subject()) - x.add_ext(X509.new_extension('basicConstraints', 'CA:FALSE')) - #x.add_ext(X509.new_extension('extendedKeyUsage', 'TLS Web Server Authentication')) - x.add_ext(X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))) + x.add_ext(M2Crypto.X509.new_extension('basicConstraints', 'CA:FALSE')) + #x.add_ext(M2Crypto.X509.new_extension('extendedKeyUsage', 'TLS Web Server Authentication')) + x.add_ext(M2Crypto.X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))) x.sign(pk, 'sha256') assert x.verify(pk) diff --git a/letsencrypt/client/display.py b/letsencrypt/client/display.py index c0c430fab..4c1633776 100644 --- a/letsencrypt/client/display.py +++ b/letsencrypt/client/display.py @@ -1,3 +1,5 @@ +import textwrap + import dialog @@ -80,7 +82,6 @@ class Display(SingletonD): class NcursesDisplay(Display): - import dialog def __init__(self): self.d = dialog.Dialog() @@ -153,11 +154,8 @@ class NcursesDisplay(Display): print text self.d.msgbox(text, width=WIDTH, height=HEIGHT) -textwrap = None class FileDisplay(Display): - global textwrap - import textwrap def __init__(self, outfile): self.outfile = outfile diff --git a/letsencrypt/client/interactive_challenge.py b/letsencrypt/client/interactive_challenge.py index dfa960483..7d3c8d062 100644 --- a/letsencrypt/client/interactive_challenge.py +++ b/letsencrypt/client/interactive_challenge.py @@ -1,6 +1,10 @@ -from letsencrypt.client.challenge import Challenge import textwrap +import dialog + +from letsencrypt.client import challenge + + ########################################################### # Interactive challenge displays the string sent by the CA # formatted to fit on the screen of the client @@ -8,7 +12,7 @@ import textwrap # client should continue the letsencrypt process ########################################################### -class Interactive_Challenge(Challenge): +class Interactive_Challenge(challenge.Challenge): BOX_SIZE = 70 def __init__(self, string): diff --git a/letsencrypt/client/le_util.py b/letsencrypt/client/le_util.py index fbe7fb53e..f9afd8171 100644 --- a/letsencrypt/client/le_util.py +++ b/letsencrypt/client/le_util.py @@ -1,12 +1,13 @@ -# This file will contain functions useful for all Letsencrypt Classes -import errno -import stat -import os, pwd, grp -import time +"""Utilities for all Let's Encrypt.""" import base64 -from letsencrypt.client import logger -#import logger +import grp +import errno +import os +import pwd +import stat +import sys +from letsencrypt.client import logger def make_or_verify_dir(directory, permissions=0755, uid=0): diff --git a/letsencrypt/client/logger.py b/letsencrypt/client/logger.py index 86a8da846..ac1b85033 100644 --- a/letsencrypt/client/logger.py +++ b/letsencrypt/client/logger.py @@ -1,6 +1,12 @@ +import logger +import textwrap import time + +import dialog + from letsencrypt.client import display + class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): @@ -36,11 +42,8 @@ class Logger(Singleton): t = time.time() return time.strftime("%b %d %Y %H:%M:%S", time.localtime(t)) + ('%.03f' % (t - int(t)))[1:] -textwrap = None class FileLogger(Logger): - global textwrap - import textwrap def __init__(self, outfile): self.outfile = outfile @@ -51,7 +54,7 @@ class FileLogger(Logger): wm = textwrap.fill(msg, 80) self.outfile.write("%s\n" % wm) -import dialog + class NcursesLogger(Logger): def __init__(self, @@ -143,28 +146,27 @@ def none(data): if __name__ == "__main__": # Unit test/example usage: - import logger # Set the logging type you want to use (stdout logging): #logger.setLogger(FileLogger(sys.stdout)) - logger.setLogger(NcursesLogger()) + setLogger(NcursesLogger()) # Set the most verbose you want to log (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE) - logger.setLogLevel(logger.TRACE) + setLogLevel(logger.TRACE) # Log a message: #logger.log(logger.INFO, "logger!") time.sleep(0.01) - logger.info("This is a long line, it's pretty long, butitalso hasbig wordsthat areprobably hardtobreak oninan easywayforthe ncurseslib, sowhatdoes itdo then?") - logger.info("aa " + "a"*70 + "B") + info("This is a long line, it's pretty long, butitalso hasbig wordsthat areprobably hardtobreak oninan easywayforthe ncurseslib, sowhatdoes itdo then?") + info("aa " + "a"*70 + "B") for i in range(20): - logger.info("iteration #%d/20" % i) + info("iteration #%d/20" % i) time.sleep(0.3) # Alternatively, use - logger.error("errrrr") + error("errrrr") - logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows')) + trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows')) diff --git a/letsencrypt/client/nginx_configurator.py b/letsencrypt/client/nginx_configurator.py index d1b927e5b..887180aed 100644 --- a/letsencrypt/client/nginx_configurator.py +++ b/letsencrypt/client/nginx_configurator.py @@ -1,8 +1,5 @@ -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, configurator +from letsencrypt.client import CONFIG +from letsencrypt.client import augeas_configurator # This might be helpful... but feel free to use whatever you want @@ -21,9 +18,9 @@ from letsencrypt.client import logger, le_util, configurator # def add_name(self, name): # self.names.append(name) -class NginxConfigurator(AugeasConfigurator): +class NginxConfigurator(augeas_configurator.AugeasConfigurator): - def __init__(self, server_root=SERVER_ROOT): + def __init__(self, server_root=CONFIG.SERVER_ROOT): self.server_root = server_root # See if any temporary changes need to be recovered @@ -188,9 +185,9 @@ class NginxConfigurator(AugeasConfigurator): # Aim for defensive coding... make sure all input files # have permissions of root # ''' - # le_util.make_or_verify_dir(CONFIG_DIR, 0755) - # le_util.make_or_verify_dir(WORK_DIR, 0755) - # le_util.make_or_verify_dir(BACKUP_DIR, 0755) + # le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755) + # le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755) + # le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755) def restart(self, quiet=False): """ diff --git a/letsencrypt/client/recovery_contact_challenge.py b/letsencrypt/client/recovery_contact_challenge.py index 3509f93fb..65f6940a0 100644 --- a/letsencrypt/client/recovery_contact_challenge.py +++ b/letsencrypt/client/recovery_contact_challenge.py @@ -1,11 +1,11 @@ -import requests - -from letsencrypt.client.challenge import Challenge -from letsencrypt.client import logger -from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT import dialog +import requests +import time -class RecoveryContact(Challenge): +from letsencrypt.client import challenge + + +class RecoveryContact(challenge.Challenge): def __init__(self, activationURL = "", successURL = "", contact = "", poll_delay = 3): self.token = "" diff --git a/letsencrypt/client/recovery_token_challenge.py b/letsencrypt/client/recovery_token_challenge.py index 1e81bd775..4b1206541 100644 --- a/letsencrypt/client/recovery_token_challenge.py +++ b/letsencrypt/client/recovery_token_challenge.py @@ -1,6 +1,9 @@ -from letsencrypt.client.challenge import Challenge +import dialog -class RecoveryToken(Challenge): +from letsencrypt.client import challenge + + +class RecoveryToken(challenge.Challenge): def __init__(self): self.token = "" diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index e21a557e4..fa2166127 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -1,14 +1,15 @@ #!/usr/bin/env python - -# This file parses the command line and calls the appropriate functions - +"""Parse command line and call the appropriate functions.""" import getopt import os import sys +from letsencrypt.client import apache_configurator +from letsencrypt.client import CONFIG from letsencrypt.client import client from letsencrypt.client import display -from letsencrypt.client.CONFIG import ACME_SERVER +from letsencrypt.client import logger + def main(): # Check to make sure user is root @@ -49,7 +50,6 @@ def main(): elif o == "--server": server = a elif o == "--rollback": - from letsencrypt.client import apache_configurator, logger logger.setLogger(logger.FileLogger(sys.stdout)) logger.setLogLevel(logger.INFO) config = apache_configurator.ApacheConfigurator() @@ -57,7 +57,6 @@ def main(): config.restart() sys.exit(0) elif o == "--view-checkpoints": - from letsencrypt.client import apache_configurator, logger logger.setLogger(logger.FileLogger(sys.stdout)) logger.setLogLevel(logger.INFO) config = apache_configurator.ApacheConfigurator() @@ -84,7 +83,7 @@ def main(): display.setDisplay(display.FileDisplay(sys.stdout)) if not server: - server = ACME_SERVER + server = CONFIG.ACME_SERVER c = client.Client(server, csr, privkey, curses) if flag_revoke: