Removed trailing whitespace as well as unused imports

This commit is contained in:
Alex Gaynor
2014-11-18 16:41:13 -08:00
parent 3ff20fbeb4
commit 0b75c5194f
12 changed files with 157 additions and 176 deletions
+7 -7
View File
@@ -16,10 +16,10 @@ def main():
sys.exit("\nOnly root can run letsencrypt.\n") sys.exit("\nOnly root can run letsencrypt.\n")
# Parse options # Parse options
try: try:
opts, args = getopt.getopt(sys.argv[1:], "", ["text", "test", opts, args = getopt.getopt(sys.argv[1:], "", ["text", "test",
"view-checkpoints", "view-checkpoints",
"privkey=", "csr=", "privkey=", "csr=",
"server=", "rollback=", "server=", "rollback=",
"revoke", "agree-eula", "revoke", "agree-eula",
"redirect", "redirect",
"no-redirect", "no-redirect",
@@ -82,10 +82,10 @@ def main():
display.setDisplay(display.NcursesDisplay()) display.setDisplay(display.NcursesDisplay())
else: else:
display.setDisplay(display.FileDisplay(sys.stdout)) display.setDisplay(display.FileDisplay(sys.stdout))
if not server: if not server:
server = ACME_SERVER server = ACME_SERVER
c = client.Client(server, csr, privkey, curses) c = client.Client(server, csr, privkey, curses)
if flag_revoke: if flag_revoke:
c.list_certs_keys() c.list_certs_keys()
@@ -108,7 +108,7 @@ def print_options():
"view-checkpoints (Used to view available checkpoints and " + "view-checkpoints (Used to view available checkpoints and " +
"see what configuration changes have been made)", "see what configuration changes have been made)",
"rollback=X (Revert the configuration X number of checkpoints)", "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)", "HTTPS for the newly authenticated vhost)",
"no-redirect (Skip the HTTPS redirect question, " + "no-redirect (Skip the HTTPS redirect question, " +
"allowing both HTTP and HTTPS)", "allowing both HTTP and HTTPS)",
+1 -1
View File
@@ -10,4 +10,4 @@ class Challenge(object):
logger.error("Error - base class challenge.generate_response()") logger.error("Error - base class challenge.generate_response()")
def clean(self): def clean(self):
logger.error("Error - base class challenge.clean()") logger.error("Error - base class challenge.clean()")
+9 -16
View File
@@ -2,7 +2,7 @@
import M2Crypto import M2Crypto
import json 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 # 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 # 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 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 import requests
from letsencrypt.client.acme import acme_object_validate from letsencrypt.client.acme import acme_object_validate
from letsencrypt.client.sni_challenge import SNI_Challenge 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 configurator
from letsencrypt.client import logger, display from letsencrypt.client import logger, display
from letsencrypt.client import le_util, crypto_util, display from letsencrypt.client import le_util, crypto_util
from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE, CERT_PATH 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 CHAIN_PATH, SERVER_ROOT, KEY_DIR, CERT_DIR
from letsencrypt.client.CONFIG import CERT_KEY_BACKUP from letsencrypt.client.CONFIG import CERT_KEY_BACKUP
from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES
@@ -50,7 +43,7 @@ class Client(object):
self.config = configurator.Configurator(SERVER_ROOT) self.config = configurator.Configurator(SERVER_ROOT)
self.server = ca_server self.server = ca_server
self.csr_file = cert_signing_request self.csr_file = cert_signing_request
self.key_file = private_key self.key_file = private_key
@@ -70,7 +63,7 @@ class Client(object):
sys.exit(1) sys.exit(1)
self.redirect = redirect self.redirect = redirect
# Display preview warning # Display preview warning
if not eula: if not eula:
with open('EULA') as f: with open('EULA') as f:
@@ -202,7 +195,7 @@ class Client(object):
os.remove(c['backup_cert_file']) os.remove(c['backup_cert_file'])
os.remove(c['backup_key_file']) os.remove(c['backup_key_file'])
def list_certs_keys(self): def list_certs_keys(self):
list_file = CERT_KEY_BACKUP + "LIST" list_file = CERT_KEY_BACKUP + "LIST"
certs = [] certs = []
@@ -214,10 +207,10 @@ class Client(object):
c_sha1_vh = {} c_sha1_vh = {}
for x in self.config.get_all_certs_keys(): for x in self.config.get_all_certs_keys():
try: 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: except:
continue continue
with open(list_file, 'rb') as csvfile: with open(list_file, 'rb') as csvfile:
csvreader = csv.reader(csvfile) csvreader = csv.reader(csvfile)
for row in csvreader: for row in csvreader:
@@ -315,7 +308,7 @@ class Client(object):
#if self.ocsp_stapling: #if self.ocsp_stapling:
# TODO enable OCSP Stapling # TODO enable OCSP Stapling
# continue # continue
def certificate_request(self, csr_der, key): def certificate_request(self, csr_der, key):
logger.info("Preparing and sending CSR..") logger.info("Preparing and sending CSR..")
+90 -92
View File
@@ -3,18 +3,16 @@ import subprocess
import re import re
import os import os
import sys import sys
import stat
import socket import socket
import time import time
import shutil import shutil
import errno
from letsencrypt.client.CONFIG import SERVER_ROOT, BACKUP_DIR 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 REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_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.CONFIG import OPTIONS_SSL_CONF, LE_VHOST_EXT
from letsencrypt.client import logger, le_util 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 #from CONFIG import WORK_DIR, TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR, OPTIONS_SSL_CONF, TRUSTIFY_VHOST_EXT
#import logger, le_util #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 # 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 # 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 # 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 '_' # to use vhost filenames that contain spaces and offer to change ' ' to '_'
# Note: FILEPATHS and changes to files are transactional. They are copied # 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 This class was originally developed for Apache 2.2 and has not seen a
an overhaul to include proper setup of new Apache configurations. an overhaul to include proper setup of new Apache configurations.
The biggest changes have been the IncludeOptional directive, the The biggest changes have been the IncludeOptional directive, the
deprecation of the NameVirtualHost directive, and the name change of deprecation of the NameVirtualHost directive, and the name change of
mod_ssl.c to ssl_module. Although these changes mod_ssl.c to ssl_module. Although these changes
have not been implemented yet, they will be shortly. have not been implemented yet, they will be shortly.
That being said, this class can still adequately configure most typical That being said, this class can still adequately configure most typical
Apache 2.4 servers as the deprecated NameVirtualHost has no effect Apache 2.4 servers as the deprecated NameVirtualHost has no effect
and the typical directories are parsed by the Augeas configuration and the typical directories are parsed by the Augeas configuration
parser automatically. parser automatically.
The API of this class will change in the coming weeks as the exact 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. needs of client's are clarified with the new and developing protocol.
This class will eventually derive from a generic Configurator class This class will eventually derive from a generic Configurator class
so that other Configurators (like Nginx) can be developed and interoperate so that other Configurators (like Nginx) can be developed and interoperate
with the client. with the client.
""" """
def __init__(self, server_root=SERVER_ROOT): 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 # relevant files - I believe -> NO_MODL_AUTOLOAD
self.server_root = server_root self.server_root = server_root
@@ -103,7 +101,7 @@ class Configurator(object):
self.check_parsing_errors() self.check_parsing_errors()
# This problem has been fixed in Augeas 1.0 # This problem has been fixed in Augeas 1.0
self.standardize_excl() self.standardize_excl()
# Determine user's main config file # Determine user's main config file
self.__set_user_config_file() self.__set_user_config_file()
@@ -114,20 +112,20 @@ class Configurator(object):
self.assoc = dict() self.assoc = dict()
# Verify that all directories and files exist with proper permissions # Verify that all directories and files exist with proper permissions
self.verify_setup() self.verify_setup()
# Note: initialization doesn't check to see if the config is correct # 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) # 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. # isn't required on startup.
# TODO: This function can be improved to ensure that the final directives # 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 # are being modified whether that be in the include files or in the
# virtualhost declaration - these directives can be overwritten # virtualhost declaration - these directives can be overwritten
def deploy_cert(self, vhost, cert, key, cert_chain=None): def deploy_cert(self, vhost, cert, key, cert_chain=None):
""" """
Currently tries to find the last directives to deploy the cert in 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 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 the three directives and finally modifies them to point to the correct
destination destination
TODO: Make sure last directive is changed TODO: Make sure last directive is changed
@@ -136,23 +134,23 @@ class Configurator(object):
""" """
search = {} search = {}
path = {} path = {}
path["cert_file"] = self.find_directive(self.case_i("SSLCertificateFile"), None, vhost.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) path["cert_key"] = self.find_directive(self.case_i("SSLCertificateKeyFile"), None, vhost.path)
# Only include if a certificate chain is specified # Only include if a certificate chain is specified
if cert_chain is not None: if cert_chain is not None:
path["cert_chain"] = self.find_directive(self.case_i("SSLCertificateChainFile"), None, vhost.path) 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: if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0:
# Throw some "can't find all of the directives error" # 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("Warn: cannot find a cert or key directive in " + vhost.path)
logger.warn("VirtualHost was not modified") logger.warn("VirtualHost was not modified")
# Presumably break here so that the virtualhost is not modified # Presumably break here so that the virtualhost is not modified
return False return False
logger.info("Deploying Certificate to VirtualHost %s" % vhost.file) logger.info("Deploying Certificate to VirtualHost %s" % vhost.file)
self.aug.set(path["cert_file"][0], cert) self.aug.set(path["cert_file"][0], cert)
self.aug.set(path["cert_key"][0], key) self.aug.set(path["cert_key"][0], key)
if cert_chain is not None: if cert_chain is not None:
@@ -160,7 +158,7 @@ class Configurator(object):
self.add_dir(vhost.path, "SSLCertificateChainFile", cert_chain) self.add_dir(vhost.path, "SSLCertificateChainFile", cert_chain)
else: else:
self.aug.set(path["cert_chain"][0], cert_chain) 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 += "Changed vhost at %s with addresses of %s\n" % (vhost.file, vhost.addrs)
self.save_notes += "\tSSLCertificateFile %s\n" % cert self.save_notes += "\tSSLCertificateFile %s\n" % cert
self.save_notes += "\tSSLCertificateKeyFile %s\n" % key self.save_notes += "\tSSLCertificateKeyFile %s\n" % key
@@ -218,7 +216,7 @@ class Configurator(object):
""" """
self.assoc[dn] = vh self.assoc[dn] = vh
return return
def get_all_names(self): def get_all_names(self):
""" """
Returns all names found in the Apache Configuration Returns all names found in the Apache Configuration
@@ -259,7 +257,7 @@ class Configurator(object):
#def __is_private_ip(ipaddr): #def __is_private_ip(ipaddr):
# re.compile() # re.compile()
def __add_servernames(self, host): def __add_servernames(self, host):
""" """
@@ -270,7 +268,7 @@ class Configurator(object):
args = self.aug.match(name + "/*") args = self.aug.match(name + "/*")
for arg in args: for arg in args:
host.add_name(self.aug.get(arg)) host.add_name(self.aug.get(arg))
def __create_vhost(self, path): def __create_vhost(self, path):
""" """
@@ -307,13 +305,13 @@ class Configurator(object):
addr: string addr: string
""" """
# search for NameVirtualHost directive for ip_addr # 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 # note ip_addr can be FQDN although Apache does not recommend it
paths = self.find_directive(self.case_i("NameVirtualHost"), None) paths = self.find_directive(self.case_i("NameVirtualHost"), None)
name_vh = [] name_vh = []
for p in paths: for p in paths:
name_vh.append(self.aug.get(p)) name_vh.append(self.aug.get(p))
# Mixed and matched wildcard NameVirtualHost with VirtualHost # Mixed and matched wildcard NameVirtualHost with VirtualHost
# behavior is undefined. Make sure that an exact match exists # behavior is undefined. Make sure that an exact match exists
@@ -321,7 +319,7 @@ class Configurator(object):
for vh in name_vh: for vh in name_vh:
if vh == addr: if vh == addr:
return True return True
return False return False
def add_name_vhost(self, addr): def add_name_vhost(self, addr):
@@ -332,17 +330,17 @@ class Configurator(object):
""" """
aug_file_path = "/files%sports.conf" % self.server_root aug_file_path = "/files%sports.conf" % self.server_root
self.add_dir_to_ifmodssl(aug_file_path, "NameVirtualHost", addr) self.add_dir_to_ifmodssl(aug_file_path, "NameVirtualHost", addr)
if len(self.find_directive(self.case_i("NameVirtualHost"), self.case_i(addr))) == 0: 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("ports.conf is not included in your Apache config...")
logger.warn("Adding NameVirtualHost directive to httpd.conf") logger.warn("Adding NameVirtualHost directive to httpd.conf")
self.add_dir_to_ifmodssl("/files" + self.server_root + "httpd.conf", "NameVirtualHost", addr) self.add_dir_to_ifmodssl("/files" + self.server_root + "httpd.conf", "NameVirtualHost", addr)
self.save_notes += 'Setting %s to be NameBasedVirtualHost\n' % addr self.save_notes += 'Setting %s to be NameBasedVirtualHost\n' % addr
def add_dir_to_ifmodssl(self, aug_conf_path, directive, val): 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 an IfMod mod_ssl.c block. If the IfMod block does not exist in
the file, it is created. the file, it is created.
""" """
@@ -377,7 +375,7 @@ class Configurator(object):
# Check for NameVirtualHost # Check for NameVirtualHost
# First see if any of the vhost addresses is a _default_ addr # First see if any of the vhost addresses is a _default_ addr
for addr in vhost.addrs: for addr in vhost.addrs:
tup = addr.partition(":") tup = addr.partition(":")
if tup[0] == "_default_": if tup[0] == "_default_":
if not self.is_name_vhost(default_addr): if not self.is_name_vhost(default_addr):
logger.debug("Setting all VirtualHosts on " + default_addr + " to be name based virtual hosts") 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): if not self.is_name_vhost(addr):
logger.debug("Setting VirtualHost at" + addr + "to be a name based virtual host") logger.debug("Setting VirtualHost at" + addr + "to be a name based virtual host")
self.add_name_vhost(addr) self.add_name_vhost(addr)
return True return True
def get_ifmod(self, aug_conf_path, mod): 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)) ifMods = self.aug.match("%s/IfModule/*[self::arg='%s']" % (aug_conf_path, mod))
# Strip off "arg" at end of first ifmod path # Strip off "arg" at end of first ifmod path
return ifMods[0][:len(ifMods[0]) - 3] return ifMods[0][:len(ifMods[0]) - 3]
def add_dir(self, aug_conf_path, directive, arg): def add_dir(self, aug_conf_path, directive, arg):
""" """
Appends directive to end of file given by aug_conf_path 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) self.aug.set(aug_conf_path + "/directive[last()]/arg", arg)
else: else:
for i in range(len(arg)): 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=""): def find_directive(self, directive, arg=None, start=""):
""" """
Recursively searches through config files to find directives Recursively searches through config files to find directives
@@ -424,17 +422,17 @@ class Configurator(object):
TODO: arg should probably be a list TODO: arg should probably be a list
Note: Augeas is inherently case sensitive while Apache is case 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 regexp(/Listen/, 'i'), however the version currently supported
by Ubuntu 0.10 does not. Thus I have included my own case insensitive by Ubuntu 0.10 does not. Thus I have included my own case insensitive
transformation by calling case_i() on everything to maintain transformation by calling case_i() on everything to maintain
compatibility. compatibility.
""" """
# Cannot place member variable in the definition of the function so... # Cannot place member variable in the definition of the function so...
if not start: if not start:
start = "/files%sapache2.conf" % self.server_root start = "/files%sapache2.conf" % self.server_root
#Debug code #Debug code
#print "find_dir:", directive, "arg:", arg, " | Looking in:", start #print "find_dir:", directive, "arg:", arg, " | Looking in:", start
# No regexp code # No regexp code
@@ -442,20 +440,20 @@ class Configurator(object):
# matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg") # matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg")
# else: # else:
# matches = self.aug.match(start + "//*[self::directive='" + directive+"']/* [self::arg='" + arg + "']") # matches = self.aug.match(start + "//*[self::directive='" + directive+"']/* [self::arg='" + arg + "']")
# includes = self.aug.match(start + "//* [self::directive='Include']/* [label()='arg']") # includes = self.aug.match(start + "//* [self::directive='Include']/* [label()='arg']")
if arg is None: if arg is None:
matches = self.aug.match(start + "//*[self::directive=~regexp('%s')]/arg" % directive) matches = self.aug.match(start + "//*[self::directive=~regexp('%s')]/arg" % directive)
else: else:
matches = self.aug.match(start + "//*[self::directive=~regexp('%s')]/*[self::arg=~regexp('%s')]" % (directive, arg)) 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')) includes = self.aug.match(start + "//* [self::directive=~regexp('%s')]/* [label()='arg']" % self.case_i('Include'))
for include in includes: for include in includes:
# start[6:] to strip off /files # 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)))) matches.extend(self.find_directive(directive, arg, self.get_include_path(self.strip_dir(start[6:]), self.aug.get(include))))
return matches return matches
def case_i(self, string): def case_i(self, string):
@@ -463,15 +461,15 @@ class Configurator(object):
Returns a sloppy, but necessary version of a case insensitive regex. Returns a sloppy, but necessary version of a case insensitive regex.
Any string should be able to be submitted and the string is Any string should be able to be submitted and the string is
escaped and then made case insensitive. 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. supported.
""" """
return "".join(["["+c.upper()+c.lower()+"]" if c.isalpha() else c for c in re.escape(string)]) return "".join(["["+c.upper()+c.lower()+"]" if c.isalpha() else c for c in re.escape(string)])
def strip_dir(self, path): 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 or directive path
Returns the current directory from a file_path along with the file 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): 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 searchable path
Returns path string Returns path string
""" """
@@ -493,11 +491,11 @@ class Configurator(object):
# If the attacker can Include anything though... and this function # If the attacker can Include anything though... and this function
# only operates on Apache real config data... then the attacker has # only operates on Apache real config data... then the attacker has
# already won. # 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? # included files?
# check_config to validate apache config doesn't work because it # check_config to validate apache config doesn't work because it
# would create a race condition between the check and this input # would create a race condition between the check and this input
# TODO: Fix this # TODO: Fix this
# Check to make sure only expected characters are used <- maybe remove # Check to make sure only expected characters are used <- maybe remove
# validChars = re.compile("[a-zA-Z0-9.*?_-/]*") # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
@@ -513,10 +511,10 @@ class Configurator(object):
elif arg.startswith("conf/"): elif arg.startswith("conf/"):
arg = self.server_root + arg[5:] arg = self.server_root + arg[5:]
# TODO: Test if Apache allows ../ or ~/ for Includes # TODO: Test if Apache allows ../ or ~/ for Includes
# Attempts to add a transform to the file if one does not already exist # Attempts to add a transform to the file if one does not already exist
self.parse_file(arg) self.parse_file(arg)
# Argument represents an fnmatch regular expression, convert it # Argument represents an fnmatch regular expression, convert it
# Split up the path and convert each into an Augeas accepted regex # Split up the path and convert each into an Augeas accepted regex
# then reassemble # then reassemble
@@ -524,14 +522,14 @@ class Configurator(object):
postfix = "" postfix = ""
splitArg = arg.split("/") splitArg = arg.split("/")
for idx, split in enumerate(splitArg): 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: if "*" in split or "?" in split:
# Turn it into a augeas regex # Turn it into a augeas regex
# TODO: Can this instead be an augeas glob instead of regex # TODO: Can this instead be an augeas glob instead of regex
splitArg[idx] = "* [label()=~regexp('%s')]" % self.fnmatch_to_re(split) splitArg[idx] = "* [label()=~regexp('%s')]" % self.fnmatch_to_re(split)
# Reassemble the argument # Reassemble the argument
arg = "/".join(splitArg) arg = "/".join(splitArg)
# If the include is a directory, just return the directory as a file # If the include is a directory, just return the directory as a file
if arg.endswith("/"): if arg.endswith("/"):
return "/files" + arg[:len(arg)-1] return "/files" + arg[:len(arg)-1]
@@ -563,7 +561,7 @@ class Configurator(object):
ssl_fp = avail_fp[:-(len(".conf"))] + LE_VHOST_EXT ssl_fp = avail_fp[:-(len(".conf"))] + LE_VHOST_EXT
else: else:
ssl_fp = avail_fp + LE_VHOST_EXT ssl_fp = avail_fp + LE_VHOST_EXT
# First register the creation so that it is properly removed if # First register the creation so that it is properly removed if
# configuration is rolled back # configuration is rolled back
self.register_file_creation(False, ssl_fp) self.register_file_creation(False, ssl_fp)
@@ -617,7 +615,7 @@ class Configurator(object):
logger.info("Created an SSL vhost at %s" % ssl_fp) logger.info("Created an SSL vhost at %s" % ssl_fp)
self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp
self.save() self.save()
# We know the length is one because of the assertion above # We know the length is one because of the assertion above
ssl_vhost = self.__create_vhost(vh_p[0]) ssl_vhost = self.__create_vhost(vh_p[0])
self.vhosts.append(ssl_vhost) self.vhosts.append(ssl_vhost)
@@ -633,7 +631,7 @@ class Configurator(object):
self.add_name_vhost(ssl_addrs[i]) self.add_name_vhost(ssl_addrs[i])
logger.info("Enabling NameVirtualHosts on " + ssl_addrs[i]) logger.info("Enabling NameVirtualHosts on " + ssl_addrs[i])
need_to_save = True need_to_save = True
if need_to_save: if need_to_save:
self.save() self.save()
@@ -699,14 +697,14 @@ class Configurator(object):
return True, 0 return True, 0
# Rewrite path exists but is not a letsencrypt https rule # Rewrite path exists but is not a letsencrypt https rule
return True, 2 return True, 2
def create_redirect_vhost(self, ssl_vhost): def create_redirect_vhost(self, ssl_vhost):
# Consider changing this to a dictionary check # Consider changing this to a dictionary check
# Make sure adding the vhost will be safe # Make sure adding the vhost will be safe
conflict, hostOrAddrs = self.__conflicting_host(ssl_vhost) conflict, hostOrAddrs = self.__conflicting_host(ssl_vhost)
if conflict: if conflict:
return False, hostOrAddrs return False, hostOrAddrs
redirect_addrs = hostOrAddrs redirect_addrs = hostOrAddrs
# get servernames and serveraliases # 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\ ErrorLog /var/log/apache2/redirect.error.log \n\
LogLevel warn \n\ LogLevel warn \n\
</VirtualHost>\n" </VirtualHost>\n"
# Write out the file # Write out the file
# This is the default name # This is the default name
redirect_filename = "letsencrypt-redirect.conf" redirect_filename = "letsencrypt-redirect.conf"
@@ -758,12 +756,12 @@ LogLevel warn \n\
new_fp = self.server_root + "sites-available/" + redirect_filename new_fp = self.server_root + "sites-available/" + redirect_filename
new_vhost = self.__create_vhost("/files" + new_fp) new_vhost = self.__create_vhost("/files" + new_fp)
self.vhosts.append(new_vhost) self.vhosts.append(new_vhost)
# Finally create documentation for the change # 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) 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 return True, new_vhost
def __conflicting_host(self, ssl_vhost): def __conflicting_host(self, ssl_vhost):
''' '''
Checks for a conflicting host, such that a new port 80 host could not 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 redirect_addrs = redirect_addrs + ssl_a_vhttp
return False, redirect_addrs return False, redirect_addrs
def __general_vhost(self, ssl_vhost): def __general_vhost(self, ssl_vhost):
""" """
Function needs to be thoroughly tested and perhaps improved Function needs to be thoroughly tested and perhaps improved
@@ -811,7 +809,7 @@ LogLevel warn \n\
ssl_addrs = ssl_vhost.addrs ssl_addrs = ssl_vhost.addrs
if ssl_addrs == ["_default_:443"]: if ssl_addrs == ["_default_:443"]:
ssl_addrs = ["*:443"] ssl_addrs = ["*:443"]
for vh in self.vhosts: for vh in self.vhosts:
found = 0 found = 0
# Not the same vhost, and same number of addresses # 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] == "*": if test_tup[2] == "80" or test_tup[2] == "" or test_tup[2] == "*":
found += 1 found += 1
break break
# Check to make sure all addresses were found # Check to make sure all addresses were found
# and names are equal # and names are equal
if found == len(ssl_vhost.addrs) and set(vh.names) == set(ssl_vhost.names): if found == len(ssl_vhost.addrs) and set(vh.names) == set(ssl_vhost.names):
return vh return vh
@@ -875,7 +873,7 @@ LogLevel warn \n\
continue continue
break break
return avail_fp return avail_fp
def is_site_enabled(self, avail_fp): def is_site_enabled(self, avail_fp):
""" """
Checks to see if the given site is enabled 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 self.save_notes += 'Enabled site %s\n' % vhost.file
return True return True
return False return False
def enable_mod(self, mod_name): def enable_mod(self, mod_name):
""" """
Enables mod_ssl Enables mod_ssl
""" """
try: 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')) 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')) subprocess.check_call(["sudo", "/etc/init.d/apache2", "restart"], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w'))
except: except:
logger.error("Error enabling mod_" + mod_name) 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.aug.add_transform("Httpd.lns", self.httpd_incl, None, self.httpd_excl)
self.__add_httpd_transform(file_path) self.__add_httpd_transform(file_path)
self.aug.load() self.aug.load()
def save_apache_config(self): def save_apache_config(self):
# Not currently used # Not currently used
# Should be safe because it is a protected directory # 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, BACKUP_DIR + "apache2-" + str(time.time()))
def recovery_routine(self): def recovery_routine(self):
""" """
Revert all previously modified files. First, any changes found in Revert all previously modified files. First, any changes found in
@@ -997,13 +995,13 @@ LogLevel warn \n\
except IOError: except IOError:
logger.fatal("Unable to remove filepaths contained within %s" % file_list) logger.fatal("Unable to remove filepaths contained within %s" % file_list)
sys.exit(41) sys.exit(41)
return True return True
def verify_setup(self): def verify_setup(self):
''' '''
Make sure that files/directories are setup with appropriate permissions 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 have permissions of root
''' '''
le_util.make_or_verify_dir(CONFIG_DIR, 0755) 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 # 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 + "*/*/*~"] 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)): for i in range(len(excl)):
self.aug.set("/augeas/load/Httpd/excl[%d]" % (i+1), excl[i]) self.aug.set("/augeas/load/Httpd/excl[%d]" % (i+1), excl[i])
@@ -1040,7 +1038,7 @@ LogLevel warn \n\
for e in error_files: for e in error_files:
# Check to see if it was an error resulting from the use of # 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') lens_path = self.aug.get(e + '/lens')
# As aug.get may return null # As aug.get may return null
if lens_path and 'httpd.aug' in lens_path: if lens_path and 'httpd.aug' in lens_path:
@@ -1072,7 +1070,7 @@ LogLevel warn \n\
try: try:
p = subprocess.Popen(['/etc/init.d/apache2', 'restart'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(['/etc/init.d/apache2', 'restart'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
text = p.communicate() text = p.communicate()
if p.returncode != 0: if p.returncode != 0:
# Enter recovery routine... # Enter recovery routine...
@@ -1118,7 +1116,7 @@ LogLevel warn \n\
Saves all changes to the configuration files Saves all changes to the configuration files
This function is not transactional This function is not transactional
TODO: Instead rely on challenge to backup all files before modifications TODO: Instead rely on challenge to backup all files before modifications
title: string - The title of the save. If a title is given, the title: string - The title of the save. If a title is given, the
configuration will be saved as a new checkpoint configuration will be saved as a new checkpoint
and put in a timestamped directory. and put in a timestamped directory.
@@ -1148,7 +1146,7 @@ LogLevel warn \n\
return False return False
# Retrieve list of modified files # 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. # set to remove this possibility. This is a known augeas 0.10 error.
save_paths = self.aug.match("/augeas/events/saved") save_paths = self.aug.match("/augeas/events/saved")
@@ -1172,7 +1170,7 @@ LogLevel warn \n\
self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files) self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files)
else: else:
self.add_to_checkpoint(IN_PROGRESS_DIR, save_files) self.add_to_checkpoint(IN_PROGRESS_DIR, save_files)
if title and not temporary and os.path.isdir(IN_PROGRESS_DIR): if title and not temporary and os.path.isdir(IN_PROGRESS_DIR):
success = self.__finalize_checkpoint(IN_PROGRESS_DIR, title) 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 # This will be hopefully be cleaned up on the recovery
# routine startup # routine startup
sys.exit(9) sys.exit(9)
self.aug.set("/augeas/save", save_state) self.aug.set("/augeas/save", save_state)
self.save_notes = "" self.save_notes = ""
@@ -1213,7 +1211,7 @@ LogLevel warn \n\
def add_to_checkpoint(self, cp_dir, save_files): def add_to_checkpoint(self, cp_dir, save_files):
le_util.make_or_verify_dir(cp_dir, 0755) le_util.make_or_verify_dir(cp_dir, 0755)
existing_filepaths = [] existing_filepaths = []
op_fd = None op_fd = None
# Open up FILEPATHS differently depending on if it already exists # Open up FILEPATHS differently depending on if it already exists
@@ -1226,7 +1224,7 @@ LogLevel warn \n\
idx = len(existing_filepaths) idx = len(existing_filepaths)
for filename in save_files: for filename in save_files:
if filename not in existing_filepaths: 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 # have the same filename
logger.debug("Creating backup of %s" % filename) logger.debug("Creating backup of %s" % filename)
shutil.copy2(filename, cp_dir + os.path.basename(filename) + "_" + str(idx)) shutil.copy2(filename, cp_dir + os.path.basename(filename) + "_" + str(idx))
@@ -1252,7 +1250,7 @@ LogLevel warn \n\
if len(backups) < rollback: if len(backups) < rollback:
logger.error("Unable to rollback %d checkpoints, only %d exist" % (rollback, len(backups))) logger.error("Unable to rollback %d checkpoints, only %d exist" % (rollback, len(backups)))
while rollback > 0 and backups: while rollback > 0 and backups:
cp_dir = BACKUP_DIR + backups.pop() cp_dir = BACKUP_DIR + backups.pop()
result = self.__recover_checkpoint(cp_dir) result = self.__recover_checkpoint(cp_dir)
@@ -1266,11 +1264,11 @@ LogLevel warn \n\
def __recover_checkpoint(self, cp_dir): def __recover_checkpoint(self, cp_dir):
""" """
Recover a specific checkpoint provided by 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 returns: 0 success, 1 Unable to revert, -1 Unable to delete
""" """
if os.path.isfile(cp_dir + "/FILEPATHS"): if os.path.isfile(cp_dir + "/FILEPATHS"):
try: try:
with open(cp_dir + "/FILEPATHS") as f: with open(cp_dir + "/FILEPATHS") as f:
@@ -1328,13 +1326,13 @@ LogLevel warn \n\
print time.ctime(float(bu)) print time.ctime(float(bu))
with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f: with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f:
print f.read() print f.read()
print "Affected files:" print "Affected files:"
with open(BACKUP_DIR + bu + "/FILEPATHS") as f: with open(BACKUP_DIR + bu + "/FILEPATHS") as f:
filepaths = f.read().splitlines() filepaths = f.read().splitlines()
for fp in filepaths: for fp in filepaths:
print " %s" % fp print " %s" % fp
try: try:
with open(BACKUP_DIR + bu + "/NEW_FILES") as f: with open(BACKUP_DIR + bu + "/NEW_FILES") as f:
print "New Configuration Files:" print "New Configuration Files:"
@@ -1356,7 +1354,7 @@ LogLevel warn \n\
cp_dir = TEMP_CHECKPOINT_DIR cp_dir = TEMP_CHECKPOINT_DIR
else: else:
cp_dir = IN_PROGRESS_DIR cp_dir = IN_PROGRESS_DIR
le_util.make_or_verify_dir(cp_dir) le_util.make_or_verify_dir(cp_dir)
try: try:
with open(cp_dir + "NEW_FILES", 'a') as fd: with open(cp_dir + "NEW_FILES", 'a') as fd:
@@ -1364,8 +1362,8 @@ LogLevel warn \n\
fd.write("%s\n" % f) fd.write("%s\n" % f)
except: except:
logger.error("ERROR: Unable to register file creation") logger.error("ERROR: Unable to register file creation")
def main(): def main():
config = Configurator() config = Configurator()
@@ -1379,7 +1377,7 @@ def main():
print name print name
""" """
print config.find_directive(config.case_i("NameVirtualHost"), config.case_i("holla:443")) print config.find_directive(config.case_i("NameVirtualHost"), config.case_i("holla:443"))
""" """
for m in config.find_directive("Listen", "443"): for m in config.find_directive("Listen", "443"):
print "Directive Path:", m, "Value:", config.aug.get(m) print "Directive Path:", m, "Value:", config.aug.get(m)
@@ -1417,7 +1415,7 @@ def main():
if vh.addrs[0] == "23.20.47.131:80": if vh.addrs[0] == "23.20.47.131:80":
print "Here we go" print "Here we go"
ssl_vh = config.make_vhost_ssl(vh) ssl_vh = config.make_vhost_ssl(vh)
config.redirect_all_ssl(ssl_vh) config.redirect_all_ssl(ssl_vh)
""" """
""" """
@@ -1425,6 +1423,6 @@ def main():
if len(vh.names) > 0: 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") 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__": if __name__ == "__main__":
main() main()
+2 -3
View File
@@ -8,7 +8,6 @@ from Crypto.Hash import SHA256
from M2Crypto import EVP, X509, ASN1 from M2Crypto import EVP, X509, ASN1
from letsencrypt.client import logger
from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE 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) #rsa = M2Crypto.RSA.gen_key(bits, 65537)
#key_pem = rsa.as_pem(cipher=None) #key_pem = rsa.as_pem(cipher=None)
#rsa = None # should not be freed here #rsa = None # should not be freed here
return key.exportKey(format='PEM') return key.exportKey(format='PEM')
@@ -147,7 +146,7 @@ def get_cert_info(filename):
d["san"] = x.get_ext("subjectAltName").get_value() d["san"] = x.get_ext("subjectAltName").get_value()
except: except:
d["san"] = "" d["san"] = ""
d["serial"] = x.get_serial_number() d["serial"] = x.get_serial_number()
d["pub_key"] = "RSA " + str(x.get_pubkey().size() * 8) d["pub_key"] = "RSA " + str(x.get_pubkey().size() * 8)
return d return d
+13 -13
View File
@@ -25,7 +25,7 @@ class Display(SingletonD):
raise Exception("Error no display defined") raise Exception("Error no display defined")
def success_installation(self, domains): def success_installation(self, domains):
raise Exception("Error no display defined") raise Exception("Error no display defined")
def gen_https_names(self, domains): def gen_https_names(self, domains):
""" """
Returns a string of the domains formatted nicely with https:// prepended Returns a string of the domains formatted nicely with https:// prepended
@@ -94,12 +94,12 @@ class NcursesDisplay(Display):
def generic_input(self, message): def generic_input(self, message):
return self.d.inputbox(message) return self.d.inputbox(message)
def generic_yesno(self, message, yes = "Yes", no = "No"): def generic_yesno(self, message, yes = "Yes", no = "No"):
a = self.d.yesno(message, HEIGHT, WIDTH) a = self.d.yesno(message, HEIGHT, WIDTH)
return a == self.d.DIALOG_OK return a == self.d.DIALOG_OK
def filter_names(self, names): def filter_names(self, names):
choices = [(n, "", 0) for n in names] choices = [(n, "", 0) for n in names]
c, s = self.d.checklist("Which names would you like to activate \ c, s = self.d.checklist("Which names would you like to activate \
@@ -115,12 +115,12 @@ class NcursesDisplay(Display):
def display_certs(self, certs): def display_certs(self, certs):
list_choices = [ list_choices = [
(str(i+1), (str(i+1),
"%s | %s | %s" % "%s | %s | %s" %
(str(c["cn"].ljust(WIDTH - 39)), (str(c["cn"].ljust(WIDTH - 39)),
c["not_before"].strftime("%m-%d-%y"), c["not_before"].strftime("%m-%d-%y"),
"Installed" if c["installed"] else "")) "Installed" if c["installed"] else ""))
for i, c in enumerate(certs)] for i, c in enumerate(certs)]
code, s = self.d.menu("Which certificates would you like to revoke?", code, s = self.d.menu("Which certificates would you like to revoke?",
choices = list_choices, help_button=True, choices = list_choices, help_button=True,
help_label="More Info", ok_label="Revoke", 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"] + menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] +
" - " + str(c["not_before"])[:-6]) " - " + str(c["not_before"])[:-6])
for i, c in enumerate(certs)] for i, c in enumerate(certs)]
self.outfile.write("Which certificate would you like to revoke?\n") self.outfile.write("Which certificate would you like to revoke?\n")
for c in menu_choices: for c in menu_choices:
wm = textwrap.fill("%s: %s - %s Signed (UTC): %s\n" % wm = textwrap.fill("%s: %s - %s Signed (UTC): %s\n" %
@@ -243,9 +243,9 @@ class FileDisplay(Display):
s_f = '*' * (79) s_f = '*' * (79)
wm = textwrap.fill(("Congratulations! You have successfully " + wm = textwrap.fill(("Congratulations! You have successfully " +
"enabled %s!") % self.gen_https_names(domains)) "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)) self.outfile.write(msg % (s_f, wm, s_f))
def confirm_revocation(self, cert): def confirm_revocation(self, cert):
self.outfile.write("Are you sure you would like to revoke \ self.outfile.write("Are you sure you would like to revoke \
the following certificate:\n") the following certificate:\n")
@@ -300,10 +300,10 @@ def redirect_by_default():
choices = [ choices = [
("Easy", "Allow both HTTP and HTTPS access to these sites"), ("Easy", "Allow both HTTP and HTTPS access to these sites"),
("Secure", "Make all requests redirect to secure HTTPS access")] ("Secure", "Make all requests redirect to secure HTTPS access")]
result = display.generic_menu("Please choose whether HTTPS access " + result = display.generic_menu("Please choose whether HTTPS access " +
"is required or optional.", "is required or optional.",
choices, choices,
"Please enter the appropriate number", "Please enter the appropriate number",
width = WIDTH) width = WIDTH)
+4 -5
View File
@@ -1,13 +1,12 @@
from letsencrypt.client.challenge import Challenge from letsencrypt.client.challenge import Challenge
from letsencrypt.client import logger
import textwrap import textwrap
############################################################ ############################################################
# Possible addition to challenge structure: priority parameter # Possible addition to challenge structure: priority parameter
# If only DVSNI and Payment are required, the user might want # If only DVSNI and Payment are required, the user might want
# to be validated before submitting payment, allowing the user # to be validated before submitting payment, allowing the user
# to gain confidence in the system. If things do go poorly the # to gain confidence in the system. If things do go poorly the
# user has less invested in that particular session/transaction. # user has less invested in that particular session/transaction.
############################################################# #############################################################
########################################################### ###########################################################
@@ -22,7 +21,7 @@ class Interactive_Challenge(Challenge):
def __init__(self, string): def __init__(self, string):
self.string = string self.string = string
def perform(self, quiet=True): def perform(self, quiet=True):
if quiet: if quiet:
dialog.Dialog().msgbox(get_display_string(), width=BOX_SIZE) dialog.Dialog().msgbox(get_display_string(), width=BOX_SIZE)
@@ -31,7 +30,7 @@ class Interactive_Challenge(Challenge):
raw_input('') raw_input('')
return True return True
def get_display_string(self): def get_display_string(self):
return textwrap.fill(self.string, width=BOX_SIZE) + "\n\nPlease Press Enter to Continue" return textwrap.fill(self.string, width=BOX_SIZE) + "\n\nPlease Press Enter to Continue"
+9 -10
View File
@@ -1,4 +1,3 @@
import sys
import time import time
from letsencrypt.client import display from letsencrypt.client import display
@@ -55,20 +54,20 @@ class FileLogger(Logger):
import dialog import dialog
class NcursesLogger(Logger): class NcursesLogger(Logger):
def __init__(self, def __init__(self,
firstmessage="", firstmessage="",
height = display.HEIGHT, height = display.HEIGHT,
width = display.WIDTH - 4): width = display.WIDTH - 4):
self.lines = [] self.lines = []
self.all_content = "" self.all_content = ""
self.d = dialog.Dialog() self.d = dialog.Dialog()
self.height = height self.height = height
self.width = width self.width = width
self.add(firstmessage) self.add(firstmessage)
''' '''
Only show the last (self.height) lines; 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 a single line could actually be multiple lines
''' '''
def add(self, s): def add(self, s):
@@ -93,7 +92,7 @@ class NcursesLogger(Logger):
if cur_out != '': if cur_out != '':
self.lines.append(cur_out) self.lines.append(cur_out)
# show last 16 lines # show last 16 lines
self.content = '\n'.join(self.lines[-self.height:]) self.content = '\n'.join(self.lines[-self.height:])
self.show() self.show()
@@ -110,7 +109,7 @@ log_instance = None
def setLogger(log_inst): def setLogger(log_inst):
global log_instance global log_instance
log_instance = log_inst log_instance = log_inst
def setLogLevel(log_level): def setLogLevel(log_level):
global log_instance global log_instance
log_instance.level = log_level log_instance.level = log_level
@@ -140,7 +139,7 @@ def fatal(data):
def none(data): def none(data):
# Uh...what? # Uh...what?
pass pass
if __name__ == "__main__": if __name__ == "__main__":
# Unit test/example usage: # Unit test/example usage:
@@ -165,7 +164,7 @@ if __name__ == "__main__":
time.sleep(0.3) time.sleep(0.3)
# Alternatively, use # Alternatively, use
logger.error("errrrr") logger.error("errrrr")
logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows')) logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows'))
+5 -6
View File
@@ -1,13 +1,12 @@
from letsencrypt.client.challenge import Challenge from letsencrypt.client.challenge import Challenge
from letsencrypt.client import logger
import dialog import dialog
############################################################ ############################################################
# Possible addition to challenge structure: priority parameter # Possible addition to challenge structure: priority parameter
# If only DVSNI and Payment are required, the user might want # If only DVSNI and Payment are required, the user might want
# to be validated before submitting payment, allowing the user # to be validated before submitting payment, allowing the user
# to gain confidence in the system. If things do go poorly the # to gain confidence in the system. If things do go poorly the
# user has less invested in that particular session/transaction. # user has less invested in that particular session/transaction.
############################################################# #############################################################
class Payment_Challenge(Challenge): class Payment_Challenge(Challenge):
@@ -21,7 +20,7 @@ class Payment_Challenge(Challenge):
def cleanup(self): def cleanup(self):
# Currently, payment challenges do not appear to require any cleanup. # Currently, payment challenges do not appear to require any cleanup.
pass pass
def perform(self, quiet=True): def perform(self, quiet=True):
if quiet: if quiet:
dialog.Dialog().msgbox(self.get_display_string(), width=70) dialog.Dialog().msgbox(self.get_display_string(), width=70)
@@ -31,7 +30,7 @@ class Payment_Challenge(Challenge):
self.times_performed += 1 self.times_performed += 1
return True return True
def get_display_string(self): def get_display_string(self):
if self.times_performed == 0: if self.times_performed == 0:
@@ -40,7 +39,7 @@ class Payment_Challenge(Challenge):
# The user has tried at least once... display a different message # The user has tried at least once... display a different message
else: 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" 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): def formatted_reasons(self):
return "\n\t* %s\n" % self.reason return "\n\t* %s\n" % self.reason
@@ -6,7 +6,7 @@ from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT
import dialog import dialog
class RecoveryContact(Challenge): class RecoveryContact(Challenge):
def __init__(self, activationURL = "", successURL = "", contact = "", poll_delay = 3): def __init__(self, activationURL = "", successURL = "", contact = "", poll_delay = 3):
self.token = "" self.token = ""
self.activationURL = activationURL self.activationURL = activationURL
@@ -24,14 +24,14 @@ class RecoveryContact(Challenge):
exit, self.token = d.inputbox(self.get_display_string())) exit, self.token = d.inputbox(self.get_display_string()))
if exit != d.OK: if exit != d.OK:
return False return False
else: else:
print self.get_display_string() print self.get_display_string()
if successURL: if successURL:
return self.poll(10, quiet) return self.poll(10, quiet)
else: else:
self.token = raw_input("Enter the recovery token:") self.token = raw_input("Enter the recovery token:")
return True return True
def cleanup(self): def cleanup(self):
@@ -46,7 +46,7 @@ class RecoveryContact(Challenge):
string += " or respond to the recovery email sent to " + self.contact string += " or respond to the recovery email sent to " + self.contact
elif self.contact: elif self.contact:
string += "Recovery email sent to" + self.contact string += "Recovery email sent to" + self.contact
def poll(self, rounds = 10, quiet = True): def poll(self, rounds = 10, quiet = True):
for i in range(rounds): for i in range(rounds):
if requests.get(self.successURL).status_code != 200: if requests.get(self.successURL).status_code != 200:
@@ -1,18 +1,16 @@
from letsencrypt.client.challenge import Challenge from letsencrypt.client.challenge import Challenge
from letsencrypt.client import logger
from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT
class RecoveryToken(Challenge): class RecoveryToken(Challenge):
def __init__(self): def __init__(self):
self.token = "" self.token = ""
def perform(self, quiet = True): def perform(self, quiet = True):
cancel, self.token = dialog.generic_input("Please Input Recovery Token: ") cancel, self.token = dialog.generic_input("Please Input Recovery Token: ")
if cancel == 1: if cancel == 1:
return False return False
return True return True
def cleanup(self): def cleanup(self):
+10 -14
View File
@@ -1,22 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python
import subprocess
import M2Crypto import M2Crypto
from Crypto import Random from Crypto import Random
import hmac
import hashlib import hashlib
from shutil import move from os import path
from os import remove, close, path
import sys import sys
import binascii import binascii
import augeas
import jose import jose
from letsencrypt.client import configurator from letsencrypt.client import configurator
from letsencrypt.client.CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT 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 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 import logger, crypto_util
from letsencrypt.client.challenge import Challenge from letsencrypt.client.challenge import Challenge
@@ -41,7 +37,7 @@ class SNI_Challenge(Challenge):
self.key = key_filepath self.key = key_filepath
self.configurator = config self.configurator = config
self.s = None self.s = None
def getDvsniCertFile(self, nonce): def getDvsniCertFile(self, nonce):
""" """
@@ -117,7 +113,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
def checkForApacheConfInclude(self, mainConfig): 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 within mainConfig
mainConfig: string - file path to main user apache config file 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] #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')) #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 = hashlib.new('sha256')
h.update(r) h.update(r)
h.update(s) h.update(s)
return h.hexdigest() + INVALID_EXT return h.hexdigest() + INVALID_EXT
def byteToHex(self, byteStr): def byteToHex(self, byteStr):
@@ -205,7 +201,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
""" """
self.configurator.revert_challenge_config() self.configurator.revert_challenge_config()
self.configurator.restart(True) self.configurator.restart(True)
def generate_response(self): def generate_response(self):
""" """
Generates a response for a completed challenge Generates a response for a completed challenge
@@ -275,14 +271,14 @@ def main():
logger.setLogLevel(logger.INFO) logger.setLogLevel(logger.INFO)
testkey = M2Crypto.RSA.load_key(key) testkey = M2Crypto.RSA.load_key(key)
#r = Random.get_random_bytes(S_SIZE) #r = Random.get_random_bytes(S_SIZE)
r = "testValueForR" r = "testValueForR"
#nonce = Random.get_random_bytes(NONCE_SIZE) #nonce = Random.get_random_bytes(NONCE_SIZE)
nonce = "nonce" nonce = "nonce"
r2 = "testValueForR2" r2 = "testValueForR2"
nonce2 = "nonce2" nonce2 = "nonce2"
r = jose.b64encode_url(r) r = jose.b64encode_url(r)
r2 = jose.b64encode_url(r2) r2 = jose.b64encode_url(r2)
@@ -296,7 +292,7 @@ def main():
nonce = binascii.hexlify(nonce) nonce = binascii.hexlify(nonce)
nonce2 = binascii.hexlify(nonce2) nonce2 = binascii.hexlify(nonce2)
config = configurator.Configurator() config = configurator.Configurator()
challenges = [("client.theobroma.info", r, nonce), ("foo.theobroma.info",r2, nonce2)] challenges = [("client.theobroma.info", r, nonce), ("foo.theobroma.info",r2, nonce2)]