Skip to content

Commit 0f9fc10

Browse files
committed
Support basic constraints/key usage checks
1 parent 8933faa commit 0f9fc10

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

autograph_utils/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,25 @@ def detail(self):
195195
)
196196

197197

198+
class CertificateCannotSign(BadCertificate):
199+
"""For intermediate/root certificates that do not have the proper
200+
metainformation bits saying that they can be used to sign
201+
signatures.
202+
203+
"""
204+
205+
def __init__(self, cert, extra):
206+
self.cert = cert
207+
self.extra = extra
208+
209+
@property
210+
def detail(self):
211+
return (
212+
"Certificate cannot be used for signing "
213+
f"because {self.extra}: {self.cert!r}"
214+
)
215+
216+
198217
class CertificateLeafHasWrongKeyUsage(BadCertificate):
199218
def __init__(self, cert, key_usage):
200219
self.cert = cert
@@ -344,6 +363,7 @@ async def verify_x5u(self, url):
344363

345364
current_cert = chain[0]
346365
for next_cert in chain[1:]:
366+
self._check_can_sign_other_certs(current_cert)
347367
self._verify_cert_link(current_cert, next_cert)
348368
self._check_name_constraints(current_cert, next_cert)
349369

@@ -423,6 +443,20 @@ def _check_name_constraints(self, current_cert, next_cert):
423443
nc.excluded_subtrees, current=current_cert, next=next_cert
424444
)
425445

446+
def _check_can_sign_other_certs(self, cert):
447+
basic = cert.extensions.get_extension_for_class(
448+
cryptography.x509.BasicConstraints
449+
).value
450+
if not basic.ca:
451+
raise CertificateCannotSign(cert, "ca is false")
452+
453+
usage = cert.extensions.get_extension_for_class(
454+
cryptography.x509.KeyUsage
455+
).value
456+
usage_is_ok = usage.key_cert_sign and usage.crl_sign
457+
if not usage_is_ok:
458+
raise CertificateCannotSign(cert, "key usage is incomplete")
459+
426460

427461
def _name_constraint_matches(hostname, name_constraint):
428462
"""Check if a name matches a constraint.

tests/test_autograph_utils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,60 @@ async def test_verify_name_constraints_excludes(
391391
assert excinfo.value.next == leaf
392392

393393

394+
async def test_verify_basic_constraints_must_have_ca(
395+
aiohttp_session, mock_with_x5u, cache, now_fixed
396+
):
397+
certs = [
398+
cryptography.x509.load_pem_x509_certificate(pem, backend=default_backend())
399+
for pem in STAGE_CERT_LIST
400+
]
401+
real_intermediate = certs[1]
402+
intermediate = mock_cert(real_intermediate)
403+
basic_mock = mock.Mock()
404+
basic_mock.ca = False
405+
mock_cert_extension(intermediate, cryptography.x509.BasicConstraints, basic_mock)
406+
certs[1] = intermediate
407+
408+
with mock.patch("cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
409+
load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
410+
s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
411+
with pytest.raises(autograph_utils.CertificateCannotSign) as excinfo:
412+
await s.verify_x5u(FAKE_CERT_URL)
413+
414+
assert excinfo.value.detail.startswith(
415+
"Certificate cannot be used for signing because "
416+
)
417+
assert excinfo.value.cert == intermediate
418+
assert excinfo.value.extra == "ca is false"
419+
420+
421+
async def test_verify_basic_constraints_must_have_cert_signing(
422+
aiohttp_session, mock_with_x5u, cache, now_fixed
423+
):
424+
certs = [
425+
cryptography.x509.load_pem_x509_certificate(pem, backend=default_backend())
426+
for pem in STAGE_CERT_LIST
427+
]
428+
real_intermediate = certs[1]
429+
intermediate = mock_cert(real_intermediate)
430+
uses_mock = mock.Mock()
431+
uses_mock.key_cert_sign = False
432+
mock_cert_extension(intermediate, cryptography.x509.KeyUsage, uses_mock)
433+
certs[1] = intermediate
434+
435+
with mock.patch("cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
436+
load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
437+
s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
438+
with pytest.raises(autograph_utils.CertificateCannotSign) as excinfo:
439+
await s.verify_x5u(FAKE_CERT_URL)
440+
441+
assert excinfo.value.detail.startswith(
442+
"Certificate cannot be used for signing because "
443+
)
444+
assert excinfo.value.cert == intermediate
445+
assert excinfo.value.extra == "key usage is incomplete"
446+
447+
394448
async def test_verify_leaf_code_signing(
395449
aiohttp_session, mock_with_x5u, cache, now_fixed
396450
):

0 commit comments

Comments
 (0)