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)
===================
* 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):
stdout=stdout,
stderr=stderr,
) as client:
ca_list = [
utils.load_ca_certificate(x)
for x in utils.getCertList({
ca_list = utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList({
MODE_SERVICE: args.ca_crt,
MODE_USER: args.user_ca_crt,
}[args.mode])
]
}[args.mode]),
)
client.putCSR(args.send_csr)
client.getCSR(args.get_csr)
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):
if args.list_csr:
client.listCSR(args.mode)
# update our CRL after all revocations we were requested
updated |= CaucaseClient.updateCRLFile(cas_url, args.crl, [
utils.load_ca_certificate(x)
for x in utils.getCertList(args.ca_crt)
])
updated |= CaucaseClient.updateCRLFile(
cas_url,
args.crl,
utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList(args.ca_crt),
),
)
# --update-user, CRL part
if args.update_user:
updated |= CaucaseClient.updateCRLFile(cau_url, args.user_crl, [
utils.load_ca_certificate(x)
for x in utils.getCertList(args.user_ca_crt)
])
updated |= CaucaseClient.updateCRLFile(
cau_url,
args.user_crl,
utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList(args.user_ca_crt),
),
)
finished = True
finally:
if updated and args.on_renew:
......@@ -922,10 +927,9 @@ def updater(argv=None, until=utils.until):
# Note: CRL expiration should happen several time during CA renewal
# period, so it should not be needed to keep track of CA expiration
# for next deadline.
ca_crt_list = [
utils.load_ca_certificate(x)
for x in utils.getCertList(args.ca)
]
ca_crt_list = utils.load_valid_ca_certificate_list(
ca_pem_list=utils.getCertList(args.ca),
)
if RetryingCaucaseClient.updateCRLFile(ca_url, args.crl, ca_crt_list):
print('Got new CRL')
updated = True
......
......@@ -93,23 +93,25 @@ class CaucaseClient(object):
certificate expired and was discarded).
"""
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:
utils.saveCertList(ca_crt_path, [client.getCACertificate()])
updated = True
expect_valid_ca = False
# Note: reloading from file instead of using ca_pem, to exercise the
# same code path as future executions, to apply the same checks.
loaded_ca_pem_list = utils.getCertList(ca_crt_path)
else:
updated = False
ca_pem_list = []
for loaded_ca_pem in loaded_ca_pem_list:
try:
utils.load_ca_certificate(loaded_ca_pem)
except exceptions.CertificateVerificationError:
continue
else:
ca_pem_list.append(loaded_ca_pem)
ca_pem_list = [
ca_pem
for ca_pem, _ in utils.iter_valid_ca_certificate_list(
ca_pem_list=loaded_ca_pem_list,
)
]
if expect_valid_ca and not ca_pem_list:
raise CaucaseError('Local trust store is unusable')
with cls(ca_url=url, ca_crt_pem_list=ca_pem_list) as client:
ca_pem_list.extend(client.getCACertificateChain())
if ca_pem_list != loaded_ca_pem_list:
......@@ -316,9 +318,8 @@ class CaucaseClient(object):
"""
found = False
previous_ca = trust_anchor = sorted(
(
utils.load_ca_certificate(x)
for x in self._ca_crt_pem_list
utils.load_valid_ca_certificate_list(
ca_pem_list=self._ca_crt_pem_list,
),
key=lambda x: x.not_valid_before,
)[-1]
......@@ -335,17 +336,27 @@ class CaucaseClient(object):
except cryptography.exceptions.InvalidSignature:
continue
if not found:
found = utils.load_ca_certificate(
utils.toBytes(payload['old_pem']),
) == trust_anchor
try:
old_ca = utils.load_ca_certificate(
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 utils.load_ca_certificate(
utils.toBytes(payload['old_pem']),
) != previous_ca:
raise ValueError('CA signature chain broken')
new_pem = utils.toBytes(payload['new_pem'])
result.append(new_pem)
previous_ca = utils.load_ca_certificate(new_pem)
try:
previous_ca = utils.load_ca_certificate(new_pem)
except exceptions.CertificateVerificationError:
pass
else:
result.append(new_pem)
return result
def renewCertificate(self, old_crt, old_key, key_len):
......
......@@ -1806,6 +1806,20 @@ class CaucaseTest(TestCase):
[new_cau_pem],
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):
"""
......
......@@ -513,6 +513,34 @@ def load_ca_certificate(data):
_verifyCertificateChain(crt, [crt], None)
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):
"""
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