mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
Initial ACME compliant DVSNI commit
This commit is contained in:
@@ -33,5 +33,8 @@ difficulty = 23
|
||||
cert_file = CERT_DIR + "trustify-cert.pem"
|
||||
chain_file = CERT_DIR + "trustify-chain.pem"
|
||||
|
||||
#Invalid Extension
|
||||
INVALID_EXT = ".acme.invalid"
|
||||
|
||||
# Rewrite rule arguments used for redirections to https vhost
|
||||
REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
||||
|
||||
26
trustify/client/client.py
Normal file → Executable file
26
trustify/client/client.py
Normal file → Executable file
@@ -327,7 +327,7 @@ class Client(object):
|
||||
sys.exit(1)
|
||||
|
||||
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
|
||||
trustify_util.make_or_verify_dir(CERT_DIR, 0755)
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
Sign this data with this private key. For client-side use.
|
||||
|
||||
@@ -10,11 +10,12 @@ import shutil
|
||||
import errno
|
||||
|
||||
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 TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from trustify.client.CONFIG import OPTIONS_SSL_CONF
|
||||
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?
|
||||
# Configurator should be turned into a Singleton
|
||||
@@ -66,6 +67,8 @@ class Configurator(object):
|
||||
# relevant files - I believe -> NO_MODL_AUTOLOAD
|
||||
# TODO: Use server_root instead SERVER_ROOT
|
||||
|
||||
self.server_root = server_root
|
||||
|
||||
# Set Augeas flags to save backup
|
||||
self.aug = augeas.Augeas(flags=augeas.Augeas.NONE)
|
||||
|
||||
@@ -78,6 +81,9 @@ class Configurator(object):
|
||||
self.check_parsing_errors()
|
||||
# This problem has been fixed in Augeas 1.0
|
||||
self.standardize_excl()
|
||||
|
||||
# Determine user's main config file
|
||||
self.__set_user_config_file()
|
||||
|
||||
self.save_notes = ""
|
||||
|
||||
@@ -88,9 +94,9 @@ class Configurator(object):
|
||||
self.verify_setup()
|
||||
|
||||
# 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
|
||||
# desired. There may be instances where correct configuration isn't
|
||||
# required on startup.
|
||||
# by Apache's standards. This should be done by the client (client.py)
|
||||
# if it is desired. There may be instances where correct configuration
|
||||
# isn't required on startup.
|
||||
|
||||
# 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
|
||||
@@ -217,8 +223,19 @@ class Configurator(object):
|
||||
|
||||
return all_names
|
||||
|
||||
def __is_private_ip(ipaddr):
|
||||
re.compile()
|
||||
def __set_user_config_file(self, filename = ''):
|
||||
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):
|
||||
@@ -293,7 +310,7 @@ class Configurator(object):
|
||||
aug_file_path = "/files%sports.conf" % SERVER_ROOT
|
||||
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("Adding NameVirtualHost directive to httpd.conf")
|
||||
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
|
||||
compatibility.
|
||||
"""
|
||||
|
||||
#Debug code
|
||||
#print "find_dir:", directive, "arg:", arg, " | Looking in:", start
|
||||
# No regexp code
|
||||
# if arg is None:
|
||||
# matches = self.aug.match(start + "//*[self::directive='"+directive+"']/arg")
|
||||
# else:
|
||||
@@ -413,11 +434,15 @@ class Configurator(object):
|
||||
def case_i(self, string):
|
||||
"""
|
||||
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
|
||||
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):
|
||||
"""
|
||||
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], "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
|
||||
logger.info("Created an SSL vhost at %s" % ssl_fp)
|
||||
@@ -927,7 +952,12 @@ LogLevel warn \n\
|
||||
with open(file_list, 'r') as f:
|
||||
filepaths = f.read().splitlines()
|
||||
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:
|
||||
logger.fatal("Unable to remove filepaths contained within %s" % file_list)
|
||||
sys.exit(41)
|
||||
@@ -975,11 +1005,14 @@ LogLevel warn \n\
|
||||
for e in error_files:
|
||||
# Check to see if it was an error resulting from the use of
|
||||
# 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
|
||||
logger.error('There has been an error in parsing the file: %s' % e[13:len(e) - 6])
|
||||
logger.error(self.aug.get(e + '/message'))
|
||||
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
try:
|
||||
with open(cp_dir + "/FILEPATHS") as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for idx, fp in enumerate(filepaths):
|
||||
shutil.copy2(cp_dir + '/' + os.path.basename(fp) + '_' + str(idx), fp)
|
||||
except:
|
||||
# This file is required in all checkpoints.
|
||||
logger.error("Unable to recover files from %s" % cp_dir)
|
||||
return 1
|
||||
|
||||
if os.path.isfile(cp_dir + "/FILEPATHS"):
|
||||
try:
|
||||
with open(cp_dir + "/FILEPATHS") as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for idx, fp in enumerate(filepaths):
|
||||
shutil.copy2(cp_dir + '/' + os.path.basename(fp) + '_' + str(idx), fp)
|
||||
except:
|
||||
# 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
|
||||
self.__remove_contained_files(cp_dir + "/NEW_FILES")
|
||||
@@ -1315,12 +1350,15 @@ def main():
|
||||
config = Configurator()
|
||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
||||
logger.setLogLevel(logger.DEBUG)
|
||||
"""
|
||||
for v in config.vhosts:
|
||||
print v.file
|
||||
print v.addrs
|
||||
for name in v.names:
|
||||
print name
|
||||
|
||||
"""
|
||||
print config.find_directive(config.case_i("NameVirtualHost"), config.case_i("holla:443"))
|
||||
|
||||
"""
|
||||
for m in config.find_directive("Listen", "443"):
|
||||
print "Directive Path:", m, "Value:", config.aug.get(m)
|
||||
|
||||
170
trustify/client/sni_challenge.py
Normal file → Executable file
170
trustify/client/sni_challenge.py
Normal file → Executable file
@@ -10,32 +10,39 @@ from os import remove, close, path
|
||||
import sys
|
||||
import binascii
|
||||
import augeas
|
||||
import jose
|
||||
|
||||
from trustify.client import configurator
|
||||
|
||||
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 import logger
|
||||
from trustify.client import logger, trustify_util
|
||||
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):
|
||||
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)
|
||||
addr (string), y (byte array), nonce (hex string),
|
||||
ext_oid (string)
|
||||
csr: string - File path to chocolate csr
|
||||
sni_todos: List of tuples with form (addr, r, nonce)
|
||||
addr (string), r (base64 string), nonce (hex string)
|
||||
key: string - File path to key
|
||||
configurator: Configurator obj
|
||||
'''
|
||||
self.listSNITuple = sni_todos
|
||||
self.csr = req_filepath
|
||||
self.key = key_filepath
|
||||
self.configurator = config
|
||||
|
||||
|
||||
def getChocCertFile(self, nonce):
|
||||
def getDvsniCertFile(self, nonce):
|
||||
"""
|
||||
Returns standardized name for challenge certificate
|
||||
|
||||
@@ -46,17 +53,6 @@ class SNI_Challenge(Challenge):
|
||||
|
||||
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):
|
||||
"""
|
||||
Chocolate virtual server configuration text
|
||||
@@ -68,14 +64,14 @@ class SNI_Challenge(Challenge):
|
||||
result: returns virtual host configuration text
|
||||
"""
|
||||
configText = "<VirtualHost " + " ".join(ip_addrs) + "> \n \
|
||||
ServerName " + nonce + ".chocolate \n \
|
||||
ServerName " + nonce + INVALID_EXT + " \n \
|
||||
UseCanonicalName on \n \
|
||||
SSLStrictSNIVHostCheck on \n \
|
||||
\n \
|
||||
LimitRequestBody 1048576 \n \
|
||||
\n \
|
||||
Include " + OPTIONS_SSL_CONF + " \n \
|
||||
SSLCertificateFile " + self.getChocCertFile(nonce) + " \n \
|
||||
SSLCertificateFile " + self.getDvsniCertFile(nonce) + " \n \
|
||||
SSLCertificateKeyFile " + key + " \n \
|
||||
\n \
|
||||
DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
@@ -109,7 +105,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
|
||||
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
|
||||
|
||||
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)"
|
||||
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
|
||||
|
||||
oid: string
|
||||
ext: string - hex z value
|
||||
nonce: string - hex
|
||||
csr: string - file path to csr
|
||||
key: string - file path to key
|
||||
|
||||
result: certificate created at getChocCertFile(nonce)
|
||||
"""
|
||||
self.createCHOC_CERT_CONF(name, ext)
|
||||
|
||||
self.updateCertConf(oid, ext)
|
||||
self.configurator.register_file_creation(True, self.getChocCertFile(nonce))
|
||||
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'))
|
||||
self.configurator.register_file_creation(True, self.getDvsniCertFile(nonce))
|
||||
cert_pem = trustify_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext])
|
||||
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
|
||||
|
||||
key: string - File path to key
|
||||
y: byte array
|
||||
r: byte array
|
||||
s: byte array
|
||||
|
||||
result: returns z value
|
||||
result: returns z + INVALID_EXT
|
||||
"""
|
||||
|
||||
rsaPrivKey = M2Crypto.RSA.load_key(key)
|
||||
r = rsaPrivKey.private_decrypt(y, M2Crypto.RSA.pkcs1_oaep_padding)
|
||||
|
||||
s = Random.get_random_bytes(S_SIZE)
|
||||
extHMAC = hmac.new(r, str(s), hashlib.sha256)
|
||||
return self.byteToHex(s) + extHMAC.hexdigest()
|
||||
h = hashlib.new('sha256')
|
||||
h.update(r)
|
||||
h.update(s)
|
||||
|
||||
return h.hexdigest() + INVALID_EXT
|
||||
|
||||
def byteToHex(self, byteStr):
|
||||
"""
|
||||
@@ -166,33 +181,6 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -212,10 +200,8 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
"""
|
||||
Sets up and reloads Apache server to handle SNI challenges
|
||||
|
||||
listSNITuple: List of tuples with form (addr, y, nonce, ext_oid)
|
||||
addr (string), y (byte array), nonce (hex string),
|
||||
ext_oid (string)
|
||||
csr: string - File path to chocolate csr
|
||||
listSNITuple: List of tuples with form (addr, r, nonce)
|
||||
addr (string), r (base64 string), nonce (hex string)
|
||||
key: string - File path to key
|
||||
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 _default_:443 vhost exists"
|
||||
print "Please specify servernames in the Apache config"
|
||||
return False
|
||||
return None
|
||||
|
||||
if not self.configurator.make_server_sni_ready(vhost, default_addr):
|
||||
return False
|
||||
return None
|
||||
|
||||
for a in vhost.addrs:
|
||||
if "_default_" in a:
|
||||
@@ -243,56 +229,64 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
else:
|
||||
addresses.append(vhost.addrs)
|
||||
|
||||
# Generate S
|
||||
s = Random.get_random_bytes(S_SIZE)
|
||||
# Create all of the challenge certs
|
||||
for tup in self.listSNITuple:
|
||||
ext = self.generateExtension(self.key, tup[1])
|
||||
self.createChallengeCert(tup[3], ext, tup[2], self.csr, self.key)
|
||||
# Need to decode from base64
|
||||
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
|
||||
self.configurator.save("SNI Challenge", True)
|
||||
self.configurator.restart(quiet)
|
||||
return True
|
||||
return jose.b64encode_url(s)
|
||||
|
||||
# This main function is just used for testing
|
||||
def main():
|
||||
key = path.abspath("key.pem")
|
||||
csr = path.abspath("req.pem")
|
||||
logger.setLogger(sys.stdout)
|
||||
key = path.abspath("/home/ubuntu/key.pem")
|
||||
csr = path.abspath("/home/ubuntu/req.pem")
|
||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
||||
logger.setLogLevel(logger.INFO)
|
||||
|
||||
testkey = M2Crypto.RSA.load_key(key)
|
||||
|
||||
r = Random.get_random_bytes(S_SIZE)
|
||||
#r = Random.get_random_bytes(S_SIZE)
|
||||
r = "testValueForR"
|
||||
nonce = Random.get_random_bytes(NONCE_SIZE)
|
||||
#nonce = Random.get_random_bytes(NONCE_SIZE)
|
||||
nonce = "nonce"
|
||||
r2 = "testValueForR2"
|
||||
nonce2 = "nonce2"
|
||||
|
||||
r = jose.b64encode_url(r)
|
||||
r2 = jose.b64encode_url(r2)
|
||||
|
||||
#ans = dns.resolver.query("google.com")
|
||||
#print ans.rrset
|
||||
#return
|
||||
#the second parameter is ignored
|
||||
#https://www.dlitz.net/software/pycrypto/api/current/
|
||||
y = testkey.public_encrypt(r, M2Crypto.RSA.pkcs1_oaep_padding)
|
||||
y2 = testkey.public_encrypt(r2, 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)
|
||||
|
||||
nonce = binascii.hexlify(nonce)
|
||||
nonce2 = binascii.hexlify(nonce2)
|
||||
|
||||
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")]
|
||||
sni_chall = SNI_Challenge(challenges, csr, key, config)
|
||||
sni_chall = SNI_Challenge(challenges, key, config)
|
||||
if sni_chall.perform():
|
||||
# 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"
|
||||
sni_chall.cleanup()
|
||||
#print "Cleaning up"
|
||||
#sni_chall.cleanup()
|
||||
else:
|
||||
print "Failed SNI challenge..."
|
||||
|
||||
|
||||
@@ -2,8 +2,76 @@
|
||||
import errno
|
||||
import stat
|
||||
import os, pwd, grp
|
||||
import M2Crypto
|
||||
import time
|
||||
from M2Crypto import EVP, X509, RSA, ASN1
|
||||
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):
|
||||
try:
|
||||
os.makedirs(directory, permissions)
|
||||
|
||||
Reference in New Issue
Block a user