File: authority.py

package info (click to toggle)
python-pyhanko-certvalidator 0.26.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,956 kB
  • sloc: python: 9,254; sh: 47; makefile: 4
file content (306 lines) | stat: -rw-r--r-- 8,798 bytes parent folder | download | duplicates (2)
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
import abc
from dataclasses import dataclass
from typing import Optional

from asn1crypto import keys, x509

from .name_trees import process_general_subtrees
from .policy_decl import PKIXValidationParams

# TODO add support for roots that are limited in time?


@dataclass(frozen=True)
class TrustQualifiers:
    """
    .. versionadded 0.20.0

    Parameters that allow a trust root to be qualified.
    """

    standard_parameters: Optional['PKIXValidationParams'] = None
    """
    Standard validation parameters that will apply when initialising
    the PKIX validation process.
    """

    max_path_length: Optional[int] = None
    """
    Maximal allowed path length for this trust root, excluding self-issued
    intermediate CA certificates. If ``None``, any path length will be accepted.
    """

    max_aa_path_length: Optional[int] = None
    """
    Maximal allowed path length for this trust root for the purposes of
    AAControls. If ``None``, any path length will be accepted.
    """


class Authority(abc.ABC):
    """
    .. versionadded:: 0.20.0

    Abstract authority, i.e. a named key.
    """

    @property
    def name(self) -> x509.Name:
        """
        The authority's name.
        """
        raise NotImplementedError

    @property
    def public_key(self) -> keys.PublicKeyInfo:
        """
        The authority's public key.
        """
        raise NotImplementedError

    @property
    def hashable(self):
        """
        A hashable unique identifier of the authority, used in ``__eq__``
        and ``__hash__``.
        """
        raise NotImplementedError

    def __hash__(self):
        return hash(self.hashable)

    def __eq__(self, other):
        if not isinstance(other, Authority):
            return False

        return self.hashable == other.hashable

    @property
    def key_id(self) -> Optional[bytes]:
        """
        Key ID as (potentially) referenced in an authorityKeyIdentifier
        extension. Only used to eliminate non-matching trust anchors,
        never to retrieve keys or to definitively identify trust anchors.
        """
        raise NotImplementedError

    def is_potential_issuer_of(self, cert: x509.Certificate) -> bool:
        """
        Function to determine whether this trust root could potentially be an
        issuer of a given certificate.
        This function is used during path building.

        :param cert:
            The certificate to evaluate.
        """
        if cert.issuer != self.name:
            return False
        if cert.authority_key_identifier and self.key_id:
            if cert.authority_key_identifier != self.key_id:
                return False
        return True


class TrustAnchor:
    """
    Abstract trust root. A trust root is an authority with trust qualifiers.
    Equality of trust roots reduces to equality of authorities.
    """

    def __init__(
        self, authority: Authority, quals: Optional[TrustQualifiers] = None
    ):
        self._authority = authority
        self._quals = quals

    @property
    def authority(self) -> Authority:
        return self._authority

    @property
    def trust_qualifiers(self) -> TrustQualifiers:
        """
        Qualifiers for the trust root.
        """
        return self._quals or TrustQualifiers()

    def __eq__(self, other):
        return (
            isinstance(other, TrustAnchor)
            and other._authority == self._authority
        )

    def __hash__(self):
        return hash(self._authority)


