Skip to content

Commit

Permalink
Require certificate verification callback to pass for the chain to be…
Browse files Browse the repository at this point in the history
… valid (pyca#2381)

Formerly unused 'cert_verif_cb' is called now. Caller of X509.verify() must
pass a callable which would return True for the certificate in the chain which
complies with the user's policy.

A minimal reasonalbe callback which can be used as default is provided.
  • Loading branch information
KonstantinShemyak committed Feb 10, 2020
1 parent 9c3dc35 commit 2e25fd7
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
azure-devops
click
coverage
freezegun
tox >= 2.4.1
twine >= 1.8.0
-e .[test,docs,docstest,pep8test]
Expand Down
7 changes: 4 additions & 3 deletions src/cryptography/hazmat/backends/openssl/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,9 @@ def verify(self, intermediates, trusted_roots, cert_verif_cb):
for cert in all_certs:
g.add_vertex(cert)
for cert in all_certs:
issuer_name = cert.issuer
issuer = None
for issuer_candidate in all_certs:
if issuer_candidate.subject == issuer_name:
if cert.is_issued_by(issuer_candidate):
issuer = issuer_candidate
break
if issuer:
Expand All @@ -220,7 +219,9 @@ def verify(self, intermediates, trusted_roots, cert_verif_cb):
trust_chains = []
for c in g.all_paths(self, 'Fake Top'):
del c[-1] # Remove 'Fake Top'
trust_chains.append(c)
chain_is_good = all([cert_verif_cb(c, i) for i in range(len(c))])
if chain_is_good:
trust_chains.append(c)

if len(trust_chains) == 0:
raise x509.base.NoValidTrustChain
Expand Down
38 changes: 36 additions & 2 deletions src/cryptography/x509/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from cryptography.hazmat.primitives.asymmetric import (
dsa, ec, ed25519, ed448, rsa
)
from cryptography.x509.extensions import Extension, ExtensionType
from cryptography.x509.extensions import Extension, ExtensionType, ExtensionOID
from cryptography.x509.name import Name


Expand Down Expand Up @@ -206,11 +206,45 @@ def verify(self, intermediates, trusted_roots, cert_verif_cb):
using certificates from `intermediates`.
:param intermediates: list of Certificate objects to build chains from
:param trusted_roots: list of Certificate objects which are trusted
:param cert_verif_cb: ignored for now. Will take (chain: List(Certificate), position: int)
:param cert_verif_cb: Will take (chain: List(Certificate), position: int)
:return: Iterable of lists of Certificate objects. Each list is a trust
chain starting with `self` and ending with one of `trusted_roots`.
"""

def default_cert_verif_cb(chain, i):
"""
Reasonable minimal default for the callback, applied to each certificate in the
trust chain.
For the chain to be valid, the callback must return True for all its certificates.
This example checks validity time, CA bits and path length.
:param chain: list of Certificate objects such as for i in range(len(chain) - 1),
issuer(chain[i]) == subject(chain[i+1].
The last (chain[-1]) certificate can be self-signed or not.
:param i: index of the checked certificate in the chain
:return: True if the certificate passes checks, False otherwise
"""
cert = chain[i]

# Check that CA bit is set for all certs but the leaf
if i > 0:
bc = cert.extensions.get_extension_for_oid(
ExtensionOID.BASIC_CONSTRAINTS
)
if bc is not None:
if not bc.value.ca:
return False

# Check that path length constraint, if present, is fulfilled
if bc.value.path_length is not None and bc.value.path_length > i:
return False

# Check validity period
now = datetime.datetime.now()
if cert.not_valid_before > now or cert.not_valid_after < now:
return False

return True

@six.add_metaclass(abc.ABCMeta)
class CertificateRevocationList(object):
Expand Down
16 changes: 15 additions & 1 deletion tests/x509/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from freezegun import freeze_time


def load_test_pem(filename):
Expand All @@ -28,7 +29,20 @@ def test_correct_3link_chain(self):

chains = leaf.verify(intermediates=[intermediate],
trusted_roots=[trusted_root],
cert_verif_cb=lambda: True)
cert_verif_cb=lambda c, i: True)

assert len(chains) == 1
chain = chains[0]
assert len(chain) == 3
assert chain[0] == leaf
assert chain[1] == intermediate
assert chain[2] == trusted_root

# Let the test pass also after expiration of the certificates
with freeze_time("2019-12-26"):
chains = leaf.verify(intermediates=[intermediate],
trusted_roots=[trusted_root],
cert_verif_cb=x509.base.default_cert_verif_cb)

assert len(chains) == 1
chain = chains[0]
Expand Down

0 comments on commit 2e25fd7

Please sign in to comment.