mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
Added code to auto-configure the Apache server for SNI challenges
This commit is contained in:
@@ -131,7 +131,7 @@ for name in dn:
|
||||
if host is not None:
|
||||
vhost.add(host)
|
||||
|
||||
sni_challenge.perform_sni_cert_challenge(sni_todo, req_file, key_file)
|
||||
sni_challenge.perform_sni_cert_challenge(sni_todo, os.path.abspath(req_file), os.path.abspath(key_file))
|
||||
|
||||
print "waiting", 3
|
||||
time.sleep(3)
|
||||
|
||||
@@ -25,7 +25,7 @@ class Configurator(object):
|
||||
# relevant files
|
||||
# Set Augeas flags to save backup
|
||||
self.aug = augeas.Augeas(None, None, 1 << 0)
|
||||
self.vhosts = []
|
||||
self.vhosts = self.get_virtual_hosts()
|
||||
# httpd_files - All parsable Httpd files
|
||||
# add_transform overwrites all currently loaded files so we must
|
||||
# maintain state
|
||||
@@ -43,6 +43,7 @@ class Configurator(object):
|
||||
the "included" confs. The function verifies that it has located
|
||||
the three directives and finally modifies them to point to the correct
|
||||
destination
|
||||
TODO: Should add/remove chain directives
|
||||
"""
|
||||
search = {}
|
||||
path = {}
|
||||
@@ -92,6 +93,7 @@ class Configurator(object):
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
TODO: Finish this function correctly
|
||||
TODO: This should return vhost of :443 if both 80 and 443 exist
|
||||
This is currently just a very basic demo version
|
||||
"""
|
||||
for v in self.vhosts:
|
||||
@@ -99,6 +101,11 @@ class Configurator(object):
|
||||
# TODO: Or a converted FQDN address
|
||||
if n == name:
|
||||
return v
|
||||
for v in self.vhosts:
|
||||
for a in v.addrs:
|
||||
tup = a.partition(":")
|
||||
if tup[0] == name:
|
||||
return v
|
||||
for v in self.vhosts:
|
||||
for a in v.addrs:
|
||||
if a == "_default_:443":
|
||||
@@ -125,17 +132,18 @@ class Configurator(object):
|
||||
def get_virtual_hosts(self):
|
||||
#Search sites-available, httpd.conf for possible virtual hosts
|
||||
paths = self.aug.match("/files" + BASE_DIR + "sites-available//VirtualHost")
|
||||
vhs = []
|
||||
for p in paths:
|
||||
addrs = []
|
||||
args = self.aug.match(p + "/arg")
|
||||
for arg in args:
|
||||
addrs.append(self.aug.get(arg))
|
||||
self.vhosts.append(VH(p, addrs))
|
||||
vhs.append(VH(p, addrs))
|
||||
|
||||
for host in self.vhosts:
|
||||
for host in vhs:
|
||||
self.add_servernames(host)
|
||||
|
||||
return self.vhosts
|
||||
return vhs
|
||||
|
||||
def is_name_vhost(self, addr):
|
||||
# search for NameVirtualHost directive for ip_addr
|
||||
@@ -197,7 +205,7 @@ class Configurator(object):
|
||||
return False
|
||||
return True
|
||||
|
||||
def make_server_sni_ready(self, addr):
|
||||
def make_server_sni_ready(self, vhost):
|
||||
"""
|
||||
Checks to see if the server is ready for SNI challenges
|
||||
"""
|
||||
@@ -207,15 +215,27 @@ class Configurator(object):
|
||||
return False
|
||||
|
||||
# Check for Listen 443
|
||||
# TODO: This could be made to also look for ip:443 combo
|
||||
# TODO: Need to search only open directives and IfMod mod_ssl.c
|
||||
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")
|
||||
|
||||
# Check for NameVirtualHost
|
||||
if not self.is_name_vhost(addr):
|
||||
print "Setting VirtualHost at", addr, "to be a name based virtual host"
|
||||
self.add_name_vhost(addr)
|
||||
# First see if any of the vhost addresses is a _default_ addr
|
||||
for addr in vhost.addrs:
|
||||
tup = addr.partition(":")
|
||||
if tup[0] == "_default_":
|
||||
if not self.is_name_vhost("*:443"):
|
||||
print "Setting all VirtualHosts on *:443 to be name based virtual hosts"
|
||||
self.add_name_vhost("*:443")
|
||||
return True
|
||||
# No default addresses... so set each one individually
|
||||
for addr in vhost.addrs:
|
||||
if not self.is_name_vhost(addr):
|
||||
print "Setting VirtualHost at", addr, "to be a name based virtual host"
|
||||
self.add_name_vhost(addr)
|
||||
|
||||
return True
|
||||
|
||||
@@ -227,7 +247,10 @@ class Configurator(object):
|
||||
ifMods = self.aug.match(aug_conf_path + "/IfModule/*[self::arg='" + mod + "']")
|
||||
# Strip off "arg" at end of first ifmod path
|
||||
return ifMods[0][:len(ifMods[0]) - 3]
|
||||
|
||||
|
||||
def add_dir(self, aug_conf_path, directive, arg):
|
||||
self.aug.set(aug_conf_path + "/directive[last() + 1]", directive)
|
||||
self.aug.set(aug_conf_path + "/directive[last()]", arg)
|
||||
|
||||
def find_directive(self, directive, arg, start="/files"+BASE_DIR+"apache2.conf"):
|
||||
"""
|
||||
@@ -306,6 +329,7 @@ class Configurator(object):
|
||||
p = subprocess.check_output(["sudo", "apache2ctl", "-M"], stderr=open("/dev/null"))
|
||||
except:
|
||||
print "Error accessing apache2ctl for loaded modules!"
|
||||
print "This may be caused by an Apache Configuration Error"
|
||||
return False
|
||||
if "ssl_module" in p:
|
||||
return True
|
||||
@@ -376,20 +400,19 @@ def recurmatch(aug, path):
|
||||
|
||||
def main():
|
||||
config = Configurator()
|
||||
config.get_virtual_hosts()
|
||||
for v in config.vhosts:
|
||||
for a in v.addrs:
|
||||
for name in v.names:
|
||||
print a, name
|
||||
print v.addrs
|
||||
for name in v.names:
|
||||
print name
|
||||
|
||||
for m in config.find_directive("Listen", "443"):
|
||||
print "Directive Path:", m, "Value:", config.aug.get(m)
|
||||
|
||||
for v in config.vhosts:
|
||||
for a in v.addrs:
|
||||
print a, config.is_name_vhost(a)
|
||||
print "Address:",a, "- Is name vhost?", config.is_name_vhost(a)
|
||||
|
||||
print config.make_server_sni_ready("example.com:443")
|
||||
#print config.make_server_sni_ready("example.com:443")
|
||||
setHost = set()
|
||||
setHost.add(config.choose_virtual_host("example.com"))
|
||||
setHost.add(config.choose_virtual_host("example2.com"))
|
||||
|
||||
@@ -8,8 +8,12 @@ import hashlib
|
||||
from shutil import move
|
||||
from os import remove, close
|
||||
import binascii
|
||||
import augeas
|
||||
import configurator
|
||||
#import dns.resolver
|
||||
|
||||
CHOC_DIR = "/home/ubuntu/chocolate/client-webserver/"
|
||||
#CHOC_DIR = "/home/ubuntu/chocolate/client-webserver/"
|
||||
CHOC_DIR = "/home/james/Documents/apache_choc/"
|
||||
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"
|
||||
@@ -30,6 +34,8 @@ def getChocCertFile(nonce):
|
||||
def findApacheConfigFile():
|
||||
"""
|
||||
Locates the file path to the user's main apache config
|
||||
|
||||
TODO: This needs to be rewritten... should use true ServerRoot
|
||||
|
||||
result: returns file path if present
|
||||
"""
|
||||
@@ -65,14 +71,14 @@ LimitRequestBody 1048576 \n \
|
||||
\n \
|
||||
Include " + OPTIONS_SSL_CONF + " \n \
|
||||
SSLCertificateFile " + getChocCertFile(nonce) + " \n \
|
||||
SSLCertificateKeyFile " + CHOC_DIR + key + " \n \
|
||||
SSLCertificateKeyFile " + key + " \n \
|
||||
\n \
|
||||
DocumentRoot " + CHOC_DIR + "challenge_page/ \n \
|
||||
</VirtualHost> \n\n "
|
||||
|
||||
return configText
|
||||
|
||||
def modifyApacheConfig(mainConfig, listSNITuple, key):
|
||||
def modifyApacheConfig(mainConfig, listSNITuple, key, configurator):
|
||||
"""
|
||||
Modifies Apache config files to include the challenge virtual servers
|
||||
|
||||
@@ -84,36 +90,40 @@ def modifyApacheConfig(mainConfig, listSNITuple, key):
|
||||
result: Apache config includes virtual servers for issued challenges
|
||||
"""
|
||||
|
||||
# TODO: Use ip address of existing vhost instead of relying on FQDN
|
||||
configText = "<IfModule mod_ssl.c> \n"
|
||||
for tup in listSNITuple:
|
||||
configText += getConfigText(tup[2], tup[0], key)
|
||||
configText += "</IfModule> \n"
|
||||
|
||||
checkForApacheConfInclude(mainConfig)
|
||||
checkForApacheConfInclude(mainConfig, configurator)
|
||||
newConf = open(APACHE_CHALLENGE_CONF, 'w')
|
||||
newConf.write(configText)
|
||||
newConf.close()
|
||||
|
||||
# Need to add NameVirtualHost IP_ADDR or does the chocolate install do this?
|
||||
def checkForApacheConfInclude(mainConfig):
|
||||
def checkForApacheConfInclude(mainConfig, configurator):
|
||||
"""
|
||||
Adds chocolate challenge include file if it does not already exist within mainConfig
|
||||
Adds chocolate challenge include file if it does not already exist
|
||||
within mainConfig
|
||||
|
||||
mainConfig: string - file path to main user apache config file
|
||||
|
||||
result: User Apache configuration includes chocolate sni challenge file
|
||||
"""
|
||||
|
||||
searchStr = "Include " + APACHE_CHALLENGE_CONF
|
||||
if len(configurator.find_directive("Include", APACHE_CHALLENGE_CONF)) == 0:
|
||||
configurator.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF)
|
||||
#searchStr = "Include " + APACHE_CHALLENGE_CONF
|
||||
|
||||
#conf = open(mainConfig, 'r+')
|
||||
conf = open(mainConfig, 'r')
|
||||
if not any(line.startswith(searchStr) for line in conf):
|
||||
#conf = open(mainConfig, 'r')
|
||||
#if not any(line.startswith(searchStr) for line in conf):
|
||||
#conf.write(searchStr)
|
||||
process = subprocess.Popen(["echo", "\n" + searchStr], stdout=subprocess.PIPE)
|
||||
subprocess.check_output(["sudo", "tee", "-a", mainConfig], stdin=process.stdout)
|
||||
process.stdout.close()
|
||||
#process = subprocess.Popen(["echo", "\n" + searchStr], stdout=subprocess.PIPE)
|
||||
#subprocess.check_output(["sudo", "tee", "-a", mainConfig], stdin=process.stdout)
|
||||
#process.stdout.close()
|
||||
|
||||
conf.close()
|
||||
#conf.close()
|
||||
|
||||
|
||||
def createChallengeCert(oid, ext, nonce, csr, key):
|
||||
@@ -142,6 +152,7 @@ def generateExtension(key, y):
|
||||
|
||||
result: returns z value
|
||||
"""
|
||||
|
||||
rsaPrivKey = M2Crypto.RSA.load_key(key)
|
||||
r = rsaPrivKey.private_decrypt(y, M2Crypto.RSA.pkcs1_oaep_padding)
|
||||
#print r
|
||||
@@ -159,6 +170,7 @@ def byteToHex(byteStr):
|
||||
|
||||
result: returns hex representation of byteStr
|
||||
"""
|
||||
|
||||
return ''.join(["%02X" % ord(x) for x in byteStr]).strip()
|
||||
|
||||
#Searches for the first extension specified in binary
|
||||
@@ -171,6 +183,7 @@ def updateCertConf(oid, value):
|
||||
|
||||
result: updated certificate config file
|
||||
"""
|
||||
|
||||
confOld = open(CHOC_CERT_CONF)
|
||||
confNew = open(CHOC_CERT_CONF + ".tmp", 'w')
|
||||
flag = False
|
||||
@@ -195,21 +208,34 @@ def apache_restart():
|
||||
subprocess.call(["sudo", "/etc/init.d/apache2", "reload"])
|
||||
|
||||
#main call
|
||||
def perform_sni_cert_challenge(listSNITuple, csr, key):
|
||||
def perform_sni_cert_challenge(listSNITuple, csr, key, configurator):
|
||||
"""
|
||||
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)
|
||||
addr (string), y (byte array), nonce (hex string),
|
||||
ext_oid (string)
|
||||
csr: string - File path to chocolate csr
|
||||
key: string - File path to key
|
||||
configurator: Configurator obj
|
||||
"""
|
||||
|
||||
|
||||
for tup in listSNITuple:
|
||||
vhost = configurator.choose_virtual_host(tup[0])
|
||||
if vhost is None:
|
||||
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
|
||||
|
||||
if not configurator.make_server_sni_ready(vhost):
|
||||
return False
|
||||
|
||||
for tup in listSNITuple:
|
||||
ext = generateExtension(key, tup[1])
|
||||
createChallengeCert(tup[3], ext, tup[2], csr, key)
|
||||
|
||||
modifyApacheConfig(findApacheConfigFile(), listSNITuple, key)
|
||||
modifyApacheConfig(findApacheConfigFile(), listSNITuple, key, configurator)
|
||||
apache_restart()
|
||||
|
||||
def main():
|
||||
@@ -224,7 +250,10 @@ def main():
|
||||
nonce = "nonce"
|
||||
r2 = "testValueForR2"
|
||||
nonce2 = "nonce2"
|
||||
|
||||
|
||||
#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)
|
||||
@@ -232,8 +261,11 @@ def main():
|
||||
|
||||
nonce = binascii.hexlify(nonce)
|
||||
nonce2 = binascii.hexlify(nonce2)
|
||||
|
||||
config = configurator.Configurator()
|
||||
|
||||
perform_sni_cert_challenge([("example.com", y, nonce, "1.3.3.7"), ("www.example.com",y2, nonce2, "1.3.3.7")], csr, key)
|
||||
#perform_sni_cert_challenge([("example.com", y, nonce, "1.3.3.7"), ("www.example.com",y2, nonce2, "1.3.3.7")], csr, key, config)
|
||||
perform_sni_cert_challenge([("127.0.0.1", y, nonce, "1.3.3.7"), ("localhost", y2, nonce2, "1.3.3.7")], csr, key, config)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -59,10 +59,12 @@ def verify_challenge(address, r, nonce, socksify=False):
|
||||
M2Crypto.SSL.Connection.postConnectionCheck = None
|
||||
|
||||
conn = M2Crypto.SSL.Connection(context)
|
||||
|
||||
if socksify:
|
||||
socksocket = socks.socksocket()
|
||||
socksocket.setproxy(socks.PROXY_TYPE_SOCKS4, "localhost", 9050)
|
||||
conn.socket = socksocket
|
||||
|
||||
sni_support.set_sni_ext(conn.ssl, sni_name)
|
||||
try:
|
||||
conn.connect((address, 443))
|
||||
@@ -104,9 +106,11 @@ def main():
|
||||
nonce = binascii.hexlify(nonce)
|
||||
nonce2 = binascii.hexlify(nonce2)
|
||||
|
||||
valid, response = verify_challenge("example.com", r, "33947bb5dd81f17f67305cb90aa5b8b5e95442e8ed4e78567092a63d04eb3db4")
|
||||
valid, response = verify_challenge("example.com", r, nonce)
|
||||
#valid, response = verify_challenge("127.0.0.1", r, nonce)
|
||||
print response
|
||||
valid, response = verify_challenge("www.example.com", r2, "no123809214unce2")
|
||||
valid, response = verify_challenge("www.example.com", r2, nonce2)
|
||||
#valid, response = verify_challenge("localhost", r2, nonce2)
|
||||
print response
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user