diff --git a/letsencrypt/client/apache_configurator.py b/letsencrypt/client/apache_configurator.py
index dcf5ac9ef..4d6879dbb 100644
--- a/letsencrypt/client/apache_configurator.py
+++ b/letsencrypt/client/apache_configurator.py
@@ -1052,8 +1052,181 @@ LogLevel warn \n\
return True
+ ###########################################################################
+ # Challenges Section
+ ###########################################################################
+
+ def perform(self, chall_type, tup):
+ if chall_type == 'dvsni':
+ return dvsni_perform(tup)
+ return None
+
+ def dvsni_perform(self, tup):
+ """
+ Sets up and reloads Apache server to handle SNI challenges
+
+ 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
+ """
+ # Save any changes to the configuration as a precaution
+ # About to make temporary changes to the config
+ self.save()
+
+ if len(tup) != 2:
+ logger.fatal("Incorrect parameter given to Apache DVSNI challenge")
+ sys.exit(1)
+
+ listSNITuple = tup[0]
+ dvsni_key = tup[1]
+
+ addresses = []
+ default_addr = "*:443"
+ for tup in listSNITuple:
+ vhost = self.choose_virtual_host(tup[0])
+ if vhost is None:
+ logger.error("No vhost exists with servername or alias of:%s" % tup[0])
+ logger.error("No _default_:443 vhost exists")
+ logger.error("Please specify servernames in the Apache config")
+ return None
+
+ # TODO - @jdkasten review this code to make sure it makes sense
+ if not self.make_server_sni_ready(vhost, default_addr):
+ return None
+
+ for a in vhost.addrs:
+ if "_default_" in a:
+ addresses.append([default_addr])
+ break
+ else:
+ addresses.append(vhost.addrs)
+
+ # Generate S
+ s = Random.get_random_bytes(S_SIZE)
+ # Create all of the challenge certs
+ for t in listSNITuple:
+ # Need to decode from base64
+ r = le_util.b64_url_dec(t[1])
+ ext = self.generateExtension(r, s)
+ self.createChallengeCert(t[0], ext, t[2], dvsni_key)
+
+ self.dvsni_mod_config(self.user_config_file, listSNITuple, addresses)
+ # Save reversible changes and restart the server
+ self.save("SNI Challenge", True)
+ self.restart(quiet)
+
+ s = le_util.b64_url_enc(s)
+ return {"type":"dvsni", "s":s}
+
+ def cleanup(self):
+ self.revert_challenge_config()
+ self.restart(True)
+ def dvsni_get_cert_file(self, nonce):
+ """
+ Returns standardized name for challenge certificate
+ nonce: string - hex
+ result: returns certificate file name
+ """
+ return WORK_DIR + nonce + ".crt"
+
+ def __getConfigText(self, nonce, ip_addrs, key):
+ """
+ Chocolate virtual server configuration text
+
+ nonce: string - hex
+ ip_addr: string - address of challenged domain
+ key: string - file path to key
+
+ result: returns virtual host configuration text
+ """
+ configText = " \n \
+ServerName " + nonce + INVALID_EXT + " \n \
+UseCanonicalName on \n \
+SSLStrictSNIVHostCheck on \n \
+\n \
+LimitRequestBody 1048576 \n \
+\n \
+Include " + OPTIONS_SSL_CONF + " \n \
+SSLCertificateFile " + self.dvsni_get_cert_file(nonce) + " \n \
+SSLCertificateKeyFile " + key + " \n \
+\n \
+DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
+ \n\n "
+
+ return configText
+
+ def dvsni_mod_config(self, mainConfig, listSNITuple, listlistAddrs):
+ """
+ Modifies Apache config files to include the challenge virtual servers
+
+ mainConfig: string - file path to Apache user config file
+ listSNITuple: list of tuples with form (addr, y, nonce, ext_oid)
+ addr (string), y (byte array), nonce (hex string), ext_oid (string)
+ key: string - file path to key
+
+ result: Apache config includes virtual servers for issued challenges
+ """
+
+ # TODO: Use ip address of existing vhost instead of relying on FQDN
+ configText = " \n"
+ for idx, lis in enumerate(listlistAddrs):
+ configText += self.__getConfigText(listSNITuple[idx][2], lis, self.key)
+ configText += " \n"
+
+ self.dvsni_conf_include_check(mainConfig)
+ self.register_file_creation(True, APACHE_CHALLENGE_CONF)
+ newConf = open(APACHE_CHALLENGE_CONF, 'w')
+ newConf.write(configText)
+ newConf.close()
+
+
+
+ def dvsni_conf_include_check(self, mainConfig):
+ """
+ Adds DVSNI 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
+ """
+ if len(self.find_directive(self.case_i("Include"), APACHE_CHALLENGE_CONF)) == 0:
+ #print "Including challenge virtual host(s)"
+ self.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF)
+
+ def createChallengeCert(self, name, ext, nonce, key):
+ """
+ Modifies challenge certificate configuration and calls openssl binary to create a certificate
+
+ ext: string - hex z value
+ nonce: string - hex
+ key: string - file path to key
+
+ result: certificate created at dvsni_get_cert_file(nonce)
+ """
+
+ self.register_file_creation(True, self.dvsni_get_cert_file(nonce))
+ cert_pem = crypto_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext])
+ with open(self.dvsni_get_cert_file(nonce), 'w') as f:
+ f.write(cert_pem)
+
+ def dvsni_gen_ext(self, r, s):
+ """
+ Generates z to be placed in certificate extension
+
+ r: byte array
+ s: byte array
+
+ result: returns z + INVALID_EXT
+ """
+ h = hashlib.new('sha256')
+ h.update(r)
+ h.update(s)
+
+ return h.hexdigest() + INVALID_EXT
def main():
config = Configurator()
diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py
index b9c06b49a..af12dfffe 100644
--- a/letsencrypt/client/augeas_configurator.py
+++ b/letsencrypt/client/augeas_configurator.py
@@ -1,6 +1,8 @@
import abc
from abc_base import Configurator
+import augeas
+
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
class AugeasConfigurator(Configurator):
diff --git a/letsencrypt/client/sni_challenge.py b/letsencrypt/client/sni_challenge.py
deleted file mode 100755
index 8170f714a..000000000
--- a/letsencrypt/client/sni_challenge.py
+++ /dev/null
@@ -1,289 +0,0 @@
-#!/usr/bin/env python
-
-import M2Crypto
-from Crypto import Random
-import hashlib
-from os import path
-import sys
-import binascii
-
-from letsencrypt.client import apache_configurator
-
-from letsencrypt.client.CONFIG import CONFIG_DIR, WORK_DIR, SERVER_ROOT
-from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, APACHE_CHALLENGE_CONF, INVALID_EXT
-from letsencrypt.client.CONFIG import S_SIZE
-from letsencrypt.client import logger, crypto_util, le_util
-from letsencrypt.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, le_util
-# from challenge import Challenge
-
-
-class SNI_Challenge(Challenge):
- def __init__(self, sni_todos, key_filepath, config):
- '''
- 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.key = key_filepath
- self.configurator = config
- self.s = None
-
-
- def getDvsniCertFile(self, nonce):
- """
- Returns standardized name for challenge certificate
-
- nonce: string - hex
-
- result: returns certificate file name
- """
-
- 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
-
- nonce: string - hex
- ip_addr: string - address of challenged domain
- key: string - file path to key
-
- result: returns virtual host configuration text
- """
- configText = " \n \
-ServerName " + nonce + INVALID_EXT + " \n \
-UseCanonicalName on \n \
-SSLStrictSNIVHostCheck on \n \
-\n \
-LimitRequestBody 1048576 \n \
-\n \
-Include " + OPTIONS_SSL_CONF + " \n \
-SSLCertificateFile " + self.getDvsniCertFile(nonce) + " \n \
-SSLCertificateKeyFile " + key + " \n \
-\n \
-DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
- \n\n "
-
- return configText
-
- def modifyApacheConfig(self, mainConfig, listlistAddrs):
- """
- Modifies Apache config files to include the challenge virtual servers
-
- mainConfig: string - file path to Apache user config file
- listSNITuple: list of tuples with form (addr, y, nonce, ext_oid)
- addr (string), y (byte array), nonce (hex string), ext_oid (string)
- key: string - file path to key
-
- result: Apache config includes virtual servers for issued challenges
- """
-
- # TODO: Use ip address of existing vhost instead of relying on FQDN
- configText = " \n"
- for idx, lis in enumerate(listlistAddrs):
- configText += self.__getConfigText(self.listSNITuple[idx][2], lis, self.key)
- configText += " \n"
-
- self.checkForApacheConfInclude(mainConfig)
- self.configurator.register_file_creation(True, APACHE_CHALLENGE_CONF)
- newConf = open(APACHE_CHALLENGE_CONF, 'w')
- newConf.write(configText)
- newConf.close()
-
- def checkForApacheConfInclude(self, mainConfig):
- """
- Adds DVSNI 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
- """
- if len(self.configurator.find_directive(self.configurator.case_i("Include"), APACHE_CHALLENGE_CONF)) == 0:
- #print "Including challenge virtual host(s)"
- self.configurator.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF)
-
- def createChallengeCert(self, name, ext, nonce, key):
- """
- Modifies challenge certificate configuration and calls openssl binary to create a certificate
-
- ext: string - hex z value
- nonce: string - hex
- key: string - file path to key
-
- result: certificate created at getDvsniCertFile(nonce)
- """
-
- self.configurator.register_file_creation(True, self.getDvsniCertFile(nonce))
- cert_pem = crypto_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext])
- with open(self.getDvsniCertFile(nonce), 'w') as f:
- f.write(cert_pem)
-
- def generateExtension(self, r, s):
- """
- Generates z to be placed in certificate extension
-
- r: byte array
- s: byte array
-
- result: returns z + INVALID_EXT
- """
- h = hashlib.new('sha256')
- h.update(r)
- h.update(s)
-
- return h.hexdigest() + INVALID_EXT
-
- def byteToHex(self, byteStr):
- """
- Converts binary array to hex string
-
- byteStr: byte array
-
- result: returns hex representation of byteStr
- """
-
- return ''.join(["%02X" % ord(x) for x in byteStr]).strip()
-
-
- def cleanup(self):
- """
- Remove all temporary changes necessary to perform the challenge
-
- configurator: Configurator object
- listSNITuple: The initial challenge tuple
-
- result: Apache server is restored to the pre-challenge state
- """
- self.configurator.revert_challenge_config()
- self.configurator.restart(True)
-
- def generate_response(self):
- """
- Generates a response for a completed challenge
- """
- if self.s:
- return {"type":"dvsni", "s":self.s}
-
- logger.error("DVSNI Challenge was not completed before calling generate_response")
- return None
-
- #main call
- def perform(self, quiet=False):
- """
- Sets up and reloads Apache server to handle SNI challenges
-
- 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
- """
- # Save any changes to the configuration as a precaution
- # About to make temporary changes to the config
- self.configurator.save()
-
- addresses = []
- default_addr = "*:443"
- for tup in self.listSNITuple:
- vhost = self.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 None
-
- if not self.configurator.make_server_sni_ready(vhost, default_addr):
- return None
-
- for a in vhost.addrs:
- if "_default_" in a:
- addresses.append([default_addr])
- break
- 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:
- # Need to decode from base64
- r = le_util.b64_url_dec(tup[1])
- ext = self.generateExtension(r, s)
- self.createChallengeCert(tup[0], ext, tup[2], self.key)
-
- 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)
-
- self.s = le_util.b64_url_enc(s)
- return self.s
-
-# This main function is just used for testing
-def main():
- 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 = "testValueForR"
- #nonce = Random.get_random_bytes(NONCE_SIZE)
- nonce = "nonce"
- r2 = "testValueForR2"
- nonce2 = "nonce2"
-
- r = le_util.b64_url_enc(r)
- r2 = le_util.b64_url_enc(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)
-
- nonce = binascii.hexlify(nonce)
- nonce2 = binascii.hexlify(nonce2)
-
- config = configurator.Configurator()
-
- 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, 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()
- else:
- print "Failed SNI challenge..."
-
-if __name__ == "__main__":
- main()