1
0
mirror of https://github.com/moepman/acertmgr.git synced 2024-06-14 10:32:34 +02:00

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) cr = tools.new_cert_request(settings['domainlist'], key)
print("Reading account key...") print("Reading account key...")
acme.register_account() 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: with io.open(crt_file, "w") as crt_fd:
crt_fd.write(tools.convert_cert_to_pem(crt)) crt_fd.write(tools.convert_cert_to_pem(crt))
@ -95,10 +95,9 @@ def cert_get(settings):
crt_final = settings['cert_file'] crt_final = settings['cert_file']
shutil.copy2(crt_file, crt_final) shutil.copy2(crt_file, crt_final)
os.chmod(crt_final, stat.S_IREAD) 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']: 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: finally:
os.remove(csr_file) os.remove(csr_file)
os.remove(crt_file) os.remove(crt_file)

View File

@ -97,7 +97,7 @@ class ACMEAuthority(AbstractACMEAuthority):
# @param csr the certificate signing request in pyopenssl format # @param csr the certificate signing request in pyopenssl format
# @param domains list of domains in the certificate, first is CN # @param domains list of domains in the certificate, first is CN
# @param challenge_handlers a dict containing challenge for all given domains # @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 # @note algorithm and parts of the code are from acme-tiny
def get_crt_from_csr(self, csr, domains, challenge_handlers): def get_crt_from_csr(self, csr, domains, challenge_handlers):
header = self._prepare_header() header = self._prepare_header()
@ -194,4 +194,4 @@ class ACMEAuthority(AbstractACMEAuthority):
# return signed certificate! # return signed certificate!
print("Certificate signed!") print("Certificate signed!")
cert = x509.load_der_x509_certificate(result, default_backend()) 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 csr the certificate signing request in pyopenssl format
# @param domains list of domains in the certificate, first is CN # @param domains list of domains in the certificate, first is CN
# @param challenge_handlers a dict containing challenge for all given domains # @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 # @note algorithm and parts of the code are from acme-tiny
def get_crt_from_csr(self, csr, domains, challenge_handlers): def get_crt_from_csr(self, csr, domains, challenge_handlers):
accountkey_json = json.dumps(self.account_protected['jwk'], sort_keys=True, separators=(',', ':')) accountkey_json = json.dumps(self.account_protected['jwk'], sort_keys=True, separators=(',', ':'))
@ -239,7 +239,7 @@ class ACMEAuthority(AbstractACMEAuthority):
time.sleep(5) time.sleep(5)
code, order, _ = self._request_url(order_url) code, order, _ = self._request_url(order_url)
if code >= 400: 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 # get the new certificate
print("Finalizing certificate") print("Finalizing certificate")
@ -257,6 +257,15 @@ class ACMEAuthority(AbstractACMEAuthority):
# return certificate # return certificate
code, certificate, _ = self._request_url(finalize['certificate'], raw_result=True) code, certificate, _ = self._request_url(finalize['certificate'], raw_result=True)
if code >= 400: if code >= 400:
raise ValueError("Error downloading certificate: {0} {1}".format(code, certificate)) raise ValueError("Error downloading certificate chain: {0} {1}".format(code, certificate))
cert = x509.load_pem_x509_certificate(certificate, default_backend())
return cert 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 # @brief download the issuer ca for a given certificate
# @param cert_file certificate file # @param cert certificate data
# @param ca_file destination for the ca file # @returns ca certificate data
def download_issuer_ca(cert_file, ca_file): def download_issuer_ca(cert):
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())
aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS) aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS)
ca_issuers = None ca_issuers = None
for data in aia.value: for data in aia.value:
if data.access_method == x509.OID_CA_ISSUERS: if data.access_method == x509.OID_CA_ISSUERS:
@ -118,14 +114,11 @@ def download_issuer_ca(cert_file, ca_file):
break break
if not ca_issuers: 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() cadata = urlopen(ca_issuers).read()
cacert = x509.load_der_x509_certificate(cadata, default_backend()) return 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)
# @brief convert certificate to PEM format # @brief convert certificate to PEM format