mirror of
https://github.com/certbot/certbot.git
synced 2026-06-06 07:12:54 -04:00
Merge branch 'configurator_refactor', Enable modular configuration
editing to support development for other webservers.
This commit is contained in:
commit
f64570c5db
8 changed files with 2086 additions and 1740 deletions
|
|
@ -55,5 +55,8 @@ CHALLENGE_PREFERENCES = ["dvsni", "recoveryToken"]
|
|||
# Mutually Exclusive Challenges - only solve 1
|
||||
EXCLUSIVE_CHALLENGES = [set(["dvsni", "simpleHttps"])]
|
||||
|
||||
# These are challenges that must be solved by a Configurator object
|
||||
CONFIG_CHALLENGES = {"dvsni", "simpleHttps"}
|
||||
|
||||
# Rewrite rule arguments used for redirections to https vhost
|
||||
REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
||||
|
|
|
|||
1298
letsencrypt/client/apache_configurator.py
Normal file
1298
letsencrypt/client/apache_configurator.py
Normal file
File diff suppressed because it is too large
Load diff
424
letsencrypt/client/augeas_configurator.py
Normal file
424
letsencrypt/client/augeas_configurator.py
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
import abc, os, sys, shutil, time
|
||||
from letsencrypt.client.configurator import Configurator
|
||||
|
||||
import augeas
|
||||
from letsencrypt.client import le_util, logger
|
||||
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from letsencrypt.client.CONFIG import BACKUP_DIR
|
||||
|
||||
class AugeasConfigurator(Configurator):
|
||||
|
||||
def __init__(self):
|
||||
super(AugeasConfigurator, self).__init__()
|
||||
# TODO: this instantiation can be optimized to only load
|
||||
# relevant files - I believe -> NO_MODL_AUTOLOAD
|
||||
# Set Augeas flags to save backup
|
||||
self.aug = augeas.Augeas(flags=augeas.Augeas.NONE)
|
||||
self.save_notes = ""
|
||||
|
||||
def deploy_cert(self, vhost, cert, key , cert_chain=None):
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
Chooses a virtual host based on a given domain name
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def get_all_names(self):
|
||||
"""
|
||||
Return all names found in the Configuration
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_redirect(self, ssl_vhost):
|
||||
"""
|
||||
Makes all traffic redirect to the given ssl_vhost
|
||||
ie. port 80 => 443
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_hsts(self, ssl_vhost):
|
||||
"""
|
||||
Enable HSTS on the given ssl_vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_ocsp_stapling(self, ssl_vhost):
|
||||
"""
|
||||
Enable OCSP stapling on given ssl_vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def get_all_certs_keys(self):
|
||||
"""
|
||||
Retrieve all certs and keys set in configuration
|
||||
return list of tuples with form [(cert, key, path)]
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_site(self, vhost):
|
||||
"""
|
||||
Enable the site at the given vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def check_parsing_errors(self, lens):
|
||||
"""
|
||||
This function checks to see if Augeas was unable to parse any of the
|
||||
lens files
|
||||
"""
|
||||
error_files = self.aug.match("/augeas//error")
|
||||
|
||||
for e in error_files:
|
||||
# Check to see if it was an error resulting from the use of
|
||||
# the httpd lens
|
||||
lens_path = self.aug.get(e + '/lens')
|
||||
# As aug.get may return null
|
||||
if lens_path and lens 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 save(self, title=None, temporary=False):
|
||||
"""
|
||||
Saves all changes to the configuration files
|
||||
This function is not transactional
|
||||
TODO: Instead rely on challenge to backup all files before modifications
|
||||
|
||||
title: string - The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint
|
||||
and put in a timestamped directory.
|
||||
`title` has no effect if temporary is true.
|
||||
temporary: boolean - Indicates whether the changes made will be
|
||||
quickly reversed in the future (challenges)
|
||||
"""
|
||||
save_state = self.aug.get("/augeas/save")
|
||||
self.aug.set("/augeas/save", "noop")
|
||||
# Existing Errors
|
||||
ex_errs = self.aug.match("/augeas//error")
|
||||
try:
|
||||
# This is a noop save
|
||||
self.aug.save()
|
||||
except:
|
||||
# Check for the root of save problems
|
||||
new_errs = self.aug.match("/augeas//error")
|
||||
logger.error("During Save - " + mod_conf)
|
||||
# Only print new errors caused by recent save
|
||||
for err in new_errs:
|
||||
if err not in ex_errs:
|
||||
logger.error("Unable to save file - %s" % err[13:len(err)-6])
|
||||
logger.error("Attempted Save Notes")
|
||||
logger.error(self.save_notes)
|
||||
# Erase Save Notes
|
||||
self.save_notes = ""
|
||||
return False
|
||||
|
||||
# Retrieve list of modified files
|
||||
# Note: Noop saves can cause the file to be listed twice, I used a
|
||||
# set to remove this possibility. This is a known augeas 0.10 error.
|
||||
save_paths = self.aug.match("/augeas/events/saved")
|
||||
|
||||
# If the augeas tree didn't change, no files were saved and a backup
|
||||
# should not be created
|
||||
if save_paths:
|
||||
save_files = set()
|
||||
for p in save_paths:
|
||||
save_files.add(self.aug.get(p)[6:])
|
||||
|
||||
valid, message = self.check_tempfile_saves(save_files, temporary)
|
||||
|
||||
if not valid:
|
||||
logger.fatal(message)
|
||||
# What is the protocol in this situation?
|
||||
# This shouldn't happen if the challenge codebase is correct
|
||||
return False
|
||||
|
||||
# Create Checkpoint
|
||||
if temporary:
|
||||
self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files)
|
||||
else:
|
||||
self.add_to_checkpoint(IN_PROGRESS_DIR, save_files)
|
||||
|
||||
|
||||
if title and not temporary and os.path.isdir(IN_PROGRESS_DIR):
|
||||
success = self.__finalize_checkpoint(IN_PROGRESS_DIR, title)
|
||||
if not success:
|
||||
# This should never happen
|
||||
# This will be hopefully be cleaned up on the recovery
|
||||
# routine startup
|
||||
sys.exit(9)
|
||||
|
||||
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
self.save_notes = ""
|
||||
self.aug.save()
|
||||
|
||||
return True
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""
|
||||
This function should reload the users original configuration files
|
||||
for all saves with reversible=True
|
||||
"""
|
||||
if os.path.isdir(TEMP_CHECKPOINT_DIR):
|
||||
result = self.__recover_checkpoint(TEMP_CHECKPOINT_DIR)
|
||||
changes = True
|
||||
if result != 0:
|
||||
# We have a partial or incomplete recovery
|
||||
logger.fatal("Incomplete or failed recovery for %s" % TEMP_CHECKPOINT_DIR)
|
||||
sys.exit(67)
|
||||
# Remember to reload Augeas
|
||||
self.aug.load()
|
||||
|
||||
|
||||
def rollback_checkpoints(self, rollback = 1):
|
||||
""" Revert 'rollback' number of configuration checkpoints """
|
||||
try:
|
||||
rollback = int(rollback)
|
||||
except:
|
||||
logger.error("Rollback argument must be a positive integer")
|
||||
# Sanity check input
|
||||
if rollback < 1:
|
||||
logger.error("Rollback argument must be a positive integer")
|
||||
return
|
||||
|
||||
backups = os.listdir(BACKUP_DIR)
|
||||
backups.sort()
|
||||
|
||||
if len(backups) < rollback:
|
||||
logger.error("Unable to rollback %d checkpoints, only %d exist" % (rollback, len(backups)))
|
||||
|
||||
while rollback > 0 and backups:
|
||||
cp_dir = BACKUP_DIR + backups.pop()
|
||||
result = self.__recover_checkpoint(cp_dir)
|
||||
if result != 0:
|
||||
logger.fatal("Failed to load checkpoint during rollback")
|
||||
sys.exit(39)
|
||||
rollback -= 1
|
||||
|
||||
self.aug.load()
|
||||
|
||||
def display_checkpoints(self):
|
||||
"""
|
||||
Displays all saved checkpoints
|
||||
Note: Any 'IN_PROGRESS' checkpoints will be removed by the cleanup
|
||||
script found in the constructor, before this function would ever be
|
||||
called
|
||||
"""
|
||||
backups = os.listdir(BACKUP_DIR)
|
||||
backups.sort(reverse=True)
|
||||
|
||||
if not backups:
|
||||
print "Letsencrypt has not saved any backups of your apache configuration"
|
||||
# Make sure there isn't anything unexpected in the backup folder
|
||||
# There should only be timestamped (float) directories
|
||||
try:
|
||||
for bu in backups:
|
||||
float(bu)
|
||||
except:
|
||||
assert False, "Invalid files in %s" % BACKUP_DIR
|
||||
|
||||
for bu in backups:
|
||||
print time.ctime(float(bu))
|
||||
with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f:
|
||||
print f.read()
|
||||
|
||||
print "Affected files:"
|
||||
with open(BACKUP_DIR + bu + "/FILEPATHS") as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
print " %s" % fp
|
||||
|
||||
try:
|
||||
with open(BACKUP_DIR + bu + "/NEW_FILES") as f:
|
||||
print "New Configuration Files:"
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
print " %s" % fp
|
||||
except:
|
||||
pass
|
||||
print ""
|
||||
|
||||
def __finalize_checkpoint(self, cp_dir, title):
|
||||
"""
|
||||
Add title to cp_dir CHANGES_SINCE
|
||||
Move cp_dir to Backups directory and rename with timestamp
|
||||
"""
|
||||
final_dir = BACKUP_DIR + str(time.time())
|
||||
try:
|
||||
with open(cp_dir + "CHANGES_SINCE.tmp", 'w') as ft:
|
||||
ft.write("-- %s --\n" % title)
|
||||
with open(cp_dir + "CHANGES_SINCE", 'r') as f:
|
||||
ft.write(f.read())
|
||||
shutil.move(cp_dir + "CHANGES_SINCE.tmp", cp_dir + "CHANGES_SINCE")
|
||||
except:
|
||||
logger.error("Unable to finalize checkpoint - adding title")
|
||||
return False
|
||||
try:
|
||||
os.rename(cp_dir, final_dir)
|
||||
except:
|
||||
logger.error("Unable to finalize checkpoint, %s -> %s" % cp_dir, final_dir)
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_to_checkpoint(self, cp_dir, save_files):
|
||||
le_util.make_or_verify_dir(cp_dir, 0755)
|
||||
|
||||
existing_filepaths = []
|
||||
op_fd = None
|
||||
# Open up FILEPATHS differently depending on if it already exists
|
||||
if os.path.isfile(cp_dir + "FILEPATHS"):
|
||||
op_fd = open(cp_dir + "FILEPATHS", 'r+')
|
||||
existing_filepaths = op_fd.read().splitlines()
|
||||
else:
|
||||
op_fd = open(cp_dir + "FILEPATHS", 'w')
|
||||
|
||||
idx = len(existing_filepaths)
|
||||
for filename in save_files:
|
||||
if filename not in existing_filepaths:
|
||||
# Tag files with index so multiple files can
|
||||
# have the same filename
|
||||
logger.debug("Creating backup of %s" % filename)
|
||||
shutil.copy2(filename, cp_dir + os.path.basename(filename) + "_" + str(idx))
|
||||
op_fd.write(filename + '\n')
|
||||
idx += 1
|
||||
op_fd.close()
|
||||
|
||||
with open(cp_dir + "CHANGES_SINCE", 'a') as notes_fd:
|
||||
notes_fd.write(self.save_notes)
|
||||
|
||||
|
||||
|
||||
def __recover_checkpoint(self, cp_dir):
|
||||
"""
|
||||
Recover a specific checkpoint provided by cp_dir
|
||||
Note: this function does not reload augeas.
|
||||
|
||||
returns: 0 success, 1 Unable to revert, -1 Unable to delete
|
||||
"""
|
||||
|
||||
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")
|
||||
|
||||
try:
|
||||
shutil.rmtree(cp_dir)
|
||||
except:
|
||||
logger.error("Unable to remove directory: %s" % cp_dir)
|
||||
return -1
|
||||
|
||||
return 0
|
||||
|
||||
def check_tempfile_saves(self, save_files, temporary):
|
||||
temp_path = "%sFILEPATHS" % TEMP_CHECKPOINT_DIR
|
||||
if os.path.isfile(temp_path):
|
||||
with open(temp_path, 'r') as protected_fd:
|
||||
protected_files = protected_fd.read().splitlines()
|
||||
for filename in protected_files:
|
||||
if filename in save_files:
|
||||
return False, "Attempting to overwrite challenge file - %s" % filename
|
||||
|
||||
return True, "Successful"
|
||||
|
||||
|
||||
def register_file_creation(self, temporary, *files):
|
||||
"""
|
||||
This is used to register the creation of all files during Letsencrypt
|
||||
execution. Call this method before writing to the file to make sure
|
||||
that the file will be cleaned up if the program exits unexpectedly.
|
||||
(Before a save occurs)
|
||||
"""
|
||||
if temporary:
|
||||
cp_dir = TEMP_CHECKPOINT_DIR
|
||||
else:
|
||||
cp_dir = IN_PROGRESS_DIR
|
||||
|
||||
le_util.make_or_verify_dir(cp_dir)
|
||||
try:
|
||||
with open(cp_dir + "NEW_FILES", 'a') as fd:
|
||||
for f in files:
|
||||
fd.write("%s\n" % f)
|
||||
except:
|
||||
logger.error("ERROR: Unable to register file creation")
|
||||
|
||||
|
||||
def recovery_routine(self):
|
||||
"""
|
||||
Revert all previously modified files. First, any changes found in
|
||||
TEMP_CHECKPOINT_DIR are removed, then IN_PROGRESS changes are removed
|
||||
The order is important. IN_PROGRESS is unable to add files that are
|
||||
already added by a TEMP change. Thus TEMP must be rolled back first
|
||||
because that will be the 'latest' occurrence of the file.
|
||||
"""
|
||||
self.revert_challenge_config()
|
||||
if os.path.isdir(IN_PROGRESS_DIR):
|
||||
result = self.__recover_checkpoint(IN_PROGRESS_DIR)
|
||||
if result != 0:
|
||||
# We have a partial or incomplete recovery
|
||||
# Not as egregious
|
||||
# TODO: Additional tests? recovery
|
||||
logger.fatal("Incomplete or failed recovery for %s" % IN_PROGRESS_DIR)
|
||||
sys.exit(68)
|
||||
|
||||
# Need to reload configuration after these changes take effect
|
||||
self.aug.load()
|
||||
|
||||
|
||||
def __remove_contained_files(self, file_list):
|
||||
"""
|
||||
Erase any files contained within the text file, file_list
|
||||
"""
|
||||
# Check to see that file exists to differentiate can't find file_list
|
||||
# and can't remove filepaths within file_list errors.
|
||||
if not os.path.isfile(file_list):
|
||||
return False
|
||||
try:
|
||||
with open(file_list, 'r') as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
# Files are registered before they are added... so check to see if file
|
||||
# exists first
|
||||
if os.path.lexists(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)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def config_test(self):
|
||||
"""
|
||||
Make sure the configuration is valid
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def restart(self):
|
||||
"""
|
||||
Restart or refresh the server content
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def perform(self, challenge):
|
||||
""" Perform the challenge """
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def cleanup(self):
|
||||
""" Clean up any challenge configurations """
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
|
@ -10,13 +10,13 @@ import requests
|
|||
|
||||
from letsencrypt.client.acme import acme_object_validate
|
||||
from letsencrypt.client.sni_challenge import SNI_Challenge
|
||||
from letsencrypt.client import configurator
|
||||
from letsencrypt.client import configurator, apache_configurator
|
||||
from letsencrypt.client import logger, display
|
||||
from letsencrypt.client import le_util, crypto_util
|
||||
from letsencrypt.client.CONFIG import RSA_KEY_SIZE, CERT_PATH
|
||||
from letsencrypt.client.CONFIG import CHAIN_PATH, SERVER_ROOT, KEY_DIR, CERT_DIR
|
||||
from letsencrypt.client.CONFIG import CERT_KEY_BACKUP
|
||||
from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES
|
||||
from letsencrypt.client.CONFIG import CERT_KEY_BACKUP, EXCLUSIVE_CHALLENGES
|
||||
from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, CONFIG_CHALLENGES
|
||||
# it's weird to point to chocolate servers via raw IPv6 addresses, and such
|
||||
# addresses can be %SCARY in some contexts, so out of paranoia let's disable
|
||||
# them by default
|
||||
|
|
@ -33,7 +33,10 @@ class Client(object):
|
|||
|
||||
# Logger needs to be initialized before Configurator
|
||||
self.init_logger()
|
||||
self.config = configurator.Configurator(SERVER_ROOT)
|
||||
# TODO: Can probably figure out which configurator to use without
|
||||
# special packaging based on system info
|
||||
# Command line arg or client function to discover
|
||||
self.config = apache_configurator.ApacheConfigurator(SERVER_ROOT)
|
||||
|
||||
self.server = ca_server
|
||||
|
||||
|
|
@ -52,7 +55,7 @@ class Client(object):
|
|||
|
||||
def authenticate(self, domains = [], redirect = None, eula = False):
|
||||
# Check configuration
|
||||
if not self.config.configtest():
|
||||
if not self.config.config_test():
|
||||
sys.exit(1)
|
||||
|
||||
self.redirect = redirect
|
||||
|
|
@ -60,7 +63,7 @@ class Client(object):
|
|||
# Display preview warning
|
||||
if not eula:
|
||||
with open('EULA') as f:
|
||||
if not display.generic_yesno(f.read(), "Agree", "Disagree"):
|
||||
if not display.generic_yesno(f.read(), "Agree", "Cancel"):
|
||||
sys.exit(0)
|
||||
|
||||
# Display screen to select domains to validate
|
||||
|
|
@ -82,11 +85,6 @@ class Client(object):
|
|||
# TODO: Use correct server depending on CA
|
||||
#choice = self.choice_of_ca()
|
||||
|
||||
# Check first if mod_ssl is loaded
|
||||
if not self.config.check_ssl_loaded():
|
||||
logger.info("Loading mod_ssl into Apache Server")
|
||||
self.config.enable_mod("ssl")
|
||||
|
||||
#Request Challenges
|
||||
challenge_dict = self.handle_challenge()
|
||||
|
||||
|
|
@ -288,7 +286,6 @@ class Client(object):
|
|||
self.redirect = display.redirect_by_default()
|
||||
|
||||
if self.redirect:
|
||||
self.config.enable_mod("rewrite")
|
||||
self.redirect_to_ssl(vhost)
|
||||
self.config.restart(quiet=self.curses)
|
||||
|
||||
|
|
@ -312,7 +309,11 @@ class Client(object):
|
|||
def cleanup_challenges(self, challenge_objs):
|
||||
logger.info("Cleaning up challenges...")
|
||||
for c in challenge_objs:
|
||||
c.cleanup()
|
||||
if c["type"] in CONFIG_CHALLENGES:
|
||||
self.config.cleanup()
|
||||
else:
|
||||
#Handle other cleanup if needed
|
||||
pass
|
||||
|
||||
def is_expected_msg(self, msg_dict, expected, delay=3, rounds = 20):
|
||||
for i in range(rounds):
|
||||
|
|
@ -372,16 +373,19 @@ class Client(object):
|
|||
challenge_objs, indicies = self.challenge_factory(
|
||||
self.names[0], c["challenges"], path)
|
||||
|
||||
responses = ["null"] * len(c["challenges"])
|
||||
|
||||
responses = [None] * len(c["challenges"])
|
||||
|
||||
# Perform challenges and populate responses
|
||||
# Perform challenges
|
||||
for i, c_obj in enumerate(challenge_objs):
|
||||
if not c_obj.perform():
|
||||
logger.fatal("Challenge Failed")
|
||||
sys.exit(1)
|
||||
response = "null"
|
||||
if c_obj["type"] in CONFIG_CHALLENGES:
|
||||
response = self.config.perform(c_obj)
|
||||
else:
|
||||
# Handle RecoveryToken type challenges
|
||||
pass
|
||||
|
||||
for index in indicies[i]:
|
||||
responses[index] = c_obj.generate_response()
|
||||
responses[index] = response
|
||||
|
||||
logger.info("Configured Apache for challenges; " +
|
||||
"waiting for verification...")
|
||||
|
|
@ -392,7 +396,7 @@ class Client(object):
|
|||
"""
|
||||
Generate a plan to get authority over the identity
|
||||
TODO: Make sure that the challenges are feasible...
|
||||
TODO Example: Do you have the recovery key?
|
||||
Example: Do you have the recovery key?
|
||||
"""
|
||||
|
||||
if combos:
|
||||
|
|
@ -515,14 +519,12 @@ class Client(object):
|
|||
|
||||
def redirect_to_ssl(self, vhost):
|
||||
for ssl_vh in vhost:
|
||||
success, redirect_vhost = self.config.redirect_all_ssl(ssl_vh)
|
||||
logger.info("\nRedirect vhost: " + redirect_vhost.file +
|
||||
success, redirect_vhost = self.config.enable_redirect(ssl_vh)
|
||||
logger.info("\nRedirect vhost: " + redirect_vhost.file +
|
||||
" - " + str(success))
|
||||
# If successful, make sure redirect site is enabled
|
||||
if success:
|
||||
if not self.config.is_site_enabled(redirect_vhost.file):
|
||||
self.config.enable_site(redirect_vhost)
|
||||
logger.info("Enabling available site: " + redirect_vhost.file)
|
||||
# If successful, make sure redirect site is enabled
|
||||
if success:
|
||||
self.config.enable_site(redirect_vhost)
|
||||
|
||||
|
||||
def get_virtual_hosts(self, domains):
|
||||
|
|
@ -551,7 +553,7 @@ class Client(object):
|
|||
elif challenges[c]["type"] == "recoveryToken":
|
||||
logger.info("\tRecovery Token Challenge for name: %s." % name)
|
||||
challenge_objs_indicies.append(c)
|
||||
challenge_objs.append(RecoveryToken())
|
||||
challenge_objs.append({type:"recoveryToken"})
|
||||
|
||||
else:
|
||||
logger.fatal("Challenge not currently supported")
|
||||
|
|
@ -560,8 +562,8 @@ class Client(object):
|
|||
if sni_todo:
|
||||
# SNI_Challenge can satisfy many sni challenges at once so only
|
||||
# one "challenge object" is issued for all sni_challenges
|
||||
challenge_objs.append(SNI_Challenge(
|
||||
sni_todo, os.path.abspath(self.key_file), self.config))
|
||||
challenge_objs.append({"type":"dvsni", "listSNITuple":sni_todo,
|
||||
"dvsni_key":os.path.abspath(self.key_file)})
|
||||
challenge_obj_indicies.append(sni_satisfies)
|
||||
logger.debug(sni_todo)
|
||||
|
||||
|
|
@ -627,33 +629,36 @@ class Client(object):
|
|||
|
||||
return selection
|
||||
|
||||
def get_cas(self):
|
||||
DV_choices = []
|
||||
OV_choices = []
|
||||
EV_choices = []
|
||||
choices = []
|
||||
try:
|
||||
with open("/etc/letsencrypt/.ca_offerings") as f:
|
||||
for line in f:
|
||||
choice = line.split(";", 1)
|
||||
if 'DV' in choice[0]:
|
||||
DV_choices.append(choice)
|
||||
elif 'OV' in choice[0]:
|
||||
OV_choices.append(choice)
|
||||
else:
|
||||
EV_choices.append(choice)
|
||||
# Legacy Code: Although I would like to see a free and open marketplace
|
||||
# in the future. The Let's Encrypt Client will not have this feature at
|
||||
# launch
|
||||
# def get_cas(self):
|
||||
# DV_choices = []
|
||||
# OV_choices = []
|
||||
# EV_choices = []
|
||||
# choices = []
|
||||
# try:
|
||||
# with open("/etc/letsencrypt/.ca_offerings") as f:
|
||||
# for line in f:
|
||||
# choice = line.split(";", 1)
|
||||
# if 'DV' in choice[0]:
|
||||
# DV_choices.append(choice)
|
||||
# elif 'OV' in choice[0]:
|
||||
# OV_choices.append(choice)
|
||||
# else:
|
||||
# EV_choices.append(choice)
|
||||
|
||||
# random.shuffle(DV_choices)
|
||||
# random.shuffle(OV_choices)
|
||||
# random.shuffle(EV_choices)
|
||||
choices = DV_choices + OV_choices + EV_choices
|
||||
choices = [(l[0], l[1]) for l in choices]
|
||||
# # random.shuffle(DV_choices)
|
||||
# # random.shuffle(OV_choices)
|
||||
# # random.shuffle(EV_choices)
|
||||
# choices = DV_choices + OV_choices + EV_choices
|
||||
# choices = [(l[0], l[1]) for l in choices]
|
||||
|
||||
except IOError as e:
|
||||
logger.fatal("Unable to find .ca_offerings file")
|
||||
sys.exit(1)
|
||||
# except IOError as e:
|
||||
# logger.fatal("Unable to find .ca_offerings file")
|
||||
# sys.exit(1)
|
||||
|
||||
return choices
|
||||
# return choices
|
||||
|
||||
def get_all_names(self):
|
||||
"""
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -309,11 +309,11 @@ def redirect_by_default():
|
|||
"Please enter the appropriate number",
|
||||
width = WIDTH)
|
||||
|
||||
if result[0] != 0:
|
||||
if result[0] != OK:
|
||||
return False
|
||||
|
||||
# different answer for each type of display
|
||||
return (result[1] == "Secure" or result[1] == 1)
|
||||
return (str(result[1]) == "Secure" or result[1] == 1)
|
||||
|
||||
|
||||
def confirm_revocation(cert):
|
||||
|
|
|
|||
222
letsencrypt/client/nginx_configurator.py
Normal file
222
letsencrypt/client/nginx_configurator.py
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
from letsencrypt.client.CONFIG import SERVER_ROOT, BACKUP_DIR
|
||||
from letsencrypt.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
|
||||
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, LE_VHOST_EXT
|
||||
from letsencrypt.client import logger, le_util, configurator
|
||||
|
||||
|
||||
# This might be helpful... but feel free to use whatever you want
|
||||
# class VH(object):
|
||||
# def __init__(self, filename_path, vh_path, vh_addrs, is_ssl, is_enabled):
|
||||
# self.file = filename_path
|
||||
# self.path = vh_path
|
||||
# self.addrs = vh_addrs
|
||||
# self.names = []
|
||||
# self.ssl = is_ssl
|
||||
# self.enabled = is_enabled
|
||||
|
||||
# def set_names(self, listOfNames):
|
||||
# self.names = listOfNames
|
||||
|
||||
# def add_name(self, name):
|
||||
# self.names.append(name)
|
||||
|
||||
class NginxConfigurator(AugeasConfigurator):
|
||||
|
||||
def __init__(self, server_root=SERVER_ROOT):
|
||||
self.server_root = server_root
|
||||
|
||||
# See if any temporary changes need to be recovered
|
||||
# This needs to occur before VH objects are setup...
|
||||
# because this will change the underlying configuration and potential
|
||||
# vhosts
|
||||
self.recovery_routine()
|
||||
# Check for errors in parsing files with Augeas
|
||||
# TODO - insert nginx lens info here???
|
||||
#self.check_parsing_errors("httpd.aug")
|
||||
|
||||
|
||||
def deploy_cert(self, vhost, cert, key, cert_chain=None):
|
||||
"""
|
||||
Deploy cert in nginx
|
||||
"""
|
||||
return
|
||||
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
Chooses a virtual host based on the given domain name
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_all_names(self):
|
||||
"""
|
||||
Returns all names found in the nginx configuration
|
||||
"""
|
||||
all_names = set()
|
||||
|
||||
return all_names
|
||||
|
||||
# Might be helpful... I know nothing about nginx lens
|
||||
# def get_include_path(self, cur_dir, arg):
|
||||
# """
|
||||
# Converts an Apache Include directive argument into an Augeas
|
||||
# searchable path
|
||||
# Returns path string
|
||||
# """
|
||||
# # Sanity check argument - maybe
|
||||
# # Question: what can the attacker do with control over this string
|
||||
# # Effect parse file... maybe exploit unknown errors in Augeas
|
||||
# # If the attacker can Include anything though... and this function
|
||||
# # only operates on Apache real config data... then the attacker has
|
||||
# # already won.
|
||||
# # Perhaps it is better to simply check the permissions on all
|
||||
# # included files?
|
||||
# # check_config to validate apache config doesn't work because it
|
||||
# # would create a race condition between the check and this input
|
||||
|
||||
# # TODO: Fix this
|
||||
# # Check to make sure only expected characters are used <- maybe remove
|
||||
# # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
||||
# # matchObj = validChars.match(arg)
|
||||
# # if matchObj.group() != arg:
|
||||
# # logger.error("Error: Invalid regexp characters in %s" % arg)
|
||||
# # return []
|
||||
|
||||
# # Standardize the include argument based on server root
|
||||
# if not arg.startswith("/"):
|
||||
# arg = cur_dir + arg
|
||||
# # conf/ is a special variable for ServerRoot in Apache
|
||||
# elif arg.startswith("conf/"):
|
||||
# arg = self.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
|
||||
# self.parse_file(arg)
|
||||
|
||||
# # Argument represents an fnmatch regular expression, convert it
|
||||
# # Split up the path and convert each into an Augeas accepted regex
|
||||
# # then reassemble
|
||||
# if "*" in arg or "?" in arg:
|
||||
# postfix = ""
|
||||
# splitArg = arg.split("/")
|
||||
# for idx, split in enumerate(splitArg):
|
||||
# # * and ? are the two special fnmatch characters
|
||||
# if "*" in split or "?" in split:
|
||||
# # Turn it into a augeas regex
|
||||
# # TODO: Can this instead be an augeas glob instead of regex
|
||||
# splitArg[idx] = "* [label()=~regexp('%s')]" % self.fnmatch_to_re(split)
|
||||
# # Reassemble the argument
|
||||
# arg = "/".join(splitArg)
|
||||
|
||||
# # If the include is a directory, just return the directory as a file
|
||||
# if arg.endswith("/"):
|
||||
# return "/files" + arg[:len(arg)-1]
|
||||
# return "/files"+arg
|
||||
|
||||
|
||||
def enable_redirect(self, ssl_vhost):
|
||||
"""
|
||||
Adds Redirect directive to the port 80 equivalent of ssl_vhost
|
||||
First the function attempts to find the vhost with equivalent
|
||||
ip addresses that serves on non-ssl ports
|
||||
The function then adds the directive
|
||||
"""
|
||||
return
|
||||
|
||||
|
||||
def enable_ocsp_stapling(self, ssl_vhost):
|
||||
return False
|
||||
|
||||
def enable_hsts(self, ssl_vhost):
|
||||
return False
|
||||
|
||||
def get_all_certs_keys(self):
|
||||
"""
|
||||
Retrieve all certs and keys set in VirtualHosts on the Apache server
|
||||
returns: list of tuples with form [(cert, key, path)]
|
||||
"""
|
||||
return None
|
||||
|
||||
# Probably helpful reference
|
||||
# def get_file_path(self, vhost_path):
|
||||
# """
|
||||
# Takes in Augeas path and returns the file name
|
||||
# """
|
||||
# # Strip off /files
|
||||
# avail_fp = vhost_path[6:]
|
||||
# # This can be optimized...
|
||||
# while True:
|
||||
# # Cast both to lowercase to be case insensitive
|
||||
# find_if = avail_fp.lower().find("/ifmodule")
|
||||
# if find_if != -1:
|
||||
# avail_fp = avail_fp[:find_if]
|
||||
# continue
|
||||
# find_vh = avail_fp.lower().find("/virtualhost")
|
||||
# if find_vh != -1:
|
||||
# avail_fp = avail_fp[:find_vh]
|
||||
# continue
|
||||
# break
|
||||
# return avail_fp
|
||||
|
||||
def enable_site(self, vhost):
|
||||
"""
|
||||
Enables an available site, Apache restart required
|
||||
"""
|
||||
return False
|
||||
|
||||
# Might be a usefule reference
|
||||
# def parse_file(self, file_path):
|
||||
# """
|
||||
# Checks to see if file_path is parsed by Augeas
|
||||
# If file_path isn't parsed, the file is added and Augeas is reloaded
|
||||
# """
|
||||
# # Test if augeas included file for Httpd.lens
|
||||
# # Note: This works for augeas globs, ie. *.conf
|
||||
# incTest = self.aug.match("/augeas/load/Httpd/incl [. ='" + file_path + "']")
|
||||
# if not incTest:
|
||||
# # Load up files
|
||||
# #self.httpd_incl.append(file_path)
|
||||
# #self.aug.add_transform("Httpd.lns", self.httpd_incl, None, self.httpd_excl)
|
||||
# self.__add_httpd_transform(file_path)
|
||||
# self.aug.load()
|
||||
|
||||
|
||||
# Helpful reference?
|
||||
# def verify_setup(self):
|
||||
# '''
|
||||
# Make sure that files/directories are setup with appropriate permissions
|
||||
# Aim for defensive coding... make sure all input files
|
||||
# have permissions of root
|
||||
# '''
|
||||
# le_util.make_or_verify_dir(CONFIG_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(WORK_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(BACKUP_DIR, 0755)
|
||||
|
||||
def restart(self, quiet=False):
|
||||
"""
|
||||
Restarts nginx server
|
||||
"""
|
||||
return
|
||||
|
||||
# May be of use?
|
||||
# def __add_httpd_transform(self, incl):
|
||||
# """
|
||||
# This function will correctly add a transform to augeas
|
||||
# The existing augeas.add_transform in python is broken
|
||||
# """
|
||||
# lastInclude = self.aug.match("/augeas/load/Httpd/incl [last()]")
|
||||
# self.aug.insert(lastInclude[0], "incl", False)
|
||||
# self.aug.set("/augeas/load/Httpd/incl[last()]", incl)
|
||||
|
||||
def config_test(self):
|
||||
""" Check Configuration """
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,312 +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 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 = "<VirtualHost " + " ".join(ip_addrs) + "> \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 \
|
||||
</VirtualHost> \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 = "<IfModule mod_ssl.c> \n"
|
||||
for idx, lis in enumerate(listlistAddrs):
|
||||
configText += self.__getConfigText(self.listSNITuple[idx][2], lis, self.key)
|
||||
configText += "</IfModule> \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.createCHOC_CERT_CONF(name, ext)
|
||||
|
||||
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)
|
||||
|
||||
#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 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
|
||||
|
||||
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()
|
||||
Loading…
Reference in a new issue