Initial ACME compliant DVSNI commit

This commit is contained in:
James Kasten
2014-11-06 05:37:22 -05:00
parent f3b0ab0db7
commit 80799e28a0
5 changed files with 215 additions and 136 deletions
+3
View File
@@ -33,5 +33,8 @@ difficulty = 23
cert_file = CERT_DIR + "trustify-cert.pem" cert_file = CERT_DIR + "trustify-cert.pem"
chain_file = CERT_DIR + "trustify-chain.pem" chain_file = CERT_DIR + "trustify-chain.pem"
#Invalid Extension
INVALID_EXT = ".acme.invalid"
# Rewrite rule arguments used for redirections to https vhost # Rewrite rule arguments used for redirections to https vhost
REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"] REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
Regular → Executable
+1 -25
View File
@@ -327,7 +327,7 @@ class Client(object):
sys.exit(1) sys.exit(1)
if not self.csr_file: if not self.csr_file:
csr_pem = self.make_csr(self.names) csr_pem = trustify_util.make_csr(self.key_file, self.names)
# Save CSR # Save CSR
trustify_util.make_or_verify_dir(CERT_DIR, 0755) trustify_util.make_or_verify_dir(CERT_DIR, 0755)
csr_f, self.csr_file = trustify_util.unique_file(CERT_DIR + "csr-trustify.pem", 0644) csr_f, self.csr_file = trustify_util.unique_file(CERT_DIR + "csr-trustify.pem", 0644)
@@ -356,30 +356,6 @@ class Client(object):
return key_pem return key_pem
def make_csr(self, domains):
"""
Returns new CSR in PEM form using self.key_file containing all domains
"""
assert domains, "Must provide one or more hostnames for the CSR."
rsa_key = M2Crypto.RSA.load_key(self.key_file)
pk = EVP.PKey()
pk.assign_rsa(rsa_key)
x = X509.Request()
x.set_pubkey(pk)
name = x.get_subject()
name.CN = domains[0]
extstack = X509.X509_Extension_Stack()
for d in domains:
ext = X509.new_extension('subjectAltName', 'DNS:%s' % d)
extstack.push(ext)
x.add_extensions(extstack)
x.sign(pk,'sha1')
assert x.verify(pk)
pk2 = x.get_pubkey()
assert x.verify(pk2)
return x.as_pem()
def __rsa_sign(self, key, data): def __rsa_sign(self, key, data):
""" """
Sign this data with this private key. For client-side use. Sign this data with this private key. For client-side use.
+61 -23
View File
@@ -10,11 +10,12 @@ import shutil
import errno import errno
from trustify.client.CONFIG import SERVER_ROOT, BACKUP_DIR from trustify.client.CONFIG import SERVER_ROOT, BACKUP_DIR
#from CONFIG import SERVER_ROOT, BACKUP_DIR, MODIFIED_FILES, REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
from trustify.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR from trustify.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
from trustify.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR from trustify.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
from trustify.client.CONFIG import OPTIONS_SSL_CONF
from trustify.client import logger, trustify_util from trustify.client import logger, trustify_util
#import logger #from CONFIG import SERVER_ROOT, BACKUP_DIR, REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR, TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR, OPTIONS_SSL_CONF
#import logger, trustify_util
# Question: Am I missing any attacks that can result from modifying CONFIG file? # Question: Am I missing any attacks that can result from modifying CONFIG file?
# Configurator should be turned into a Singleton # Configurator should be turned into a Singleton
@@ -66,6 +67,8 @@ class Configurator(object):
# relevant files - I believe -> NO_MODL_AUTOLOAD # relevant files - I believe -> NO_MODL_AUTOLOAD
# TODO: Use server_root instead SERVER_ROOT # TODO: Use server_root instead SERVER_ROOT
self.server_root = server_root
# Set Augeas flags to save backup # Set Augeas flags to save backup
self.aug = augeas.Augeas(flags=augeas.Augeas.NONE) self.aug = augeas.Augeas(flags=augeas.Augeas.NONE)
@@ -78,6 +81,9 @@ 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
self.__set_user_config_file()
self.save_notes = "" self.save_notes = ""
@@ -88,9 +94,9 @@ class Configurator(object):
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 if it is # by Apache's standards. This should be done by the client (client.py)
# desired. There may be instances where correct configuration isn't # if it is desired. There may be instances where correct configuration
# 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
@@ -217,8 +223,19 @@ class Configurator(object):
return all_names return all_names
def __is_private_ip(ipaddr): def __set_user_config_file(self, filename = ''):
re.compile() if filename:
self.user_config_file = filename
else:
# Basic check to see if httpd.conf exists and is included via direct include
# httpd.conf was very common as a user file in Apache 2.2
if os.path.isfile(self.server_root + 'httpd.conf') and self.find_directive(self.case_i("Include"), self.case_i("httpd.conf")):
self.user_config_file = self.server_root + 'httpd.conf'
else:
self.user_config_file = self.server_root + 'apache2.conf'
#def __is_private_ip(ipaddr):
# re.compile()
def __add_servernames(self, host): def __add_servernames(self, host):
@@ -293,7 +310,7 @@ class Configurator(object):
aug_file_path = "/files%sports.conf" % SERVER_ROOT aug_file_path = "/files%sports.conf" % 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"), 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" + SERVER_ROOT + "httpd.conf", "NameVirtualHost", addr) self.add_dir_to_ifmodssl("/files" + SERVER_ROOT + "httpd.conf", "NameVirtualHost", addr)
@@ -390,6 +407,10 @@ class Configurator(object):
transformation by calling case_i() on everything to maintain transformation by calling case_i() on everything to maintain
compatibility. compatibility.
""" """
#Debug code
#print "find_dir:", directive, "arg:", arg, " | Looking in:", start
# No regexp code
# if arg is None: # if arg is None:
# matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg") # matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg")
# else: # else:
@@ -413,11 +434,15 @@ class Configurator(object):
def case_i(self, string): def case_i(self, string):
""" """
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
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() for c in string]) + ']'
#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
@@ -550,7 +575,7 @@ class Configurator(object):
self.add_dir(vh_p[0], "SSLCertificateFile", "/etc/ssl/certs/ssl-cert-snakeoil.pem") self.add_dir(vh_p[0], "SSLCertificateFile", "/etc/ssl/certs/ssl-cert-snakeoil.pem")
self.add_dir(vh_p[0], "SSLCertificateKeyFile", "/etc/ssl/private/ssl-cert-snakeoil.key") self.add_dir(vh_p[0], "SSLCertificateKeyFile", "/etc/ssl/private/ssl-cert-snakeoil.key")
self.add_dir(vh_p[0], "Include", CONFIG_DIR + "options-ssl.conf") self.add_dir(vh_p[0], "Include", OPTIONS_SSL_CONF)
# Log actions and create save notes # Log actions and create save notes
logger.info("Created an SSL vhost at %s" % ssl_fp) logger.info("Created an SSL vhost at %s" % ssl_fp)
@@ -927,7 +952,12 @@ LogLevel warn \n\
with open(file_list, 'r') as f: with open(file_list, 'r') as f:
filepaths = f.read().splitlines() filepaths = f.read().splitlines()
for fp in filepaths: for fp in filepaths:
os.remove(fp) # Files are registered before they are added... so check to see if file
# exists first
if os.path.isfile(fp):
os.remove(fp)
else:
logger.warn("File: %s - Could not be found to be deleted\nProgram was probably shut down unexpectedly, in which case this is not a problem" % fp)
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)
@@ -975,11 +1005,14 @@ 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
if 'httpd.aug' in self.aug.get(e + '/lens'): lens_path = self.aug.get(e + '/lens')
# As aug.get may return null
if lens_path and 'httpd.aug' in lens_path:
# Strip off /augeas/files and /error # Strip off /augeas/files and /error
logger.error('There has been an error in parsing the file: %s' % e[13:len(e) - 6]) logger.error('There has been an error in parsing the file: %s' % e[13:len(e) - 6])
logger.error(self.aug.get(e + '/message')) logger.error(self.aug.get(e + '/message'))
def revert_challenge_config(self): def revert_challenge_config(self):
""" """
This function should reload the users original configuration files This function should reload the users original configuration files
@@ -1201,15 +1234,17 @@ LogLevel warn \n\
returns: 0 success, 1 Unable to revert, -1 Unable to delete returns: 0 success, 1 Unable to revert, -1 Unable to delete
""" """
try:
with open(cp_dir + "/FILEPATHS") as f: if os.path.isfile(cp_dir + "/FILEPATHS"):
filepaths = f.read().splitlines() try:
for idx, fp in enumerate(filepaths): with open(cp_dir + "/FILEPATHS") as f:
shutil.copy2(cp_dir + '/' + os.path.basename(fp) + '_' + str(idx), fp) filepaths = f.read().splitlines()
except: for idx, fp in enumerate(filepaths):
# This file is required in all checkpoints. shutil.copy2(cp_dir + '/' + os.path.basename(fp) + '_' + str(idx), fp)
logger.error("Unable to recover files from %s" % cp_dir) except:
return 1 # This file is required in all checkpoints.
logger.error("Unable to recover files from %s" % cp_dir)
return 1
# Remove any newly added files if they exist # Remove any newly added files if they exist
self.__remove_contained_files(cp_dir + "/NEW_FILES") self.__remove_contained_files(cp_dir + "/NEW_FILES")
@@ -1315,12 +1350,15 @@ def main():
config = Configurator() config = Configurator()
logger.setLogger(logger.FileLogger(sys.stdout)) logger.setLogger(logger.FileLogger(sys.stdout))
logger.setLogLevel(logger.DEBUG) logger.setLogLevel(logger.DEBUG)
"""
for v in config.vhosts: for v in config.vhosts:
print v.file print v.file
print v.addrs print v.addrs
for name in v.names: for name in v.names:
print name print name
"""
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)
Regular → Executable
+82 -88
View File
@@ -10,32 +10,39 @@ from os import remove, close, path
import sys import sys
import binascii import binascii
import augeas import augeas
import jose
from trustify.client import configurator from trustify.client import configurator
from trustify.client.CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT from trustify.client.CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT
from trustify.client.CONFIG import CHOC_CERT_CONF, OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF from trustify.client.CONFIG import CHOC_CERT_CONF, OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF, INVALID_EXT
from trustify.client.CONFIG import S_SIZE, NONCE_SIZE from trustify.client.CONFIG import S_SIZE, NONCE_SIZE
from trustify.client import logger from trustify.client import logger, trustify_util
from trustify.client.challenge import Challenge from trustify.client.challenge import Challenge
# import configurator
# from CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT
# from CONFIG import CHOC_CERT_CONF, OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF, INVALID_EXT
# from CONFIG import S_SIZE, NONCE_SIZE
# import logger, trustify_util
# from challenge import Challenge
class SNI_Challenge(Challenge): class SNI_Challenge(Challenge):
def __init__(self, sni_todos, req_filepath, key_filepath, config): def __init__(self, sni_todos, key_filepath, config):
''' '''
sni_todos: List of tuples with form (addr, y, nonce, ext_oid) sni_todos: List of tuples with form (addr, r, nonce)
addr (string), y (byte array), nonce (hex string), addr (string), r (base64 string), nonce (hex string)
ext_oid (string)
csr: string - File path to chocolate csr
key: string - File path to key key: string - File path to key
configurator: Configurator obj configurator: Configurator obj
''' '''
self.listSNITuple = sni_todos self.listSNITuple = sni_todos
self.csr = req_filepath
self.key = key_filepath self.key = key_filepath
self.configurator = config self.configurator = config
def getChocCertFile(self, nonce): def getDvsniCertFile(self, nonce):
""" """
Returns standardized name for challenge certificate Returns standardized name for challenge certificate
@@ -46,17 +53,6 @@ class SNI_Challenge(Challenge):
return WORK_DIR + nonce + ".crt" return WORK_DIR + nonce + ".crt"
def findApacheConfigFile(self):
"""
Locates the file path to the user's main apache config
result: returns file path if present
"""
if path.isfile(SERVER_ROOT + "httpd.conf"):
return SERVER_ROOT + "httpd.conf"
logger.error("Unable to find httpd.conf, file does not exist in Apache ServerRoot")
return None
def __getConfigText(self, nonce, ip_addrs, key): def __getConfigText(self, nonce, ip_addrs, key):
""" """
Chocolate virtual server configuration text Chocolate virtual server configuration text
@@ -68,14 +64,14 @@ class SNI_Challenge(Challenge):
result: returns virtual host configuration text result: returns virtual host configuration text
""" """
configText = "<VirtualHost " + " ".join(ip_addrs) + "> \n \ configText = "<VirtualHost " + " ".join(ip_addrs) + "> \n \
ServerName " + nonce + ".chocolate \n \ ServerName " + nonce + INVALID_EXT + " \n \
UseCanonicalName on \n \ UseCanonicalName on \n \
SSLStrictSNIVHostCheck on \n \ SSLStrictSNIVHostCheck on \n \
\n \ \n \
LimitRequestBody 1048576 \n \ LimitRequestBody 1048576 \n \
\n \ \n \
Include " + OPTIONS_SSL_CONF + " \n \ Include " + OPTIONS_SSL_CONF + " \n \
SSLCertificateFile " + self.getChocCertFile(nonce) + " \n \ SSLCertificateFile " + self.getDvsniCertFile(nonce) + " \n \
SSLCertificateKeyFile " + key + " \n \ SSLCertificateKeyFile " + key + " \n \
\n \ \n \
DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
@@ -109,7 +105,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
def checkForApacheConfInclude(self, mainConfig): def checkForApacheConfInclude(self, mainConfig):
""" """
Adds chocolate 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
@@ -120,40 +116,59 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
#print "Including challenge virtual host(s)" #print "Including challenge virtual host(s)"
self.configurator.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF) self.configurator.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF)
def createChallengeCert(self, oid, ext, nonce, csr, key): def createChallengeCert(self, name, ext, nonce, key):
""" """
Modifies challenge certificate configuration and calls openssl binary to create a certificate Modifies challenge certificate configuration and calls openssl binary to create a certificate
oid: string
ext: string - hex z value ext: string - hex z value
nonce: string - hex nonce: string - hex
csr: string - file path to csr
key: string - file path to key key: string - file path to key
result: certificate created at getChocCertFile(nonce) result: certificate created at getChocCertFile(nonce)
""" """
self.createCHOC_CERT_CONF(name, ext)
self.updateCertConf(oid, ext) self.configurator.register_file_creation(True, self.getDvsniCertFile(nonce))
self.configurator.register_file_creation(True, self.getChocCertFile(nonce)) cert_pem = trustify_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext])
subprocess.call(["openssl", "x509", "-req", "-days", "21", "-extfile", CHOC_CERT_CONF, "-extensions", "v3_ca", "-signkey", key, "-out", self.getChocCertFile(nonce), "-in", csr], stdout=open("/dev/null", 'w'), stderr=open("/dev/null", 'w')) with open(self.getDvsniCertFile(nonce), 'w') as f:
f.write(cert_pem)
#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'))
def generateExtension(self, key, y): def createCHOC_CERT_CONF(self, name, ext):
"""
Generates an OpenSSL certificate configuration file
"""
text = " # OpenSSL configuration file. \n\n \
[ v3_ca ] \n \
basicConstraints = CA:TRUE\n\
subjectAltName = @alt_names\n\n\
[ alt_names ]\n"
with open(CHOC_CERT_CONF, 'w') as f:
f.write(text)
f.write("DNS:1 = %s\n" % name)
f.write("DNS:2 = %s\n" % ext)
def generateExtension(self, r, s):
""" """
Generates z to be placed in certificate extension Generates z to be placed in certificate extension
key: string - File path to key r: byte array
y: byte array s: byte array
result: returns z value result: returns z + INVALID_EXT
""" """
h = hashlib.new('sha256')
rsaPrivKey = M2Crypto.RSA.load_key(key) h.update(r)
r = rsaPrivKey.private_decrypt(y, M2Crypto.RSA.pkcs1_oaep_padding) h.update(s)
s = Random.get_random_bytes(S_SIZE) return h.hexdigest() + INVALID_EXT
extHMAC = hmac.new(r, str(s), hashlib.sha256)
return self.byteToHex(s) + extHMAC.hexdigest()
def byteToHex(self, byteStr): def byteToHex(self, byteStr):
""" """
@@ -166,33 +181,6 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
return ''.join(["%02X" % ord(x) for x in byteStr]).strip() return ''.join(["%02X" % ord(x) for x in byteStr]).strip()
#Searches for the first extension specified in binary
def updateCertConf(self, oid, value):
"""
Updates the sni_challenge openssl certificate config file
oid: string - ex. 1.3.3.7
value string hex - value of OID
result: updated certificate config file
"""
confOld = open(CHOC_CERT_CONF)
confNew = open(CHOC_CERT_CONF + ".tmp", 'w')
flag = False
for line in confOld:
if "=critical, DER:" in line:
confNew.write(oid + "=critical, DER:" + value + "\n")
flag = True
else:
confNew.write(line)
if flag is False:
print "Error: Could not find extension in CHOC_CERT_CONF"
exit()
confNew.close()
confOld.close()
remove(CHOC_CERT_CONF)
move(CHOC_CERT_CONF + ".tmp", CHOC_CERT_CONF)
def cleanup(self): def cleanup(self):
""" """
@@ -212,10 +200,8 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
""" """
Sets up and reloads Apache server to handle SNI challenges Sets up and reloads Apache server to handle SNI challenges
listSNITuple: List of tuples with form (addr, y, nonce, ext_oid) listSNITuple: List of tuples with form (addr, r, nonce)
addr (string), y (byte array), nonce (hex string), addr (string), r (base64 string), nonce (hex string)
ext_oid (string)
csr: string - File path to chocolate csr
key: string - File path to key key: string - File path to key
configurator: Configurator obj configurator: Configurator obj
""" """
@@ -231,10 +217,10 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
print "No vhost exists with servername or alias of:", tup[0] print "No vhost exists with servername or alias of:", tup[0]
print "No _default_:443 vhost exists" print "No _default_:443 vhost exists"
print "Please specify servernames in the Apache config" print "Please specify servernames in the Apache config"
return False return None
if not self.configurator.make_server_sni_ready(vhost, default_addr): if not self.configurator.make_server_sni_ready(vhost, default_addr):
return False return None
for a in vhost.addrs: for a in vhost.addrs:
if "_default_" in a: if "_default_" in a:
@@ -243,56 +229,64 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
else: else:
addresses.append(vhost.addrs) addresses.append(vhost.addrs)
# Generate S
s = Random.get_random_bytes(S_SIZE)
# Create all of the challenge certs
for tup in self.listSNITuple: for tup in self.listSNITuple:
ext = self.generateExtension(self.key, tup[1]) # Need to decode from base64
self.createChallengeCert(tup[3], ext, tup[2], self.csr, self.key) r = jose.b64decode_url(tup[1])
ext = self.generateExtension(r, s)
self.createChallengeCert(tup[0], ext, tup[2], self.key)
self.modifyApacheConfig(self.findApacheConfigFile(), addresses) self.modifyApacheConfig(self.configurator.user_config_file, addresses)
# Save reversible changes and restart the server # Save reversible changes and restart the server
self.configurator.save("SNI Challenge", True) self.configurator.save("SNI Challenge", True)
self.configurator.restart(quiet) self.configurator.restart(quiet)
return True return jose.b64encode_url(s)
# This main function is just used for testing # This main function is just used for testing
def main(): def main():
key = path.abspath("key.pem") key = path.abspath("/home/ubuntu/key.pem")
csr = path.abspath("req.pem") csr = path.abspath("/home/ubuntu/req.pem")
logger.setLogger(sys.stdout) logger.setLogger(logger.FileLogger(sys.stdout))
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)
r2 = jose.b64encode_url(r2)
#ans = dns.resolver.query("google.com") #ans = dns.resolver.query("google.com")
#print ans.rrset #print ans.rrset
#return #return
#the second parameter is ignored #the second parameter is ignored
#https://www.dlitz.net/software/pycrypto/api/current/ #https://www.dlitz.net/software/pycrypto/api/current/
y = testkey.public_encrypt(r, M2Crypto.RSA.pkcs1_oaep_padding) #y = testkey.public_encrypt(r, M2Crypto.RSA.pkcs1_oaep_padding)
y2 = testkey.public_encrypt(r2, M2Crypto.RSA.pkcs1_oaep_padding) #y2 = testkey.public_encrypt(r2, M2Crypto.RSA.pkcs1_oaep_padding)
nonce = binascii.hexlify(nonce) nonce = binascii.hexlify(nonce)
nonce2 = binascii.hexlify(nonce2) nonce2 = binascii.hexlify(nonce2)
config = configurator.Configurator() config = configurator.Configurator()
challenges = [("example.com", y, nonce, "1.3.3.7"), ("www.example.com",y2, nonce2, "1.3.3.7")] challenges = [("client.theobroma.info", r, nonce), ("foo.theobroma.info",r2, nonce2)]
#challenges = [("127.0.0.1", y, nonce, "1.3.3.7"), ("localhost", y2, nonce2, "1.3.3.7")] #challenges = [("127.0.0.1", y, nonce, "1.3.3.7"), ("localhost", y2, nonce2, "1.3.3.7")]
sni_chall = SNI_Challenge(challenges, csr, key, config) sni_chall = SNI_Challenge(challenges, key, config)
if sni_chall.perform(): if sni_chall.perform():
# Waste some time without importing time module... just for testing # Waste some time without importing time module... just for testing
for i in range(0, 12000): for i in range(0, 12000):
if i % 2000 == 0: if i % 2000 == 0:
print "Waiting:", i print "Waiting:", i
print "Cleaning up" #print "Cleaning up"
sni_chall.cleanup() #sni_chall.cleanup()
else: else:
print "Failed SNI challenge..." print "Failed SNI challenge..."
+68
View File
@@ -2,8 +2,76 @@
import errno import errno
import stat import stat
import os, pwd, grp import os, pwd, grp
import M2Crypto
import time
from M2Crypto import EVP, X509, RSA, ASN1
from trustify.client import logger from trustify.client import logger
#import logger
def make_csr(key_file, domains):
"""
Returns new CSR in PEM form using key_file containing all domains
"""
assert domains, "Must provide one or more hostnames for the CSR."
rsa_key = M2Crypto.RSA.load_key(key_file)
pk = EVP.PKey()
pk.assign_rsa(rsa_key)
x = X509.Request()
x.set_pubkey(pk)
name = x.get_subject()
name.CN = domains[0]
extstack = X509.X509_Extension_Stack()
for d in domains:
ext = X509.new_extension('subjectAltName', 'DNS:%s' % d)
extstack.push(ext)
x.add_extensions(extstack)
x.sign(pk,'sha256')
assert x.verify(pk)
pk2 = x.get_pubkey()
assert x.verify(pk2)
return x.as_pem()
def make_ss_cert(key_file, domains):
"""
Returns new self-signed cert in PEM form using key_file containing all domains
"""
assert domains, "Must provide one or more hostnames for the CSR."
rsa_key = M2Crypto.RSA.load_key(key_file)
pk = EVP.PKey()
pk.assign_rsa(rsa_key)
x = X509.X509()
x.set_pubkey(pk)
x.set_serial_number(1337)
x.set_version(2)
t = long(time.time())
current = ASN1.ASN1_UTCTIME()
current.set_time(t)
expire = ASN1.ASN1_UTCTIME()
expire.set_time((7 * 24 * 60 * 60) + t)
x.set_not_before(current)
x.set_not_after(expire)
name = x.get_subject()
name.C = "US"
name.ST = "Michigan"
name.L = "Ann Arbor"
name.O = "University of Michigan"
name.OU = "Halderman's Research Group"
name.CN = domains[0]
x.set_issuer(x.get_subject())
x.add_ext(X509.new_extension('subjectAltName', ",".join(["DNS:%s" % d for d in domains])))
x.sign(pk, 'sha1')
assert x.verify(pk)
pk2 = x.get_pubkey()
assert x.verify(pk2)
return x.as_pem()
def make_or_verify_dir(directory, permissions=0755, uid=0): def make_or_verify_dir(directory, permissions=0755, uid=0):
try: try:
os.makedirs(directory, permissions) os.makedirs(directory, permissions)