Skip to content

Commit 5ef0b92

Browse files
committed
Custom extension policy tests.
1 parent 7b09f8f commit 5ef0b92

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

tests/x509/verification/test_verification.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
import os
77
from functools import lru_cache
88
from ipaddress import IPv4Address
9+
from typing import Optional
910

1011
import pytest
1112

1213
from cryptography import utils, x509
1314
from cryptography.hazmat._oid import ExtendedKeyUsageOID
15+
from cryptography.x509.extensions import ExtendedKeyUsage
1416
from cryptography.x509.general_name import DNSName, IPAddress
1517
from cryptography.x509.verification import (
18+
Criticality,
19+
ExtensionPolicy,
20+
Policy,
1621
PolicyBuilder,
1722
Store,
1823
VerificationError,
@@ -261,3 +266,162 @@ def test_error_message(self):
261266
match=r"<Certificate\(subject=.*?CN=www.cryptography.io.*?\)>",
262267
):
263268
verifier.verify(leaf, [])
269+
270+
271+
class TestCustomExtensionPolicies:
272+
leaf = _load_cert(
273+
os.path.join("x509", "cryptography.io.pem"),
274+
x509.load_pem_x509_certificate,
275+
)
276+
ca = _load_cert(
277+
os.path.join("x509", "rapidssl_sha256_ca_g3.pem"),
278+
x509.load_pem_x509_certificate,
279+
)
280+
store = Store([ca])
281+
validation_time = datetime.datetime.fromisoformat(
282+
"2018-11-16T00:00:00+00:00"
283+
)
284+
285+
def test_builder_methods(self):
286+
ext_policy = ExtensionPolicy.permit_all()
287+
ext_policy = ext_policy.require_not_present(x509.BasicConstraints)
288+
with pytest.raises(
289+
ValueError, match=x509.BasicConstraints.oid.dotted_string
290+
):
291+
ext_policy.require_not_present(x509.BasicConstraints)
292+
with pytest.raises(
293+
ValueError, match=x509.BasicConstraints.oid.dotted_string
294+
):
295+
ext_policy.may_be_present(
296+
x509.BasicConstraints, Criticality.NON_CRITICAL, None
297+
)
298+
with pytest.raises(
299+
ValueError, match=x509.BasicConstraints.oid.dotted_string
300+
):
301+
ext_policy.require_present(
302+
x509.BasicConstraints, Criticality.CRITICAL, None
303+
)
304+
305+
with pytest.raises(TypeError):
306+
307+
class _Extension:
308+
pass
309+
310+
ext_policy.require_present(
311+
_Extension, # type: ignore
312+
Criticality.AGNOSTIC,
313+
None,
314+
)
315+
316+
@staticmethod
317+
def _eku_validator_cb(policy, cert, ext: Optional[ExtendedKeyUsage]):
318+
assert isinstance(policy, Policy)
319+
assert (
320+
policy.validation_time
321+
== TestCustomExtensionPolicies.validation_time.replace(tzinfo=None)
322+
)
323+
assert isinstance(cert, x509.Certificate)
324+
assert ext is None or isinstance(ext, x509.ExtendedKeyUsage)
325+
326+
def test_custom_cb_pass(self):
327+
ca_ext_policy = ExtensionPolicy.webpki_defaults_ca()
328+
ee_ext_policy = ExtensionPolicy.webpki_defaults_ee()
329+
330+
ee_ext_policy = ee_ext_policy.may_be_present(
331+
ExtendedKeyUsage, Criticality.AGNOSTIC, self._eku_validator_cb
332+
)
333+
ca_ext_policy = ca_ext_policy.may_be_present(
334+
ExtendedKeyUsage, Criticality.AGNOSTIC, self._eku_validator_cb
335+
)
336+
337+
ca_validator_called = False
338+
339+
def ca_basic_constraints_validator(policy, cert, ext):
340+
assert cert == self.ca
341+
assert isinstance(policy, Policy)
342+
assert isinstance(cert, x509.Certificate)
343+
assert isinstance(ext, x509.BasicConstraints)
344+
nonlocal ca_validator_called
345+
ca_validator_called = True
346+
347+
ca_ext_policy = ca_ext_policy.may_be_present(
348+
x509.BasicConstraints,
349+
Criticality.AGNOSTIC,
350+
ca_basic_constraints_validator,
351+
)
352+
353+
builder = PolicyBuilder().store(self.store)
354+
builder = builder.time(self.validation_time).max_chain_depth(16)
355+
builder = builder.extension_policies(ca_ext_policy, ee_ext_policy)
356+
357+
builder.build_client_verifier().verify(self.leaf, [])
358+
assert ca_validator_called
359+
ca_validator_called = False
360+
361+
path = builder.build_server_verifier(
362+
DNSName("cryptography.io")
363+
).verify(self.leaf, [])
364+
assert ca_validator_called
365+
assert path == [self.leaf, self.ca]
366+
367+
def test_custom_cb_no_retval_enforced(self):
368+
ca_ext_policy = ExtensionPolicy.webpki_defaults_ca()
369+
ee_ext_policy = ExtensionPolicy.webpki_defaults_ee()
370+
371+
def validator(*_):
372+
return False
373+
374+
ca_ext_policy = ca_ext_policy.may_be_present(
375+
x509.BasicConstraints,
376+
Criticality.AGNOSTIC,
377+
validator,
378+
)
379+
ee_ext_policy = ee_ext_policy.may_be_present(
380+
ExtendedKeyUsage,
381+
Criticality.AGNOSTIC,
382+
validator,
383+
)
384+
385+
builder = PolicyBuilder().store(self.store).time(self.validation_time)
386+
builder = builder.extension_policies(ca_ext_policy, ee_ext_policy)
387+
388+
for verifier in (
389+
builder.build_client_verifier(),
390+
builder.build_server_verifier(DNSName("cryptography.io")),
391+
):
392+
with pytest.raises(
393+
VerificationError,
394+
match="Python validator must return None.",
395+
):
396+
verifier.verify(self.leaf, [])
397+
398+
def test_custom_cb_exception_fails_verification(self):
399+
ca_ext_policy = ExtensionPolicy.webpki_defaults_ca()
400+
ee_ext_policy = ExtensionPolicy.webpki_defaults_ee()
401+
402+
def validator(*_):
403+
raise ValueError("test")
404+
405+
ca_ext_policy = ca_ext_policy.may_be_present(
406+
x509.BasicConstraints,
407+
Criticality.AGNOSTIC,
408+
validator,
409+
)
410+
ee_ext_policy = ee_ext_policy.may_be_present(
411+
ExtendedKeyUsage,
412+
Criticality.AGNOSTIC,
413+
validator,
414+
)
415+
416+
builder = PolicyBuilder().store(self.store).time(self.validation_time)
417+
builder = builder.extension_policies(ca_ext_policy, ee_ext_policy)
418+
419+
for verifier in (
420+
builder.build_client_verifier(),
421+
builder.build_server_verifier(DNSName("cryptography.io")),
422+
):
423+
with pytest.raises(
424+
VerificationError,
425+
match="Python extension validator failed: ValueError: test",
426+
):
427+
verifier.verify(self.leaf, [])

0 commit comments

Comments
 (0)