diff --git a/client-webserver/CONFIG.py b/client-webserver/CONFIG.py new file mode 100644 index 000000000..ac94d2783 --- /dev/null +++ b/client-webserver/CONFIG.py @@ -0,0 +1,19 @@ +# Apache server root directory +SERVER_ROOT = "/etc/apache2/" +# Configuration file directory for trustify +CONFIG_DIR = "/etc/trustify/" +# Working directory for trustify +WORK_DIR = "/var/lib/trustify/" + +# Used by openssl to sign challenge certificate with trustify extension +CHOC_CERT_CONF = CONFIG_DIR + "choc_cert_extensions.cnf" +# Contains standard Apache SSL directives +OPTIONS_SSL_CONF = CONFIG_DIR + "options-ssl.conf" +# Temporary file for challenge virtual hosts +APACHE_CHALLENGE_CONF = CONFIG_DIR + "choc_sni_cert_challenge.conf" +# Byte size of S and Nonce +S_SIZE = 32 +NONCE_SIZE = 32 + +# bits of hashcash to generate +difficulty = 23 diff --git a/client-webserver/client.py b/client-webserver/client.py index 9e506a53a..60fb1088b 100755 --- a/client-webserver/client.py +++ b/client-webserver/client.py @@ -6,7 +6,8 @@ import urllib2, os, grp, pwd, sys, time, random, sys, hashlib, subprocess # It is OK to use the upstream M2Crypto here instead of our modified # version. -difficulty = 23 # bits of hashcash to generate +# bits of hashcash to generate +from CONFIG import difficulty def sha256(m): return hashlib.sha256(m).hexdigest() diff --git a/client-webserver/configurator.py b/client-webserver/configurator.py index 3138cb59c..025da5a3e 100644 --- a/client-webserver/configurator.py +++ b/client-webserver/configurator.py @@ -5,7 +5,7 @@ import os import sys import socket -BASE_DIR = "/etc/apache2/" +from CONFIG import SERVER_ROOT class VH(object): def __init__(self, vh_path, vh_addrs): @@ -141,7 +141,7 @@ class Configurator(object): Returns list of virtual hosts found in the Apache configuration """ #Search sites-available, httpd.conf for possible virtual hosts - paths = self.aug.match("/files" + BASE_DIR + "sites-available//VirtualHost") + paths = self.aug.match("/files" + SERVER_ROOT + "sites-available//VirtualHost") vhs = [] for p in paths: addrs = [] @@ -192,13 +192,13 @@ class Configurator(object): Adds NameVirtualHost directive for given address Directive is added to ports.conf unless """ - aug_file_path = "/files" + BASE_DIR + "ports.conf" + aug_file_path = "/files" + SERVER_ROOT + "ports.conf" self.add_dir_to_ifmodssl(aug_file_path, "NameVirtualHost", addr) if len(self.find_directive("NameVirtualHost", addr)) == 0: print "ports.conf is not included in your Apache config... " print "Adding NameVirtualHost directive to httpd.conf" - self.add_dir_to_ifmodssl("/files" + BASE_DIR + "httpd.conf", "NameVirtualHost", addr) + self.add_dir_to_ifmodssl("/files" + SERVER_ROOT + "httpd.conf", "NameVirtualHost", addr) def add_dir_to_ifmodssl(self, aug_conf_path, directive, val): @@ -232,7 +232,7 @@ class Configurator(object): if len(self.find_directive("Listen", "443")) == 0: print self.find_directive("Listen", "443") print "Setting the Apache Server to Listen on port 443" - self.add_dir_to_ifmodssl("/files" + BASE_DIR + "ports.conf", "Listen", "443") + self.add_dir_to_ifmodssl("/files" + SERVER_ROOT + "ports.conf", "Listen", "443") # Check for NameVirtualHost # First see if any of the vhost addresses is a _default_ addr @@ -271,7 +271,7 @@ class Configurator(object): self.aug.set(aug_conf_path + "/directive[last() + 1]", directive) self.aug.set(aug_conf_path + "/directive[last()]/arg", arg) - def find_directive(self, directive, arg=None, start="/files"+BASE_DIR+"apache2.conf"): + def find_directive(self, directive, arg=None, start="/files"+SERVER_ROOT+"apache2.conf"): """ Recursively searches through config files to find directives TODO: arg should probably be a list @@ -311,7 +311,7 @@ class Configurator(object): arg = cur_dir + arg # conf/ is a special variable for ServerRoot in Apache elif arg.startswith("conf/"): - arg = BASE_DIR + arg[5:] + arg = SERVER_ROOT + arg[5:] # TODO: Test if Apache allows ../ or ~/ for Includes # Attempts to add a transform to the file if one does not already exist @@ -360,7 +360,7 @@ class Configurator(object): avail_fp: string - Should be complete file path """ - enabled_dir = BASE_DIR + "sites-enabled/" + enabled_dir = SERVER_ROOT + "sites-enabled/" for f in os.listdir(enabled_dir): if os.path.realpath(enabled_dir + f) == avail_fp: return True @@ -374,7 +374,7 @@ class Configurator(object): """ if "/sites-available/" in avail_fp: index = avail_fp.rfind("/") - os.symlink(avail_fp, BASE_DIR + "sites-enabled/" + avail_fp[index:]) + os.symlink(avail_fp, SERVER_ROOT + "sites-enabled/" + avail_fp[index:]) return True return False @@ -387,11 +387,11 @@ class Configurator(object): subprocess.check_output(["sudo", "a2enmod", "ssl"]) subprocess.call(["sudo", "/etc/init.d/apache2", "reload"]) """ - a_conf = BASE_DIR + "mods-available/ssl.conf" - a_load = BASE_DIR + "mods-available/ssl.load" + a_conf = SERVER_ROOT + "mods-available/ssl.conf" + a_load = SERVER_ROOT + "mods-available/ssl.load" if os.path.exists(a_conf) and os.path.exists(a_load): - os.symlink(a_conf, BASE_DIR + "mods-enabled/ssl.conf") - os.symlink(a_load, BASE_DIR + "mods-enabled/ssl.load") + os.symlink(a_conf, SERVER_ROOT + "mods-enabled/ssl.conf") + os.symlink(a_load, SERVER_ROOT + "mods-enabled/ssl.load") return True return False """ @@ -403,7 +403,7 @@ class Configurator(object): # Standardize the include argument based on server root arg = includeArg if not includeArg.startswith("/"): - arg = BASE_DIR + includeArg + arg = SERVER_ROOT + includeArg # Test if augeas included file for Httpd.lens incTest = aug.match("/files" + arg + "/*") diff --git a/client-webserver/sni_challenge.py b/client-webserver/sni_challenge.py index 8fb58ac4a..25b6ad9af 100644 --- a/client-webserver/sni_challenge.py +++ b/client-webserver/sni_challenge.py @@ -6,18 +6,14 @@ from Crypto import Random import hmac import hashlib from shutil import move -from os import remove, close +from os import remove, close, path import binascii import augeas import configurator -#import dns.resolver -CHOC_DIR = "/home/ubuntu/chocolate/client-webserver/" -CHOC_CERT_CONF = "choc_cert_extensions.cnf" -OPTIONS_SSL_CONF = CHOC_DIR + "options-ssl.conf" -APACHE_CHALLENGE_CONF = CHOC_DIR + "choc_sni_cert_challenge.conf" -S_SIZE = 32 -NONCE_SIZE = 32 +from CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT +from CONFIG import CHOC_CERT_CONF, OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF +from CONFIG import S_SIZE, NONCE_SIZE def getChocCertFile(nonce): """ @@ -28,26 +24,20 @@ def getChocCertFile(nonce): result: returns certificate file name """ - return CHOC_DIR + nonce + ".crt" + return WORK_DIR + nonce + ".crt" def findApacheConfigFile(): """ Locates the file path to the user's main apache config - TODO: This needs to be rewritten... should use true ServerRoot + TODO: This needs to use true server_root result: returns file path if present """ - # This needs to be fixed to account for multiple httpd.conf files - try: - p = subprocess.check_output(["sudo", "find", "/etc", "-name", "httpd.conf"], stderr=open("/dev/null")) - p = p[:len(p)-1] - print "Apache Config: ", p - return p - except subprocess.CalledProcessError, e: - print "httpd.conf not found" - print "Please include .... in the conf file" - return None + if path.isfile(SERVER_ROOT + "httpd.conf"): + return SERVER_ROOT + "httpd.conf" + print "Unable to find httpd.conf, file does not exist in Apache ServerRoot" + return None def getConfigText(nonce, ip_addrs, key): """ @@ -70,7 +60,7 @@ Include " + OPTIONS_SSL_CONF + " \n \ SSLCertificateFile " + getChocCertFile(nonce) + " \n \ SSLCertificateKeyFile " + key + " \n \ \n \ -DocumentRoot " + CHOC_DIR + "challenge_page/ \n \ +DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ \n\n " return configText @@ -191,6 +181,9 @@ def apache_restart(): """ subprocess.call(["sudo", "/etc/init.d/apache2", "reload"]) +# TODO: This function is insufficient as the user could edit the files +# before the challenge is completed. It is safer to log all of the changes +# and revert each one individually def cleanup(listSNITuple, configurator): """ Remove all temporary changes necessary to perform the challenge @@ -259,9 +252,10 @@ def perform_sni_cert_challenge(listSNITuple, csr, key, configurator): apache_restart() return True +# This main function is just used for testing def main(): - key = CHOC_DIR + "key.pem" - csr = CHOC_DIR + "req.pem" + key = path.abspath("key.pem") + csr = path.abspath("req.pem") testkey = M2Crypto.RSA.load_key(key) @@ -287,15 +281,17 @@ def main(): challenges = [("example.com", y, nonce, "1.3.3.7"), ("www.example.com",y2, nonce2, "1.3.3.7")] #challenges = [("127.0.0.1", y, nonce, "1.3.3.7"), ("localhost", y2, nonce2, "1.3.3.7")] - perform_sni_cert_challenge(challenges, csr, key, config) + if perform_sni_cert_challenge(challenges, csr, key, config): + + # Waste some time without importing time module... just for testing + for i in range(0, 12000): + if i % 2000 == 0: + print "Waiting:", i - # Waste some time without importing time module... just for testing - for i in range(0, 12000): - if i % 2000 == 0: - print "Waiting:", i - - print "Cleaning up" - cleanup(challenges, config) + print "Cleaning up" + cleanup(challenges, config) + else: + print "Failed SNI challenge..." if __name__ == "__main__": main() diff --git a/server-ca/daemon.py b/server-ca/daemon.py index 16a1a6f95..967a994d7 100644 --- a/server-ca/daemon.py +++ b/server-ca/daemon.py @@ -280,42 +280,36 @@ dispatch = { "makechallenge": ("pending-makechallenge", makechallenge), "done": ("pending-done", lambda x: None) } # Main loop: act on queues notified via Redis pubsub mechanism. -# If the queue is empty by the time we pop from it (indicated by -# session is None), some other daemon instance has already handled -# the request, which is fine; we then return immediately to waiting -# for the next request. +# Currently, we ignore the specific details of which queue was +# notified and, upon any notification, repeatedly process a single +# item from each queue until all queues are empty. ps.subscribe(["requests"]) for message in ps.listen(): populated_queue = message["data"] - if populated_queue in dispatch: - queue, function = dispatch[populated_queue] - for repetition in (1,2): - # This is a potentially inappropriate hack to prevent backlogs - # from accumulating in queues if daemons die or if spurious - # requests arrived while no daemons were listening. The idea - # is that every daemon process double-checks a queue that it - # was told to look at, processing it twice per pubsub message - # instead of once. This causes a pressure on the daemon to - # empty the queue over time even if it isn't explicitly asked - # to. For example, if asked 7 times to process a particular - # queue, a daemon instance would try 14 times. - session = r.rpop(queue) - if session: - if debug: print "going to %s for %s" % (populated_queue, session) - if ancient(session, populated_queue): - if populated_queue == "issue": + if populated_queue == "clean-exit": + pass # fall through to check whether this particular daemon + # instance has its clean_shutdown flag set + else: + while True: + inactive = True + for queue in ("makechallenge", "testchallenge", "issue"): + session = r.rpop("pending-" + queue) + if session: + inactive = False + if debug: print "going to %s for %s" % (queue, session) + if ancient(session, queue): + if queue == "issue": if debug: print "not expiring issue-state", session else: if debug: print "expiring ancient session", session r.hset(session, "live", False) else: - function(session) - elif populated_queue == "clean-exit": - pass # fall through to check whether this particular daemon - # instance has its clean_shutdown flag set - else: - if debug: print "UNKNOWN queue %s" % populated_queue + if queue == "makechallenge": makechallenge(session) + elif queue == "testchallenge": testchallenge(session) + elif queue == "issue": issue(session) + if inactive: + break if clean_shutdown: print "daemon exiting cleanly"