1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import datetime
import os
from functools import lru_cache
from ipaddress import IPv4Address
import pytest
from cryptography import x509
from cryptography.x509.general_name import DNSName, IPAddress
from cryptography.x509.verification import (
PolicyBuilder,
Store,
VerificationError,
)
from tests.x509.test_x509 import _load_cert
@lru_cache(maxsize=1)
def dummy_store() -> Store:
cert = _load_cert(
os.path.join("x509", "cryptography.io.pem"),
x509.load_pem_x509_certificate,
)
return Store([cert])
class TestPolicyBuilder:
def test_time_already_set(self):
with pytest.raises(ValueError):
PolicyBuilder().time(datetime.datetime.now()).time(
datetime.datetime.now()
)
def test_store_already_set(self):
with pytest.raises(ValueError):
PolicyBuilder().store(dummy_store()).store(dummy_store())
def test_max_chain_depth_already_set(self):
with pytest.raises(ValueError):
PolicyBuilder().max_chain_depth(8).max_chain_depth(9)
def test_ipaddress_subject(self):
policy = (
PolicyBuilder()
.store(dummy_store())
.build_server_verifier(IPAddress(IPv4Address("0.0.0.0")))
)
assert policy.subject == IPAddress(IPv4Address("0.0.0.0"))
def test_dnsname_subject(self):
policy = (
PolicyBuilder()
.store(dummy_store())
.build_server_verifier(DNSName("cryptography.io"))
)
assert policy.subject == DNSName("cryptography.io")
def test_subject_bad_types(self):
# Subject must be a supported GeneralName type
with pytest.raises(TypeError):
PolicyBuilder().store(dummy_store()).build_server_verifier(
"cryptography.io" # type: ignore[arg-type]
)
with pytest.raises(TypeError):
PolicyBuilder().store(dummy_store()).build_server_verifier(
"0.0.0.0" # type: ignore[arg-type]
)
with pytest.raises(TypeError):
PolicyBuilder().store(dummy_store()).build_server_verifier(
IPv4Address("0.0.0.0") # type: ignore[arg-type]
)
with pytest.raises(TypeError):
PolicyBuilder().store(dummy_store()).build_server_verifier(None) # type: ignore[arg-type]
def test_builder_pattern(self):
now = datetime.datetime.now().replace(microsecond=0)
store = dummy_store()
max_chain_depth = 16
builder = PolicyBuilder()
builder = builder.time(now)
builder = builder.store(store)
builder = builder.max_chain_depth(max_chain_depth)
verifier = builder.build_server_verifier(DNSName("cryptography.io"))
assert verifier.subject == DNSName("cryptography.io")
assert verifier.validation_time == now
assert verifier.store == store
assert verifier.max_chain_depth == max_chain_depth
def test_build_server_verifier_missing_store(self):
with pytest.raises(
ValueError, match="A server verifier must have a trust store"
):
PolicyBuilder().build_server_verifier(DNSName("cryptography.io"))
class TestStore:
def test_store_rejects_empty_list(self):
with pytest.raises(ValueError):
Store([])
def test_store_rejects_non_certificates(self):
with pytest.raises(TypeError):
Store(["not a cert"]) # type: ignore[list-item]
class TestClientVerifier:
def test_build_client_verifier_missing_store(self):
with pytest.raises(
ValueError, match="A client verifier must have a trust store"
):
PolicyBuilder().build_client_verifier()
def test_verify(self):
# expires 2018-11-16 01:15:03 UTC
leaf = _load_cert(
os.path.join("x509", "cryptography.io.pem"),
x509.load_pem_x509_certificate,
)
store = Store([leaf])
validation_time = datetime.datetime.fromisoformat(
"2018-11-16T00:00:00+00:00"
)
builder = PolicyBuilder().store(store)
builder = builder.time(validation_time).max_chain_depth(16)
verifier = builder.build_client_verifier()
assert verifier.validation_time == validation_time.replace(tzinfo=None)
assert verifier.max_chain_depth == 16
assert verifier.store is store
verified_client = verifier.verify(leaf, [])
assert verified_client.chain == [leaf]
assert x509.DNSName("www.cryptography.io") in verified_client.subjects
assert x509.DNSName("cryptography.io") in verified_client.subjects
assert len(verified_client.subjects) == 2
def test_verify_fails_renders_oid(self):
leaf = _load_cert(
os.path.join("x509", "custom", "ekucrit-testuser-cert.pem"),
x509.load_pem_x509_certificate,
)
store = Store([leaf])
validation_time = datetime.datetime.fromisoformat(
"2024-06-26T00:00:00+00:00"
)
builder = PolicyBuilder().store(store)
builder = builder.time(validation_time)
verifier = builder.build_client_verifier()
pattern = (
r"invalid extension: 2\.5\.29\.37: "
r"Certificate extension has incorrect criticality"
)
with pytest.raises(
VerificationError,
match=pattern,
):
verifier.verify(leaf, [])
class TestServerVerifier:
@pytest.mark.parametrize(
("validation_time", "valid"),
[
# 03:15:02 UTC+2, or 1 second before expiry in UTC
("2018-11-16T03:15:02+02:00", True),
# 00:15:04 UTC-1, or 1 second after expiry in UTC
("2018-11-16T00:15:04-01:00", False),
],
)
def test_verify_tz_aware(self, validation_time, valid):
# expires 2018-11-16 01:15:03 UTC
leaf = _load_cert(
os.path.join("x509", "cryptography.io.pem"),
x509.load_pem_x509_certificate,
)
store = Store([leaf])
builder = PolicyBuilder().store(store)
builder = builder.time(
datetime.datetime.fromisoformat(validation_time)
)
verifier = builder.build_server_verifier(DNSName("cryptography.io"))
if valid:
assert verifier.verify(leaf, []) == [leaf]
else:
with pytest.raises(
x509.verification.VerificationError,
match="cert is not valid at validation time",
):
verifier.verify(leaf, [])
|