def derive_quals_from_cert(cert: x509.Certificate) -> TrustQualifiers:
    """
    Extract trust qualifiers from data and extensions of a certificate.

    .. note::
        Recall that any property of a trust root other than its name and public
        key are in principle irrelevant to the PKIX validation algorithm
        itself.
        This function is merely a helper function that allows the certificate's
        other data to be conveniently gathered to populate the default
        validation parameters for paths deriving from that trust root.

    :param cert:
        The certificate from which to extract qualifiers (usually a
        self-signed one)
    :return:
        A :class:`TrustQualifiers` object with the extracted qualifiers.
    """
    # TODO align with RFC 5937?
    ext_found = False
    permitted_subtrees = excluded_subtrees = None
    if cert.name_constraints_value is not None:
        ext_found = True
        nc_ext: x509.NameConstraints = cert.name_constraints_value
        permitted_val = nc_ext['permitted_subtrees']
        if isinstance(permitted_val, x509.GeneralSubtrees):
            permitted_subtrees = process_general_subtrees(permitted_val)
        excluded_val = nc_ext['excluded_subtrees']
        if isinstance(excluded_val, x509.GeneralSubtrees):
            excluded_subtrees = process_general_subtrees(excluded_val)

    acceptable_policies = None
    if cert.certificate_policies_value is not None:
        ext_found = True
        policies_val: x509.CertificatePolicies = cert.certificate_policies_value
        acceptable_policies = frozenset(
            [pol_info['policy_identifier'].dotted for pol_info in policies_val]
        )

    params = None
    if ext_found:
        params = PKIXValidationParams(
            user_initial_policy_set=(
                acceptable_policies or frozenset(['any_policy'])
            ),
            # For trust roots where the user asked for this derivation,
            #  let's assume that they want the policies to be enforced.
            initial_explicit_policy=acceptable_policies is not None,
            initial_permitted_subtrees=permitted_subtrees,
            initial_excluded_subtrees=excluded_subtrees,
        )

    return TrustQualifiers(
        max_path_length=cert.max_path_length, standard_parameters=params
    )


class AuthorityWithCert(Authority):
    """
    .. versionadded:: 0.20.0

    Authority provisioned as a certificate.

    :param cert:
        The certificate.
    """

    def __init__(self, cert: x509.Certificate):
        self._cert = cert

    @property
    def name(self) -> x509.Name:
        return self._cert.subject

    @property
    def public_key(self):
        return self._cert.public_key

    @property
    def hashable(self):
        cert = self._cert
        return cert.subject.hashable, cert.public_key.dump()

    @property
    def key_id(self) -> Optional[bytes]:
        return self._cert.key_identifier

    @property
    def certificate(self) -> x509.Certificate:
        return self._cert

    def is_potential_issuer_of(self, cert: x509.Certificate):
        if not super().is_potential_issuer_of(cert):
            return False
        if cert.authority_issuer_serial:
            if cert.authority_issuer_serial != self._cert.issuer_serial:
                return False
        return True


class CertTrustAnchor(TrustAnchor):
    """
    .. versionadded:: 0.20.0

    Trust anchor provisioned as a certificate.

    :param cert:
        The certificate, usually self-signed.
    :param quals:
        Explicit trust qualifiers.
    :param derive_default_quals_from_cert:
        Flag indicating to derive default trust qualifiers from the certificate
        content if explicit ones are not provided. Defaults to ``False``.
    """

    def __init__(
        self,
        cert: x509.Certificate,
        quals: Optional[TrustQualifiers] = None,
        derive_default_quals_from_cert: bool = False,
    ):
        authority = AuthorityWithCert(cert)
        self._cert = cert
        super().__init__(authority, quals)
        self._derive = derive_default_quals_from_cert

    @property
    def certificate(self) -> x509.Certificate:
        return self._cert

    @property
    def trust_qualifiers(self) -> TrustQualifiers:
        if self._quals is not None:
            return self._quals
        elif self._derive:
            self._quals = quals = derive_quals_from_cert(self._cert)
            return quals
        else:
            return TrustQualifiers()


class NamedKeyAuthority(Authority):
    """
    Authority provisioned as a named key.

    :param entity_name:
        The name of the entity that controls the private key of the trust root.
    :param public_key:
        The trust root's public key.
    """

    def __init__(self, entity_name: x509.Name, public_key: keys.PublicKeyInfo):
        self._name = entity_name
        self._public_key = public_key

    @property
    def name(self) -> x509.Name:
        return self._name

    @property
    def public_key(self):
        return self._public_key

    @property
    def key_id(self) -> Optional[bytes]:
        return None

    @property
    def hashable(self):
        return self._name.hashable, self._public_key.dump()