From c054ecebe908d6c0a231a53f437622e9cd30cb20 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Thu, 21 Mar 2019 12:18:49 +0100 Subject: [PATCH] acertmgr: change the way the issuer CA is fetched This changes the way the issuer CA is retrieved if no static_ca file is used. Previously we would always download the CA using the AIA Info but API v2 provides normally the full chain PEM upon certificate retrieval and does not need this step. For the APIv2 case we now use the CA provided with the certificate which required some changes to the basic handling of CA files. APIv1 has been adapted to this new handling. APIv2 has a fallback option to the way APIv1 handles it in case no CA has been provided. --- acertmgr/__init__.py | 7 +++---- acertmgr/authority/v1.py | 4 ++-- acertmgr/authority/v2.py | 19 ++++++++++++++----- acertmgr/tools.py | 19 ++++++------------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/acertmgr/__init__.py b/acertmgr/__init__.py index af4a9a3..7e30be3 100755 --- a/acertmgr/__init__.py +++ b/acertmgr/__init__.py @@ -86,7 +86,7 @@ def cert_get(settings): cr = tools.new_cert_request(settings['domainlist'], key) print("Reading account key...") acme.register_account() - crt = acme.get_crt_from_csr(cr, settings['domainlist'], challenge_handlers) + crt, ca = acme.get_crt_from_csr(cr, settings['domainlist'], challenge_handlers) with io.open(crt_file, "w") as crt_fd: crt_fd.write(tools.convert_cert_to_pem(crt)) @@ -95,10 +95,9 @@ def cert_get(settings): crt_final = settings['cert_file'] shutil.copy2(crt_file, crt_final) os.chmod(crt_final, stat.S_IREAD) - # download current ca file for the new certificate if no static ca is configured if "static_ca" in settings and not settings['static_ca']: - tools.download_issuer_ca(crt_final, settings['ca_file']) - + with io.open(settings['ca_file'], "w") as ca_fd: + ca_fd.write(tools.convert_cert_to_pem(ca)) finally: os.remove(csr_file) os.remove(crt_file) diff --git a/acertmgr/authority/v1.py b/acertmgr/authority/v1.py index 72807fa..1c39687 100644 --- a/acertmgr/authority/v1.py +++ b/acertmgr/authority/v1.py @@ -97,7 +97,7 @@ class ACMEAuthority(AbstractACMEAuthority): # @param csr the certificate signing request in pyopenssl format # @param domains list of domains in the certificate, first is CN # @param challenge_handlers a dict containing challenge for all given domains - # @return the certificate in pyopenssl format + # @return the certificate and corresponding ca as a tuple # @note algorithm and parts of the code are from acme-tiny def get_crt_from_csr(self, csr, domains, challenge_handlers): header = self._prepare_header() @@ -194,4 +194,4 @@ class ACMEAuthority(AbstractACMEAuthority): # return signed certificate! print("Certificate signed!") cert = x509.load_der_x509_certificate(result, default_backend()) - return cert + return cert, tools.download_issuer_ca(cert) diff --git a/acertmgr/authority/v2.py b/acertmgr/authority/v2.py index 809e78b..756351c 100644 --- a/acertmgr/authority/v2.py +++ b/acertmgr/authority/v2.py @@ -146,7 +146,7 @@ class ACMEAuthority(AbstractACMEAuthority): # @param csr the certificate signing request in pyopenssl format # @param domains list of domains in the certificate, first is CN # @param challenge_handlers a dict containing challenge for all given domains - # @return the certificate + # @return the certificate and corresponding ca as a tuple # @note algorithm and parts of the code are from acme-tiny def get_crt_from_csr(self, csr, domains, challenge_handlers): accountkey_json = json.dumps(self.account_protected['jwk'], sort_keys=True, separators=(',', ':')) @@ -239,7 +239,7 @@ class ACMEAuthority(AbstractACMEAuthority): time.sleep(5) code, order, _ = self._request_url(order_url) if code >= 400: - raise ValueError("Order is still pending: {0} {1}".format(code, order)) + raise ValueError("Order is still not ready to be finalized: {0} {1}".format(code, order)) # get the new certificate print("Finalizing certificate") @@ -257,6 +257,15 @@ class ACMEAuthority(AbstractACMEAuthority): # return certificate code, certificate, _ = self._request_url(finalize['certificate'], raw_result=True) if code >= 400: - raise ValueError("Error downloading certificate: {0} {1}".format(code, certificate)) - cert = x509.load_pem_x509_certificate(certificate, default_backend()) - return cert + raise ValueError("Error downloading certificate chain: {0} {1}".format(code, certificate)) + + cert_dict = re.match(("(?P-----BEGIN CERTIFICATE-----[^\-]+-----END CERTIFICATE-----)\n\n" + "(?P-----BEGIN CERTIFICATE-----[^\-]+-----END CERTIFICATE-----)?"), + certificate.decode('utf-8'), re.DOTALL).groupdict() + cert = x509.load_pem_x509_certificate(cert_dict['cert'].encode('utf-8'), default_backend()) + if cert_dict['ca'] is None: + ca = tools.download_issuer_ca(cert) + else: + ca = x509.load_pem_x509_certificate(cert_dict['ca'].encode('utf-8'), default_backend()) + + return cert, ca diff --git a/acertmgr/tools.py b/acertmgr/tools.py index 411aa92..4dc33ab 100644 --- a/acertmgr/tools.py +++ b/acertmgr/tools.py @@ -103,14 +103,10 @@ def new_rsa_key(path, key_size=4096): # @brief download the issuer ca for a given certificate -# @param cert_file certificate file -# @param ca_file destination for the ca file -def download_issuer_ca(cert_file, ca_file): - with io.open(cert_file, 'r') as f: - cert_data = f.read().encode('utf-8') - cert = x509.load_pem_x509_certificate(cert_data, default_backend()) +# @param cert certificate data +# @returns ca certificate data +def download_issuer_ca(cert): aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS) - ca_issuers = None for data in aia.value: if data.access_method == x509.OID_CA_ISSUERS: @@ -118,14 +114,11 @@ def download_issuer_ca(cert_file, ca_file): break if not ca_issuers: - raise Exception("Could not determine issuer CA for {}".format(cert_file)) + raise Exception("Could not determine issuer CA for given certificate: {}".format(cert)) - print("Downloading CA certificate from {} to {}".format(ca_issuers, ca_file)) + print("Downloading CA certificate from {}".format(ca_issuers)) cadata = urlopen(ca_issuers).read() - cacert = x509.load_der_x509_certificate(cadata, default_backend()) - pem = cacert.public_bytes(encoding=serialization.Encoding.PEM) - with io.open(ca_file, 'wb') as pem_out: - pem_out.write(pem) + return x509.load_der_x509_certificate(cadata, default_backend()) # @brief convert certificate to PEM format