307 lines
8.6 KiB
Python
307 lines
8.6 KiB
Python
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()
|