Merge branch 'master' of github.com:research/chocolate

This commit is contained in:
Peter Eckersley
2012-08-08 17:39:55 -07:00
5 changed files with 83 additions and 73 deletions
+19
View File
@@ -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
+2 -1
View File
@@ -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()
+14 -14
View File
@@ -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 + "/*")
+27 -31
View File
@@ -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 \
</VirtualHost> \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()
+21 -27
View File
@@ -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"