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.
This commit is contained in:
Kishi85 2019-03-21 12:18:49 +01:00
parent 316ecdba2e
commit c054ecebe9
4 changed files with 25 additions and 24 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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<cert>-----BEGIN CERTIFICATE-----[^\-]+-----END CERTIFICATE-----)\n\n"
"(?P<ca>-----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

View File

@ -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