mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 00:35:50 +02:00
Added namedtuples for key, csr in client
This commit is contained in:
@@ -196,7 +196,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
def choose_virtual_host(self, target_name):
|
||||
""" Chooses a virtual host based on the given domain name.
|
||||
|
||||
.. todo:: This should maybe return list if no obvious answer is presented
|
||||
.. todo:: This should maybe return list if no obvious answer
|
||||
is presented.
|
||||
|
||||
:param str name: domain name
|
||||
|
||||
@@ -286,7 +287,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
|
||||
.. todo:: This will have to be updated for other distros versions
|
||||
|
||||
:param str filename: optional filename that will be used as the user config
|
||||
:param str filename: optional filename that will be used as the
|
||||
user config
|
||||
|
||||
"""
|
||||
if filename:
|
||||
@@ -1253,12 +1255,12 @@ LogLevel warn \n\
|
||||
"""Peform a DVSNI challenge.
|
||||
|
||||
Composed of
|
||||
list_sni_tuple: List of tuples with form (addr, r, nonce)
|
||||
addr (string), r (base64 string), nonce (hex string)
|
||||
dvsni_key: string - File path to key
|
||||
list_sni_tuple: List of tuples with form (addr, r, nonce)
|
||||
addr (string), r (base64 string), nonce (hex string)
|
||||
|
||||
:param chall_dict: dvsni challenge - see documentation
|
||||
:type chall_dict: dict
|
||||
dvsni_key: namedtuple - client.Client.Key()
|
||||
|
||||
:param dict chall_dict: dvsni challenge - see documentation
|
||||
|
||||
"""
|
||||
# Save any changes to the configuration as a precaution
|
||||
@@ -1326,10 +1328,11 @@ LogLevel warn \n\
|
||||
Result: Apache config includes virtual servers for issued challs
|
||||
|
||||
:param list_sni_tuple: list of tuples with the form (addr, y, nonce)
|
||||
addr (string), y (byte array), nonce (hex str)
|
||||
addr (string), y (byte array), nonce (hex str)
|
||||
:type list_sni_tuple: list
|
||||
|
||||
:param str dvsni_key: file path to key
|
||||
:param dvsni_key: Namedtuple with file, pem
|
||||
:type dvsni_key: `client.Client.Key` - namedtuple
|
||||
|
||||
:param list ll_addrs: list of list of addresses to apply
|
||||
|
||||
@@ -1349,7 +1352,7 @@ LogLevel warn \n\
|
||||
config_text = "<IfModule mod_ssl.c> \n"
|
||||
for idx, lis in enumerate(ll_addrs):
|
||||
config_text += get_config_text(
|
||||
list_sni_tuple[idx][2], lis, dvsni_key)
|
||||
list_sni_tuple[idx][2], lis, dvsni_key.file)
|
||||
config_text += "</IfModule> \n"
|
||||
|
||||
self.dvsni_conf_include_check(self.user_config_file)
|
||||
@@ -1373,26 +1376,21 @@ LogLevel warn \n\
|
||||
self.add_dir("/files" + main_config,
|
||||
"Include", CONFIG.APACHE_CHALLENGE_CONF)
|
||||
|
||||
def dvsni_create_chall_cert(self, name, ext, nonce, key_file):
|
||||
def dvsni_create_chall_cert(self, name, ext, nonce, dvsni_key):
|
||||
"""Creates DVSNI challenge certifiate.
|
||||
|
||||
Certificate created at dvsni_get_cert_file(nonce)
|
||||
|
||||
:param str nonce: hex form of nonce
|
||||
:param str key_file: absolute path to key file
|
||||
|
||||
:param dvsni_key: absolute path to key file
|
||||
:type dvsni_key: `client.Client.Key`
|
||||
|
||||
"""
|
||||
try:
|
||||
with open(key_file, 'r') as key_fd:
|
||||
key_str = key_fd.read()
|
||||
except IOError:
|
||||
raise errors.LetsEncryptDvsniError(
|
||||
"Unable to load key file: %s" % key_file)
|
||||
|
||||
self.register_file_creation(True, dvsni_get_cert_file(nonce))
|
||||
|
||||
cert_pem = crypto_util.make_ss_cert(
|
||||
key_str, [nonce + CONFIG.INVALID_EXT, name, ext])
|
||||
dvsni_key.pem, [nonce + CONFIG.INVALID_EXT, name, ext])
|
||||
|
||||
with open(dvsni_get_cert_file(nonce), 'w') as chall_cert_file:
|
||||
chall_cert_file.write(cert_pem)
|
||||
@@ -1513,7 +1511,7 @@ def strip_dir(path):
|
||||
.. todo:: Replace this with Python standard function
|
||||
|
||||
:param str path: path is a file path. not an augeas section or
|
||||
directive path
|
||||
directive path
|
||||
|
||||
:returns: directory
|
||||
:rtype: str
|
||||
@@ -1539,7 +1537,7 @@ def dvsni_get_cert_file(nonce):
|
||||
return CONFIG.WORK_DIR + nonce + ".crt"
|
||||
|
||||
|
||||
def get_config_text(nonce, ip_addrs, key):
|
||||
def get_config_text(nonce, ip_addrs, dvsni_key_file):
|
||||
"""Chocolate virtual server configuration text
|
||||
|
||||
:param nonce: hex form of nonce
|
||||
@@ -1548,8 +1546,7 @@ def get_config_text(nonce, ip_addrs, key):
|
||||
:param ip_addrs: addresses of challenged domain
|
||||
:type ip_addrs: str
|
||||
|
||||
:param key: file path to key
|
||||
:type key: str
|
||||
:param str dvsni_key_file: Path to key file
|
||||
|
||||
:returns: virtual host configuration text
|
||||
:rtype: str
|
||||
@@ -1564,7 +1561,7 @@ def get_config_text(nonce, ip_addrs, key):
|
||||
"\n"
|
||||
"Include " + CONFIG.OPTIONS_SSL_CONF + " \n"
|
||||
"SSLCertificateFile " + dvsni_get_cert_file(nonce) + " \n"
|
||||
"SSLCertificateKeyFile " + key + " \n"
|
||||
"SSLCertificateKeyFile " + dvsni_key_file + " \n"
|
||||
"\n"
|
||||
"DocumentRoot " + CONFIG.CONFIG_DIR + "challenge_page/ \n"
|
||||
"</VirtualHost> \n\n")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""ACME protocol client class and helper functions."""
|
||||
import collections
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
@@ -31,15 +32,16 @@ ALLOW_RAW_IPV6_SERVER = False
|
||||
|
||||
class Client(object):
|
||||
"""ACME protocol client."""
|
||||
Key = collections.namedtuple("Key", "file pem")
|
||||
CSR = collections.namedtuple("CSR", "file data type")
|
||||
|
||||
def __init__(self, ca_server, cert_signing_request=None,
|
||||
private_key=None, private_key_file=None, use_curses=True):
|
||||
def __init__(self, ca_server, cert_signing_request=CSR(None, None, None),
|
||||
private_key=Key(None, None), use_curses=True):
|
||||
"""
|
||||
|
||||
:param str ca_server: Certificate authority server
|
||||
:param str cert_signing_request: Contents of the CSR
|
||||
:param str private_key: Contents of the private key
|
||||
:param str private_key_file: absolute path to private_key
|
||||
:param bool use_curses: Use curses UI
|
||||
|
||||
"""
|
||||
@@ -53,9 +55,10 @@ class Client(object):
|
||||
self.config = apache_configurator.ApacheConfigurator(
|
||||
CONFIG.SERVER_ROOT)
|
||||
self.server = ca_server
|
||||
|
||||
# These are CSR/Key namedtuples
|
||||
self.csr = cert_signing_request
|
||||
self.privkey = private_key
|
||||
self.privkey_file = private_key_file
|
||||
|
||||
# TODO: Figure out all exceptions from this function
|
||||
try:
|
||||
@@ -115,11 +118,11 @@ class Client(object):
|
||||
# Request Challenges
|
||||
challenge_msg = self.acme_challenge()
|
||||
|
||||
# Get key and csr to perform challenges
|
||||
_, csr_der = self.get_key_csr_pem()
|
||||
# Make sure we have key and csr to perform challenges
|
||||
self.init_key_csr()
|
||||
|
||||
# TODO: Handle this exception/problem
|
||||
if not crypto_util.csr_matches_names(self.csr, self.names):
|
||||
if not crypto_util.csr_matches_names(self.csr.data, self.names):
|
||||
raise errors.LetsEncryptClientError(
|
||||
"CSR subject does not contain one of the specified names")
|
||||
|
||||
@@ -129,7 +132,7 @@ class Client(object):
|
||||
self.acme_authorization(challenge_msg, challenge_objs, responses)
|
||||
|
||||
# Retrieve certificate
|
||||
certificate_dict = self.acme_certificate(csr_der)
|
||||
certificate_dict = self.acme_certificate(self.csr.data)
|
||||
|
||||
# Find set of virtual hosts to deploy certificates to
|
||||
vhost = self.get_virtual_hosts(self.names)
|
||||
@@ -170,7 +173,7 @@ class Client(object):
|
||||
"""
|
||||
auth_dict = self.send(acme.authorization_request(
|
||||
challenge_msg["sessionID"], self.names[0],
|
||||
challenge_msg["nonce"], responses, self.privkey))
|
||||
challenge_msg["nonce"], responses, self.privkey.pem))
|
||||
|
||||
try:
|
||||
return self.is_expected_msg(auth_dict, "authorization")
|
||||
@@ -192,7 +195,7 @@ class Client(object):
|
||||
"""
|
||||
logger.info("Preparing and sending CSR..")
|
||||
return self.send_and_receive_expected(
|
||||
acme.certificate_request(csr_der, self.privkey), "certificate")
|
||||
acme.certificate_request(csr_der, self.privkey.pem), "certificate")
|
||||
|
||||
def acme_revocation(self, cert):
|
||||
"""Handle ACME "revocation" phase.
|
||||
@@ -411,7 +414,7 @@ class Client(object):
|
||||
for host in vhost:
|
||||
self.config.deploy_cert(host,
|
||||
os.path.abspath(cert_file),
|
||||
os.path.abspath(self.privkey_file),
|
||||
os.path.abspath(self.privkey.file),
|
||||
cert_chain_abspath)
|
||||
# Enable any vhost that was issued to, but not enabled
|
||||
if not host.enabled:
|
||||
@@ -519,17 +522,17 @@ class Client(object):
|
||||
for row in csvreader:
|
||||
idx = int(row[0]) + 1
|
||||
csvwriter = csv.writer(csvfile)
|
||||
csvwriter.writerow([str(idx), cert_file, self.privkey_file])
|
||||
csvwriter.writerow([str(idx), cert_file, self.privkey.file])
|
||||
|
||||
else:
|
||||
with open(list_file, 'wb') as csvfile:
|
||||
csvwriter = csv.writer(csvfile)
|
||||
csvwriter.writerow(["0", cert_file, self.privkey_file])
|
||||
csvwriter.writerow(["0", cert_file, self.privkey.file])
|
||||
|
||||
shutil.copy2(self.privkey_file,
|
||||
shutil.copy2(self.privkey.file,
|
||||
os.path.join(
|
||||
CONFIG.CERT_KEY_BACKUP,
|
||||
os.path.basename(self.privkey_file) + "_" + str(idx)))
|
||||
os.path.basename(self.privkey.file) + "_" + str(idx)))
|
||||
shutil.copy2(cert_file,
|
||||
os.path.join(
|
||||
CONFIG.CERT_KEY_BACKUP,
|
||||
@@ -602,34 +605,24 @@ class Client(object):
|
||||
challenge_objs.append({
|
||||
"type": "dvsni",
|
||||
"list_sni_tuple": sni_todo,
|
||||
"dvsni_key": os.path.abspath(self.privkey_file),
|
||||
"dvsni_key": self.privkey,
|
||||
})
|
||||
challenge_obj_indices.append(sni_satisfies)
|
||||
logger.debug(sni_todo)
|
||||
|
||||
return challenge_objs, challenge_obj_indices
|
||||
|
||||
def get_key_csr_pem(self, csr_return_format='der'):
|
||||
"""Return key and CSR, generate if necessary.
|
||||
def init_key_csr(self):
|
||||
"""Initializes privkey and csr.
|
||||
|
||||
Returns key and CSR using provided files or generating new files
|
||||
Inits key and CSR using provided files or generating new files
|
||||
if necessary. Both will be saved in PEM format on the
|
||||
filesystem. The CSR can optionally be returned in DER format as
|
||||
the CSR cannot be loaded back into M2Crypto.
|
||||
|
||||
:param csr_return_format: If "der" returned CSR is in DER format,
|
||||
PEM otherwise.
|
||||
:param csr_return_format: str
|
||||
|
||||
:returns: A pair of `(key, csr)`, where `key` is PEM encoded `str`
|
||||
and `csr` is PEM/DER (depedning on `csr_return_format`
|
||||
encoded `str`.
|
||||
:rtype: tuple
|
||||
filesystem. The CSR is placed into DER format to allow
|
||||
the namedtuple to easily work with the protocol.
|
||||
|
||||
"""
|
||||
if not self.privkey:
|
||||
if not self.privkey.file:
|
||||
key_pem = crypto_util.make_key(CONFIG.RSA_KEY_SIZE)
|
||||
self.privkey = key_pem
|
||||
|
||||
# Save file
|
||||
le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0o700)
|
||||
@@ -638,14 +631,13 @@ class Client(object):
|
||||
key_f.write(key_pem)
|
||||
key_f.close()
|
||||
|
||||
self.privkey_file = key_filename
|
||||
logger.info("Generating key: %s" % self.privkey_file)
|
||||
else:
|
||||
key_pem = self.privkey
|
||||
logger.info("Generating key: %s" % key_filename)
|
||||
|
||||
if not self.csr:
|
||||
csr_pem, csr_der = crypto_util.make_csr(self.privkey, self.names)
|
||||
self.csr = csr_pem
|
||||
self.privkey = Client.Key(key_filename, key_pem)
|
||||
|
||||
if not self.csr.file:
|
||||
csr_pem, csr_der = crypto_util.make_csr(
|
||||
self.privkey.pem, self.names)
|
||||
|
||||
# Save CSR
|
||||
le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0o755)
|
||||
@@ -653,15 +645,16 @@ class Client(object):
|
||||
os.path.join(CONFIG.CERT_DIR, "csr-letsencrypt.pem"), 0o644)
|
||||
csr_f.write(csr_pem)
|
||||
csr_f.close()
|
||||
logger.info("Creating CSR: %s" % csr_filename)
|
||||
else:
|
||||
csr_obj = M2Crypto.X509.load_request_string(self.csr)
|
||||
csr_pem, csr_der = csr_obj.as_pem(), csr_obj.as_der()
|
||||
|
||||
if csr_return_format == 'der':
|
||||
return key_pem, csr_der
|
||||
else:
|
||||
return key_pem, csr_pem
|
||||
logger.info("Creating CSR: %s" % csr_filename)
|
||||
|
||||
self.csr = Client.CSR(csr_filename, csr_der, "der")
|
||||
elif self.csr.type != "der":
|
||||
# The user is going to pass in a pem format file
|
||||
# That is why we must conver it to der since the
|
||||
# protocol uses der exclusively.
|
||||
csr_obj = M2Crypto.X509.load_request_string(self.csr.data)
|
||||
self.csr = Client.CSR(self.csr.file, csr_obj.as_der(), "der")
|
||||
|
||||
def _validate_csr_key_cli(self):
|
||||
"""Validate CSR and key files.
|
||||
@@ -677,19 +670,21 @@ class Client(object):
|
||||
# and allow the user to take more appropriate actions
|
||||
|
||||
# If CSR is provided, it must be readable and valid.
|
||||
if self.csr and not crypto_util.valid_csr(self.csr):
|
||||
if self.csr.data and not crypto_util.valid_csr(self.csr.data):
|
||||
raise errors.LetsEncryptClientError(
|
||||
"The provided CSR is not a valid CSR")
|
||||
|
||||
# If key is provided, it must be readable and valid.
|
||||
if self.privkey and not crypto_util.valid_privkey(self.privkey):
|
||||
if (self.privkey.pem and
|
||||
not crypto_util.valid_privkey(self.privkey.pem)):
|
||||
raise errors.LetsEncryptClientError(
|
||||
"The provided key is not a valid key")
|
||||
|
||||
# If CSR and key are provided, the key must be the same key used
|
||||
# in the CSR.
|
||||
if self.csr and self.privkey:
|
||||
if not crypto_util.csr_matches_pubkey(self.csr, self.privkey):
|
||||
if self.csr.data and self.privkey.pem:
|
||||
if not crypto_util.csr_matches_pubkey(
|
||||
self.csr.data, self.privkey.pem):
|
||||
raise errors.LetsEncryptClientError(
|
||||
"The key and CSR do not match")
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ def csr_matches_names(csr, domains):
|
||||
|
||||
"""
|
||||
try:
|
||||
csr_obj = M2Crypto.X509.load_request_string(csr)
|
||||
csr_obj = M2Crypto.X509.load_request_der_string(csr)
|
||||
return csr_obj.get_subject().CN in domains
|
||||
except M2Crypto.X509.X509Error:
|
||||
return False
|
||||
|
||||
@@ -28,10 +28,10 @@ def main():
|
||||
nargs="+")
|
||||
parser.add_argument("-s", "--server", dest="server",
|
||||
help="The ACME CA server address.")
|
||||
parser.add_argument("-p", "--privkey", dest="privkey_tup", type=read_file,
|
||||
parser.add_argument("-p", "--privkey", dest="privkey", type=read_file,
|
||||
help="Path to the private key file for certificate "
|
||||
"generation.")
|
||||
parser.add_argument("-c", "--csr", dest="csr_tup", type=read_file,
|
||||
parser.add_argument("-c", "--csr", dest="csr", type=read_file,
|
||||
help="Path to the certificate signing request file "
|
||||
"corresponding to the private key file. The "
|
||||
"private key file argument is required if this "
|
||||
@@ -63,7 +63,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# Enforce '--privkey' is set along with '--csr'.
|
||||
if args.csr_tup and not args.privkey_tup:
|
||||
if args.csr and not args.privkey:
|
||||
parser.error("private key file (--privkey) must be specified along{0} "
|
||||
"with the certificate signing request file (--csr)"
|
||||
.format(os.linesep))
|
||||
@@ -84,13 +84,16 @@ def main():
|
||||
server = args.server is None and CONFIG.ACME_SERVER or args.server
|
||||
|
||||
# Prepare for init of Client
|
||||
if args.privkey_tup is None:
|
||||
args.privkey_tup = (None, None)
|
||||
if args.csr_tup is None:
|
||||
args.csr_tup = (None, None)
|
||||
if args.privkey is None:
|
||||
privkey = client.Client.Key(None, None)
|
||||
else:
|
||||
privkey = client.Client.Key(args.privkey[0], args.privkey[1])
|
||||
if args.csr is None:
|
||||
csr = client.Client.CSR(None, None, None)
|
||||
else:
|
||||
csr = client.Client.CSR(args.csr[0], args.csr[1], "pem")
|
||||
|
||||
acme = client.Client(server, args.csr_tup[1], args.privkey_tup[1],
|
||||
args.privkey_tup[0], args.curses)
|
||||
acme = client.Client(server, csr, privkey, args.curses)
|
||||
if args.revoke:
|
||||
acme.list_certs_keys()
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user