|
6 | 6 | import os
|
7 | 7 | from functools import lru_cache
|
8 | 8 | from ipaddress import IPv4Address
|
| 9 | +from typing import Optional |
9 | 10 |
|
10 | 11 | import pytest
|
11 | 12 |
|
12 | 13 | from cryptography import utils, x509
|
13 | 14 | from cryptography.hazmat._oid import ExtendedKeyUsageOID
|
| 15 | +from cryptography.x509.extensions import ExtendedKeyUsage |
14 | 16 | from cryptography.x509.general_name import DNSName, IPAddress
|
15 | 17 | from cryptography.x509.verification import (
|
| 18 | + Criticality, |
| 19 | + ExtensionPolicy, |
| 20 | + Policy, |
16 | 21 | PolicyBuilder,
|
17 | 22 | Store,
|
18 | 23 | VerificationError,
|
@@ -261,3 +266,162 @@ def test_error_message(self):
|
261 | 266 | match=r"<Certificate\(subject=.*?CN=www.cryptography.io.*?\)>",
|
262 | 267 | ):
|
263 | 268 | 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