Finished refactoring client.py and also reduced column size to 80 through display and client

This commit is contained in:
James Kasten
2014-11-11 01:42:46 -08:00
parent 66c37a2d40
commit 4af311894b
3 changed files with 211 additions and 132 deletions
+208 -129
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
import M2Crypto import M2Crypto
# It is OK to use the upstream M2Crypto here instead of our modified
# version.
import urllib2, json import urllib2, json
# XXX TODO: per https://docs.google.com/document/pub?id=1roBIeSJsYq3Ntpf6N0PIeeAAvu4ddn7mGo6Qb7aL7ew, urllib2 is unsafe (!) and must be replaced # XXX TODO: per https://docs.google.com/document/pub?
#id=1roBIeSJsYq3Ntpf6N0PIeeAAvu4ddn7mGo6Qb7aL7ew
# urllib2 is unsafe (!) and must be replaced
import os, grp, pwd, sys, time, random, sys, shutil import os, grp, pwd, sys, time, random, sys, shutil
import jose, csv import jose, csv
import subprocess import subprocess
@@ -20,8 +20,9 @@ from trustify.client.payment_challenge import Payment_Challenge
from trustify.client import configurator from trustify.client import configurator
from trustify.client import logger, display from trustify.client import logger, display
from trustify.client import trustify_util, crypto_util, display from trustify.client import trustify_util, crypto_util, display
from trustify.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE, CERT_PATH, CHAIN_PATH from trustify.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE, CERT_PATH
from trustify.client.CONFIG import SERVER_ROOT, KEY_DIR, CERT_DIR, CERT_KEY_BACKUP from trustify.client.CONFIG import CHAIN_PATH, SERVER_ROOT, KEY_DIR, CERT_DIR
from trustify.client.CONFIG import CERT_KEY_BACKUP
from trustify.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES from trustify.client.CONFIG import CHALLENGE_PREFERENCES, EXCLUSIVE_CHALLENGES
# it's weird to point to chocolate servers via raw IPv6 addresses, and such # 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 # addresses can be %SCARY in some contexts, so out of paranoia let's disable
@@ -31,8 +32,9 @@ allow_raw_ipv6_server = False
class Client(object): class Client(object):
# In case of import, dialog needs scope over the class # In case of import, dialog needs scope over the class
dialog = None dialog = None
def __init__(self, ca_server, domains=[], cert_signing_request=None, private_key=None, use_curses=True): def __init__(self, ca_server, domains=[], cert_signing_request=None,
private_key=None, use_curses=True):
global dialog global dialog
self.curses = use_curses self.curses = use_curses
@@ -44,7 +46,7 @@ class Client(object):
if domains: if domains:
self.names = domains self.names = domains
else: else:
# This function adds all names # This function adds all names
# found within the config to self.names # found within the config to self.names
self.get_all_names() self.get_all_names()
self.csr_file = cert_signing_request self.csr_file = cert_signing_request
@@ -54,15 +56,14 @@ class Client(object):
# TODO: Make sure key was actually used in CSR # TODO: Make sure key was actually used in CSR
# TODO: Make sure key has proper permissions # TODO: Make sure key has proper permissions
if self.csr_file and not self.key_file: if self.csr_file and not self.key_file:
logger.fatal("Please provide the private key file used in generating the provided CSR") logger.fatal("Please provide the private key file used in \
generating the provided CSR")
sys.exit(1) sys.exit(1)
self.sanity_check_names([ca_server] + domains) self.sanity_check_names([ca_server] + domains)
self.server_url = "https://%s/acme/" % self.server self.server_url = "https://%s/acme/" % self.server
def authenticate(self): def authenticate(self):
# Check configuration # Check configuration
if not self.config.configtest(): if not self.config.configtest():
@@ -88,33 +89,24 @@ class Client(object):
key_pem, csr_der = self.get_key_csr_pem() key_pem, csr_der = self.get_key_csr_pem()
challenge_dict = self.send(self.challenge_request(self.names)) #Request Challenges
challenge_dict = self.handle_challenge()
challenge_dict = self.is_expected_msg(challenge_dict, "challenge")
#Perform Challenges #Perform Challenges
responses, challenge_objs = self.verify_identity(challenge_dict) responses, challenge_objs = self.verify_identity(challenge_dict)
# Get Authorization
self.handle_authorization(challenge_dict, challenge_objs, responses)
# Retrieve certificate
certificate_dict = self.handle_certificate(csr_der)
# Find set of virtual hosts to deploy certificates to # Find set of virtual hosts to deploy certificates to
vhost = self.get_virtual_hosts(self.names) vhost = self.get_virtual_hosts(self.names)
authorization_dict = self.send(self.authorization_request(challenge_dict["sessionID"], self.names[0], challenge_dict["nonce"], responses))
authorization_dict = self.is_expected_msg(authorization_dict, "authorization")
if not authorization_dict:
self.cleanup_challenges(challenge_objs)
logger.fatal("Failed Authorization procedure - cleaning up challenges")
sys.exit(1)
certificate_dict = self.send(self.certificate_request(csr_der, self.key_file))
certificate_dict = self.is_expected_msg(certificate_dict, "certificate")
# Install Certificate # Install Certificate
self.cleanup_challenges(challenge_objs)
self.install_certificate(certificate_dict, vhost) self.install_certificate(certificate_dict, vhost)
# Perform optimal config changes # Perform optimal config changes
self.optimize_config(vhost) self.optimize_config(vhost)
@@ -125,16 +117,53 @@ class Client(object):
return return
def handle_challenge(self):
challenge_dict = self.send(self.challenge_request(self.names))
try:
return self.is_expected_msg(challenge_dict, "challenge")
except:
logger.fatal("Unexpected error")
sys.exit(1)
def handle_authorization(self, challenge_dict, chal_objs, responses):
auth_dict = self.send(self.authorization_request(
challenge_dict["sessionID"], self.names[0],
challenge_dict["nonce"], responses))
try:
return self.is_expected_msg(auth_dict, "authorization")
except:
logger.fatal("Failed Authorization procedure - \
cleaning up challenges")
sys.exit(1)
finally:
self.cleanup_challenges(chal_objs)
def handle_certificate(self, csr_der):
certificate_dict = self.send(
self.certificate_request(csr_der, self.key_file))
try:
return self.is_expected_msg(certificate_dict, "certificate")
except:
logger.fatal("Encountered unexpected message")
sys.exit(1)
def revoke(self, c): def revoke(self, c):
x = M2Crypto.X509.load_cert(c["backup_cert_file"]) x = M2Crypto.X509.load_cert(c["backup_cert_file"])
cert_der = x.as_der() cert_der = x.as_der()
#self.find_key_for_cert() #self.find_key_for_cert()
revocation_dict = self.send(self.revocation_request(c["backup_key_file"], cert_der)) revocation_dict = self.send(
self.revocation_request(c["backup_key_file"], cert_der))
revocation_dict = self.is_expected_msg(revocation_dict, "revocation") revocation_dict = self.is_expected_msg(revocation_dict, "revocation")
dialog.generic_notification("You have successfully revoked the certificate for %s" % c["cn"]) display.generic_notification(
"You have successfully revoked the certificate for %s" % c["cn"])
self.remove_cert_key(c) self.remove_cert_key(c)
sys.exit(0) sys.exit(0)
@@ -147,45 +176,50 @@ class Client(object):
with open(list_file2, 'wb') as newfile: with open(list_file2, 'wb') as newfile:
csvwriter = csv.writer(newfile) csvwriter = csv.writer(newfile)
for row in csvreader: for row in csvreader:
if not (row[0] == str(c["idx"]) and row[1] == c["orig_cert_file"] and row[2] == c["orig_key_file"]): if not (row[0] == str(c["idx"]) and
row[1] == c["orig_cert_file"] and
row[2] == c["orig_key_file"]):
csvwriter.writerow(row) csvwriter.writerow(row)
# remember that these are
shutil.copy2(list_file2, list_file) shutil.copy2(list_file2, list_file)
os.remove(list_file2) os.remove(list_file2)
os.remove(c['backup_cert_file']) os.remove(c['backup_cert_file'])
os.remove(c['backup_key_file']) os.remove(c['backup_key_file'])
def store_revocation_token(self, token): def store_revocation_token(self, token):
return return
def store_cert_key(self, encrypt = False): def store_cert_key(self, encrypt = False):
list_file = CERT_KEY_BACKUP + "LIST" list_file = CERT_KEY_BACKUP + "LIST"
trustify_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700) trustify_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700)
idx = 0 idx = 0
if encrypt: if encrypt:
logger.error("Unfortunately securely storing the certificates/keys is not yet available. Stay tuned for the next update!") logger.error("Unfortunately securely storing the certificates/keys \
is not yet available. Stay tuned for the next update!")
return False return False
if os.path.isfile(list_file):
with open(list_file, 'r+b') as csvfile:
csvreader = csv.reader(csvfile)
for r in csvreader:
idx = int(r[0]) + 1
csvwriter = csv.writer(csvfile)
csvwriter.writerow([str(idx), self.cert_file, self.key_file])
else: else:
if os.path.isfile(list_file): with open(list_file, 'wb') as csvfile:
with open(list_file, 'r+b') as csvfile: csvwriter = csv.writer(csvfile)
csvreader = csv.reader(csvfile) csvwriter.writerow(["0", self.cert_file, self.key_file])
for r in csvreader:
idx = int(r[0]) + 1
csvwriter = csv.writer(csvfile)
csvwriter.writerow([str(idx), self.cert_file, self.key_file])
else: shutil.copy2(self.key_file,
with open(list_file, 'wb') as csvfile: CERT_KEY_BACKUP + os.path.basename(self.key_file) +
csvwriter = csv.writer(csvfile) "_" + str(idx))
csvwriter.writerow(["0", self.cert_file, self.key_file]) shutil.copy2(self.cert_file,
CERT_KEY_BACKUP + os.path.basename(self.cert_file) +
shutil.copy2(self.key_file, CERT_KEY_BACKUP + os.path.basename(self.key_file) + "_" + str(idx)) "_" + str(idx))
shutil.copy2(self.cert_file, CERT_KEY_BACKUP + os.path.basename(self.cert_file) + "_" + str(idx))
def list_certs_keys(self): def list_certs_keys(self):
list_file = CERT_KEY_BACKUP + "LIST" list_file = CERT_KEY_BACKUP + "LIST"
@@ -194,59 +228,73 @@ class Client(object):
if not os.path.isfile(CERT_KEY_BACKUP + "LIST"): if not os.path.isfile(CERT_KEY_BACKUP + "LIST"):
logger.info("You don't have any certificates saved from trustify") logger.info("You don't have any certificates saved from trustify")
return return
with open(list_file, 'rb') as csvfile: with open(list_file, 'rb') as csvfile:
csvreader = csv.reader(csvfile) csvreader = csv.reader(csvfile)
for row in csvreader: for row in csvreader:
c = crypto_util.get_cert_info(row[1]) c = crypto_util.get_cert_info(row[1])
b_k = CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0]
b_c = CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0]
c["orig_key_file"] = row[2] c["orig_key_file"] = row[2]
c["orig_cert_file"] = row[1] c["orig_cert_file"] = row[1]
c["backup_key_file"] = CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0]
c["backup_cert_file"] = CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0]
c["idx"] = int(row[0]) c["idx"] = int(row[0])
c["backup_key_file"] = b_k
c["backup_cert_file"] = b_c
certs.append(c) certs.append(c)
if certs: if certs:
self.choose_certs(certs) self.choose_certs(certs)
else: else:
display.generic_notification("There are not any trusted Let's Encrypt certificates for this server.") display.generic_notification("There are not any trusted \
Let's Encrypt certificates for this server.")
def choose_certs(self, certs): def choose_certs(self, certs):
while True: while True:
code, selection = display.display_certs(certs) code, selection = display.display_certs(certs)
if code == display.OK: if code == display.OK:
if display.confirm_revocation(certs[int(selection)-1]): if display.confirm_revocation(certs[int(selection)-1]):
self.revoke(c) self.revoke(certs[int(selection)-1])
elif code == display.CANCEL: elif code == display.CANCEL:
exit(0) exit(0)
elif code == display.HELP: elif code == display.HELP:
display.more_info_cert(certs[int(selection)-1]) display.more_info_cert(certs[int(selection)-1])
def revocation_request(self, key_file, cert_der):
return {"type":"revocationRequest", "certificate":jose.b64encode_url(cert_der), "signature":crypto_util.create_sig(cert_der, key_file)}
def revocation_request(self, key_file, cert_der):
return {"type":"revocationRequest",
"certificate":jose.b64encode_url(cert_der),
"signature":crypto_util.create_sig(cert_der, key_file)}
def install_certificate(self, certificate_dict, vhost): def install_certificate(self, certificate_dict, vhost):
cert_chain_abspath = None cert_chain_abspath = None
cert_fd, self.cert_file = trustify_util.unique_file(CERT_PATH, 644) cert_fd, self.cert_file = trustify_util.unique_file(CERT_PATH, 644)
cert_fd.write(crypto_util.convert_b64_cert_to_pem(certificate_dict["certificate"])) cert_fd.write(
crypto_util.b64_cert_to_pem(certificate_dict["certificate"]))
cert_fd.close() cert_fd.close()
logger.info("Server issued certificate; certificate written to %s" % self.cert_file) logger.info("Server issued certificate; certificate written to %s" %
self.cert_file)
if certificate_dict.get("chain", None): if certificate_dict.get("chain", None):
chain_fd, chain_fn = trustify_util.unique_file(CHAIN_PATH, 644) chain_fd, chain_fn = trustify_util.unique_file(CHAIN_PATH, 644)
for c in certificate_dict.get("chain", []): for c in certificate_dict.get("chain", []):
chain_fd.write(crypto_util.convert_b64_cert_to_pem(c)) chain_fd.write(crypto_util.b64_cert_to_pem(c))
chain_fd.close() chain_fd.close()
logger.info("Cert chain written to %s" % chain_fn) logger.info("Cert chain written to %s" % chain_fn)
# This expects a valid chain file # This expects a valid chain file
cert_chain_abspath = os.path.abspath(chain_fn) cert_chain_abspath = os.path.abspath(chain_fn)
for host in vhost: for host in vhost:
self.config.deploy_cert(host, os.path.abspath(self.cert_file), os.path.abspath(self.key_file), cert_chain_abspath) self.config.deploy_cert(host,
os.path.abspath(self.cert_file),
os.path.abspath(self.key_file),
cert_chain_abspath)
# Enable any vhost that was issued to, but not enabled # Enable any vhost that was issued to, but not enabled
if not host.enabled: if not host.enabled:
logger.info("Enabling Site " + host.file) logger.info("Enabling Site " + host.file)
@@ -266,7 +314,9 @@ class Client(object):
def certificate_request(self, csr_der, key): def certificate_request(self, csr_der, key):
logger.info("Preparing and sending CSR..") logger.info("Preparing and sending CSR..")
return {"type":"certificateRequest", "csr":jose.b64encode_url(csr_der), "signature":crypto_util.create_sig(csr_der, self.key_file)} return {"type":"certificateRequest",
"csr":jose.b64encode_url(csr_der),
"signature":crypto_util.create_sig(csr_der, self.key_file)}
def cleanup_challenges(self, challenge_objs): def cleanup_challenges(self, challenge_objs):
logger.info("Cleaning up challenges...") logger.info("Cleaning up challenges...")
@@ -277,9 +327,14 @@ class Client(object):
for i in range(rounds): for i in range(rounds):
if msg_dict["type"] == expected: if msg_dict["type"] == expected:
return msg_dict return msg_dict
elif msg_dict["type"] == "error": elif msg_dict["type"] == "error":
logger.error("%s: %s - More Info: %s" % (msg_dict["error"], msg_dict.get("message", ""), msg_dict.get("moreInfo", ""))) logger.error("%s: %s - More Info: %s" %
return None (msg_dict["error"],
msg_dict.get("message", ""),
msg_dict.get("moreInfo", "")))
raise Exception(msg_dict["error"])
elif msg_dict["type"] == "defer": elif msg_dict["type"] == "defer":
logger.info("Waiting for %d seconds..." % delay) logger.info("Waiting for %d seconds..." % delay)
time.sleep(delay) time.sleep(delay)
@@ -290,32 +345,41 @@ class Client(object):
logger.fatal("Received: " + msg_dict) logger.fatal("Received: " + msg_dict)
sys.exit(33) sys.exit(33)
logger.error("Server has deferred past the max of %d seconds" % (rounds * delay)) logger.error("Server has deferred past the max of %d seconds" %
(rounds * delay))
return None return None
def authorization_request(self, id, name, server_nonce, responses): def authorization_request(self, id, name, server_nonce, responses):
auth_req = {"type":"authorizationRequest", "sessionID":id, "nonce":server_nonce} auth_req = {"type":"authorizationRequest",
auth_req["signature"] = crypto_util.create_sig(name + jose.b64decode_url(server_nonce), self.key_file) "sessionID":id,
"nonce":server_nonce}
auth_req["signature"] = crypto_util.create_sig(
name + jose.b64decode_url(server_nonce), self.key_file)
auth_req["responses"] = responses auth_req["responses"] = responses
return auth_req return auth_req
def status_request(self, token): def status_request(self, token):
return {"type":"statusRequest", "token":token} return {"type":"statusRequest", "token":token}
def challenge_request(self, names): def challenge_request(self, names):
#logger.info("Temporarily only enabling one name") #logger.info("Temporarily only enabling one name")
return {"type":"challengeRequest", "identifier": names[0]} return {"type":"challengeRequest", "identifier": names[0]}
def verify_identity(self, c): def verify_identity(self, c):
path = self.gen_challenge_path(c["challenges"], c.get("combinations", None)) path = self.gen_challenge_path(
c["challenges"], c.get("combinations", None))
logger.info("Peforming the following challenges:") logger.info("Peforming the following challenges:")
# Every indicies element is a list of integers referring to which challenges in the master list # Every indicies element is a list of integers referring to which
# the challenge object satisfies # challenges in the master list the challenge object satisfies
# Single Challenge objects that can satisfy multiple server challenges # Single Challenge objects that can satisfy multiple server challenges
# mess up the order of the challenges, thus requiring the indicies # mess up the order of the challenges, thus requiring the indicies
challenge_objs, indicies = self.challenge_factory(self.names[0], c["challenges"], path) challenge_objs, indicies = self.challenge_factory(
self.names[0], c["challenges"], path)
responses = [None] * len(c["challenges"]) responses = [None] * len(c["challenges"])
@@ -328,10 +392,11 @@ class Client(object):
for index in indicies[i]: for index in indicies[i]:
responses[index] = c_obj.generate_response() responses[index] = c_obj.generate_response()
logger.info("Configured Apache for challenges; waiting for verification...") logger.info("Configured Apache for challenges; \
waiting for verification...")
return responses, challenge_objs return responses, challenge_objs
def gen_challenge_path(self, challenges, combos): def gen_challenge_path(self, challenges, combos):
""" """
Generate a plan to get authority over the identity Generate a plan to get authority over the identity
@@ -343,11 +408,12 @@ class Client(object):
return self.__find_smart_path(challenges, combos) return self.__find_smart_path(challenges, combos)
return self.__find_dumb_path(challenges) return self.__find_dumb_path(challenges)
def __find_smart_path(self, challenges, combos): def __find_smart_path(self, challenges, combos):
""" """
Can be called if combinations is included Can be called if combinations is included
Function uses a simple ranking system to choose the combo with the lowest cost Function uses a simple ranking system to choose the combo with the
lowest cost
""" """
chall_cost = {} chall_cost = {}
max_cost = 0 max_cost = 0
@@ -358,7 +424,7 @@ class Client(object):
best_combo = [] best_combo = []
# Set above completing all of the available challenges # Set above completing all of the available challenges
best_combo_cost = max_cost + 1 best_combo_cost = max_cost + 1
combo_total = 0 combo_total = 0
for combo in combos: for combo in combos:
for c in combo: for c in combo:
@@ -368,7 +434,8 @@ class Client(object):
combo_total = 0 combo_total = 0
if not best_combo: if not best_combo:
logger.fatal("Client does not support any combination of challenges to satisfy ACME server") logger.fatal("Client does not support any combination of \
challenges to satisfy ACME server")
sys.exit(22) sys.exit(22)
return best_combo return best_combo
@@ -376,15 +443,16 @@ class Client(object):
def __find_dumb_path(self, challenges): def __find_dumb_path(self, challenges):
""" """
Should be called if the combinations hint is not included by the server Should be called if the combinations hint is not included by the server
This function returns the best path that does not contain multiple mutually exclusive This function returns the best path that does not contain multiple
challenges mutually exclusive challenges
""" """
# Add logic for a crappy server # Add logic for a crappy server
# Choose a DV # Choose a DV
path = [] path = []
for pref_c in CHALLENGE_PREFERENCES: for pref_c in CHALLENGE_PREFERENCES:
for i, offered_c in enumerate(challenges): for i, offered_c in enumerate(challenges):
if pref_c == offered_c["type"] and self.is_preferred(offered_c["type"], path): if (pref_c == offered_c["type"] and
self.is_preferred(offered_c["type"], path)):
path.append((i, offered_c["type"])) path.append((i, offered_c["type"]))
return [tup[0] for tup in path] return [tup[0] for tup in path]
@@ -393,31 +461,38 @@ class Client(object):
def is_preferred(self, offered_c_type, path): def is_preferred(self, offered_c_type, path):
for tup in path: for tup in path:
for s in EXCLUSIVE_CHALLENGES: for s in EXCLUSIVE_CHALLENGES:
# Second part is in case we eventually allow multiple names to be challenged # Second part is in case we eventually allow multiple names
# at the same time # to be challenges at the same time
if (tup[1] in s and offered_c_type in s) and tup[1] != offered_c_type: if (tup[1] in s and offered_c_type in s and
tup[1] != offered_c_type):
return False return False
return True return True
def send(self, json_obj): def send(self, json_obj):
acme_object_validate(json.dumps(json_obj)) try:
response = urllib2.urlopen(self.server_url, json.dumps(json_obj)).read() acme_object_validate(json.dumps(json_obj))
acme_object_validate(response) response = urllib2.urlopen(
return json.loads(response) self.server_url, json.dumps(json_obj)).read()
acme_object_validate(response)
return json.loads(response)
except:
logger.fatal("Send() failed... may have lost connection to server")
sys.exit(8)
def redirect_to_ssl(self, vhost): def redirect_to_ssl(self, vhost):
for ssl_vh in vhost: for ssl_vh in vhost:
success, redirect_vhost = self.config.redirect_all_ssl(ssl_vh) success, redirect_vhost = self.config.redirect_all_ssl(ssl_vh)
logger.info("\nRedirect vhost: " + redirect_vhost.file + " - " + str(success)) logger.info("\nRedirect vhost: " + redirect_vhost.file +
" - " + str(success))
# If successful, make sure redirect site is enabled # If successful, make sure redirect site is enabled
if success: if success:
if not self.config.is_site_enabled(redirect_vhost.file): if not self.config.is_site_enabled(redirect_vhost.file):
self.config.enable_site(redirect_vhost) self.config.enable_site(redirect_vhost)
logger.info("Enabling available site: " + redirect_vhost.file) logger.info("Enabling available site: " + redirect_vhost.file)
def get_virtual_hosts(self, domains): def get_virtual_hosts(self, domains):
vhost = set() vhost = set()
for name in domains: for name in domains:
@@ -428,8 +503,8 @@ class Client(object):
def challenge_factory(self, name, challenges, path): def challenge_factory(self, name, challenges, path):
sni_todo = [] sni_todo = []
# Since a single invocation of SNI challenge can satsify multiple challenges # Since a single invocation of SNI challenge can satsify multiple
# We must keep track of all the challenges it satisfies # challenges. We must keep track of all the challenges it satisfies
sni_satisfies = [] sni_satisfies = []
challenge_objs = [] challenge_objs = []
@@ -438,20 +513,22 @@ class Client(object):
if challenges[c]["type"] == "dvsni": if challenges[c]["type"] == "dvsni":
logger.info("\tDomainValidateSNI challenge for name %s." % name) logger.info("\tDomainValidateSNI challenge for name %s." % name)
sni_satisfies.append(c) sni_satisfies.append(c)
sni_todo.append( (str(name), str(challenges[c]["r"]), str(challenges[c]["nonce"])) ) sni_todo.append( (str(name), str(challenges[c]["r"]),
str(challenges[c]["nonce"])) )
elif challenges[c]["type"] == "recoveryToken": elif challenges[c]["type"] == "recoveryToken":
logger.fatal("RecoveryToken Challenge type not currently supported") challenge_objs_indicies.append(c)
sys.exit(82) challenge_objs.append(RecoveryToken())
else: else:
logger.fatal("Challenge not currently supported") logger.fatal("Challenge not currently supported")
sys.exit(82) sys.exit(82)
if sni_todo: if sni_todo:
# SNI_Challenge can satisfy many sni challenges at once so only # SNI_Challenge can satisfy many sni challenges at once so only
# one "challenge object" is issued for all sni_challenges # 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(SNI_Challenge(
sni_todo, os.path.abspath(self.key_file), self.config))
challenge_obj_indicies.append(sni_satisfies) challenge_obj_indicies.append(sni_satisfies)
logger.debug(sni_todo) logger.debug(sni_todo)
@@ -460,10 +537,10 @@ class Client(object):
def get_key_csr_pem(self, csr_return_format = 'der'): def get_key_csr_pem(self, csr_return_format = 'der'):
""" """
Returns key and CSR using provided files or generating new files if necessary. Returns key and CSR using provided files or generating new files if
Both will be saved in pem format on the filesystem. The CSR can necessary. Both will be saved in pem format on the filesystem.
optionally be returned in DER format as the CSR cannot be loaded back into The CSR can optionally be returned in DER format as the CSR cannot be
M2Crypto. loaded back into M2Crypto.
""" """
key_pem = None key_pem = None
csr_pem = None csr_pem = None
@@ -471,7 +548,8 @@ class Client(object):
key_pem = crypto_util.make_key(RSA_KEY_SIZE) key_pem = crypto_util.make_key(RSA_KEY_SIZE)
# Save file # Save file
trustify_util.make_or_verify_dir(KEY_DIR, 0700) trustify_util.make_or_verify_dir(KEY_DIR, 0700)
key_f, self.key_file = trustify_util.unique_file(KEY_DIR + "key-trustify.pem", 0600) key_f, self.key_file = trustify_util.unique_file(
KEY_DIR + "key-trustify.pem", 0600)
key_f.write(key_pem) key_f.write(key_pem)
key_f.close() key_f.close()
logger.info("Generating key: %s" % self.key_file) logger.info("Generating key: %s" % self.key_file)
@@ -486,7 +564,8 @@ class Client(object):
csr_pem, csr_der = crypto_util.make_csr(self.key_file, self.names) csr_pem, csr_der = crypto_util.make_csr(self.key_file, self.names)
# Save CSR # Save CSR
trustify_util.make_or_verify_dir(CERT_DIR, 0755) trustify_util.make_or_verify_dir(CERT_DIR, 0755)
csr_f, self.csr_file = trustify_util.unique_file(CERT_DIR + "csr-trustify.pem", 0644) csr_f, self.csr_file = trustify_util.unique_file(
CERT_DIR + "csr-trustify.pem", 0644)
csr_f.write(csr_pem) csr_f.write(csr_pem)
csr_f.close() csr_f.close()
logger.info("Creating CSR: %s" % self.csr_file) logger.info("Creating CSR: %s" % self.csr_file)
@@ -503,7 +582,7 @@ class Client(object):
return key_pem, csr_pem return key_pem, csr_pem
def choice_of_ca(self): def choice_of_ca(self):
choices = self.get_cas() choices = self.get_cas()
message = "Pick a Certificate Authority. They're all unique and special!" message = "Pick a Certificate Authority. They're all unique and special!"
@@ -536,7 +615,7 @@ class Client(object):
# random.shuffle(EV_choices) # random.shuffle(EV_choices)
choices = DV_choices + OV_choices + EV_choices choices = DV_choices + OV_choices + EV_choices
choices = [(l[0], l[1]) for l in choices] choices = [(l[0], l[1]) for l in choices]
except IOError as e: except IOError as e:
logger.fatal("Unable to find .ca_offerings file") logger.fatal("Unable to find .ca_offerings file")
sys.exit(1) sys.exit(1)
@@ -544,14 +623,15 @@ class Client(object):
return choices return choices
def get_all_names(self): def get_all_names(self):
self.names = self.config.get_all_names() self.names = self.config.get_all_names()
if not self.names: if not self.names:
logger.fatal("No domain names were found in your apache config") logger.fatal("No domain names were found in your apache config")
logger.fatal("Either specify which names you would like trustify to validate or add server names to your virtual hosts") logger.fatal("Either specify which names you would like trustify \
to validate or add server names to your virtual hosts")
sys.exit(1) sys.exit(1)
def init_logger(self): def init_logger(self):
if self.curses: if self.curses:
logger.setLogger(logger.NcursesLogger()) logger.setLogger(logger.NcursesLogger())
@@ -562,7 +642,8 @@ class Client(object):
def sanity_check_names(self, names): def sanity_check_names(self, names):
for name in names: for name in names:
assert self.is_hostname_sane(name), `name` + " is an impossible hostname" if not self.is_hostname_sane(name):
logger.fatal(`name` + " is an impossible hostname")
def is_hostname_sane(self, hostname): def is_hostname_sane(self, hostname):
""" """
@@ -573,10 +654,10 @@ class Client(object):
allowed = s.ascii_letters + s.digits + "-." # hostnames & IPv4 allowed = s.ascii_letters + s.digits + "-." # hostnames & IPv4
if all([c in allowed for c in hostname]): if all([c in allowed for c in hostname]):
return True return True
if not allow_raw_ipv6_server: return False if not allow_raw_ipv6_server: return False
# ipv6 is messy and complicated, can contain %zoneindex etc. # ipv6 is messy and complicated, can contain %zoneindex etc.
import socket import socket
try: try:
# is this a valid IPv6 address? # is this a valid IPv6 address?
@@ -620,6 +701,4 @@ def renew(config):
# Wait for response, act accordingly # Wait for response, act accordingly
gen_req_from_cert() gen_req_from_cert()
# vim: set expandtab tabstop=4 shiftwidth=4 # vim: set expandtab tabstop=4 shiftwidth=4
+1 -1
View File
@@ -12,7 +12,7 @@ from trustify.client import logger
from trustify.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE from trustify.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE
def convert_b64_cert_to_pem(b64_der_cert): def b64_cert_to_pem(b64_der_cert):
x = M2Crypto.X509.load_cert_der_string(jose.b64decode_url(b64_der_cert)) x = M2Crypto.X509.load_cert_der_string(jose.b64decode_url(b64_der_cert))
return x.as_pem() return x.as_pem()
+2 -2
View File
@@ -100,11 +100,11 @@ class NcursesDisplay(Display):
str(c["not_before"])[:-6]) str(c["not_before"])[:-6])
for i, c in enumerate(certs)] for i, c in enumerate(certs)]
c, s = self.d.menu("Which certificate would you like to revoke?", code, s = self.d.menu("Which certificate would you like to revoke?",
choices = menu_choices, help_button=True, choices = menu_choices, help_button=True,
help_label="More Info", ok_label="Revoke", help_label="More Info", ok_label="Revoke",
width=WIDTH, height=HEIGHT) width=WIDTH, height=HEIGHT)
return c, s return code, s
def redirect_by_default(self): def redirect_by_default(self):