Commit e0742532 authored by Vincent Pelletier's avatar Vincent Pelletier

{cli,client}: Ignore CA certificates which fail loading.

Fixes cli.updater crashing when one of the locally-stored CA is expired.
Also, explicitly raise when there are CAs in the local trust store but all
fail loading.
parent f907890c
0.9.12 (2021-10-20)
===================
* Fix caucase-updater crashes after a local trust anchor CA expires.
0.9.11 (2021-10-07) 0.9.11 (2021-10-07)
=================== ===================
* Drop reliance on install-time 2to3 for py3 compatibility. Now the source is directly compatible with 2.7 and 3.x . * Drop reliance on install-time 2to3 for py3 compatibility. Now the source is directly compatible with 2.7 and 3.x .
......
...@@ -641,13 +641,12 @@ def main(argv=None, stdout=sys.stdout, stderr=sys.stderr): ...@@ -641,13 +641,12 @@ def main(argv=None, stdout=sys.stdout, stderr=sys.stderr):
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
) as client: ) as client:
ca_list = [ ca_list = utils.load_valid_ca_certificate_list(
utils.load_ca_certificate(x) ca_pem_list=utils.getCertList({
for x in utils.getCertList({
MODE_SERVICE: args.ca_crt, MODE_SERVICE: args.ca_crt,
MODE_USER: args.user_ca_crt, MODE_USER: args.user_ca_crt,
}[args.mode]) }[args.mode]),
] )
client.putCSR(args.send_csr) client.putCSR(args.send_csr)
client.getCSR(args.get_csr) client.getCSR(args.get_csr)
warning, error = client.getCRT(warning, error, args.get_crt, ca_list) warning, error = client.getCRT(warning, error, args.get_crt, ca_list)
...@@ -672,16 +671,22 @@ def main(argv=None, stdout=sys.stdout, stderr=sys.stderr): ...@@ -672,16 +671,22 @@ def main(argv=None, stdout=sys.stdout, stderr=sys.stderr):
if args.list_csr: if args.list_csr:
client.listCSR(args.mode) client.listCSR(args.mode)
# update our CRL after all revocations we were requested # update our CRL after all revocations we were requested
updated |= CaucaseClient.updateCRLFile(cas_url, args.crl, [ updated |= CaucaseClient.updateCRLFile(
utils.load_ca_certificate(x) cas_url,
for x in utils.getCertList(args.ca_crt) args.crl,
]) utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList(args.ca_crt),
),
)
# --update-user, CRL part # --update-user, CRL part
if args.update_user: if args.update_user:
updated |= CaucaseClient.updateCRLFile(cau_url, args.user_crl, [ updated |= CaucaseClient.updateCRLFile(
utils.load_ca_certificate(x) cau_url,
for x in utils.getCertList(args.user_ca_crt) args.user_crl,
]) utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList(args.user_ca_crt),
),
)
finished = True finished = True
finally: finally:
if updated and args.on_renew: if updated and args.on_renew:
...@@ -922,10 +927,9 @@ def updater(argv=None, until=utils.until): ...@@ -922,10 +927,9 @@ def updater(argv=None, until=utils.until):
# Note: CRL expiration should happen several time during CA renewal # Note: CRL expiration should happen several time during CA renewal
# period, so it should not be needed to keep track of CA expiration # period, so it should not be needed to keep track of CA expiration
# for next deadline. # for next deadline.
ca_crt_list = [ ca_crt_list = utils.load_valid_ca_certificate_list(
utils.load_ca_certificate(x) ca_pem_list=utils.getCertList(args.ca),
for x in utils.getCertList(args.ca) )
]
if RetryingCaucaseClient.updateCRLFile(ca_url, args.crl, ca_crt_list): if RetryingCaucaseClient.updateCRLFile(ca_url, args.crl, ca_crt_list):
print('Got new CRL') print('Got new CRL')
updated = True updated = True
......
...@@ -93,23 +93,25 @@ class CaucaseClient(object): ...@@ -93,23 +93,25 @@ class CaucaseClient(object):
certificate expired and was discarded). certificate expired and was discarded).
""" """
loaded_ca_pem_list = utils.getCertList(ca_crt_path) loaded_ca_pem_list = utils.getCertList(ca_crt_path)
if not loaded_ca_pem_list: if loaded_ca_pem_list:
updated = False
expect_valid_ca = True
else:
with cls(ca_url=url) as client: with cls(ca_url=url) as client:
utils.saveCertList(ca_crt_path, [client.getCACertificate()]) utils.saveCertList(ca_crt_path, [client.getCACertificate()])
updated = True updated = True
expect_valid_ca = False
# Note: reloading from file instead of using ca_pem, to exercise the # Note: reloading from file instead of using ca_pem, to exercise the
# same code path as future executions, to apply the same checks. # same code path as future executions, to apply the same checks.
loaded_ca_pem_list = utils.getCertList(ca_crt_path) loaded_ca_pem_list = utils.getCertList(ca_crt_path)
else: ca_pem_list = [
updated = False ca_pem
ca_pem_list = [] for ca_pem, _ in utils.iter_valid_ca_certificate_list(
for loaded_ca_pem in loaded_ca_pem_list: ca_pem_list=loaded_ca_pem_list,
try: )
utils.load_ca_certificate(loaded_ca_pem) ]
except exceptions.CertificateVerificationError: if expect_valid_ca and not ca_pem_list:
continue raise CaucaseError('Local trust store is unusable')
else:
ca_pem_list.append(loaded_ca_pem)
with cls(ca_url=url, ca_crt_pem_list=ca_pem_list) as client: with cls(ca_url=url, ca_crt_pem_list=ca_pem_list) as client:
ca_pem_list.extend(client.getCACertificateChain()) ca_pem_list.extend(client.getCACertificateChain())
if ca_pem_list != loaded_ca_pem_list: if ca_pem_list != loaded_ca_pem_list:
...@@ -316,9 +318,8 @@ class CaucaseClient(object): ...@@ -316,9 +318,8 @@ class CaucaseClient(object):
""" """
found = False found = False
previous_ca = trust_anchor = sorted( previous_ca = trust_anchor = sorted(
( utils.load_valid_ca_certificate_list(
utils.load_ca_certificate(x) ca_pem_list=self._ca_crt_pem_list,
for x in self._ca_crt_pem_list
), ),
key=lambda x: x.not_valid_before, key=lambda x: x.not_valid_before,
)[-1] )[-1]
...@@ -335,17 +336,27 @@ class CaucaseClient(object): ...@@ -335,17 +336,27 @@ class CaucaseClient(object):
except cryptography.exceptions.InvalidSignature: except cryptography.exceptions.InvalidSignature:
continue continue
if not found: if not found:
found = utils.load_ca_certificate( try:
utils.toBytes(payload['old_pem']), old_ca = utils.load_ca_certificate(
) == trust_anchor utils.toBytes(payload['old_pem']),
)
except exceptions.CertificateVerificationError:
# Expired CAs are allowed to appear before our trust anchor.
pass
else:
found = old_ca == trust_anchor
if found: if found:
if utils.load_ca_certificate( if utils.load_ca_certificate(
utils.toBytes(payload['old_pem']), utils.toBytes(payload['old_pem']),
) != previous_ca: ) != previous_ca:
raise ValueError('CA signature chain broken') raise ValueError('CA signature chain broken')
new_pem = utils.toBytes(payload['new_pem']) new_pem = utils.toBytes(payload['new_pem'])
result.append(new_pem) try:
previous_ca = utils.load_ca_certificate(new_pem) previous_ca = utils.load_ca_certificate(new_pem)
except exceptions.CertificateVerificationError:
pass
else:
result.append(new_pem)
return result return result
def renewCertificate(self, old_crt, old_key, key_len): def renewCertificate(self, old_crt, old_key, key_len):
......
...@@ -1806,6 +1806,20 @@ class CaucaseTest(TestCase): ...@@ -1806,6 +1806,20 @@ class CaucaseTest(TestCase):
[new_cau_pem], [new_cau_pem],
utils.getCertList(self._client_user_ca_crt), utils.getCertList(self._client_user_ca_crt),
) )
# A client with only expired trust anchor does not get a new CA
utils.saveCertList(
self._client_user_ca_crt,
[old_cau_pem],
)
self.assertRaises(
CaucaseError,
self._runClient,
'--mode', 'user', '--update-user',
)
self.assertItemsEqual(
[old_cau_pem],
utils.getCertList(self._client_user_ca_crt),
)
def testCaucasedCRLRenewal(self): def testCaucasedCRLRenewal(self):
""" """
......
...@@ -513,6 +513,34 @@ def load_ca_certificate(data): ...@@ -513,6 +513,34 @@ def load_ca_certificate(data):
_verifyCertificateChain(crt, [crt], None) _verifyCertificateChain(crt, [crt], None)
return crt return crt
def iter_valid_ca_certificate_list(ca_pem_list):
"""
Load multiple CA certificates from a list of PEM-encoded values.
Yields the PEM-encoded value along with the loaded CA.
Skips items failing to load.
"""
for ca_pem in ca_pem_list:
try:
ca = load_ca_certificate(ca_pem)
except CertificateVerificationError:
continue
else:
yield (ca_pem, ca)
def load_valid_ca_certificate_list(ca_pem_list):
"""
Load CA certificates from PEM-encoded data, skipping items which fail
signature verification.
Returns the list of loaded CA certificates.
"""
return [
ca
for _, ca in iter_valid_ca_certificate_list(ca_pem_list)
]
def load_certificate(data, trusted_cert_list, crl_list): def load_certificate(data, trusted_cert_list, crl_list):
""" """
Load a certificate from PEM-encoded data. Load a certificate from PEM-encoded data.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment