mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 16:19:13 +02:00
Finished refactoring client.py and also reduced column size to 80 through display and client
This commit is contained in:
+208
-129
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user