codekingpro/portable-devtools
114k
1# This file is dual licensed under the terms of the Apache License, Version2# 2.0, and the BSD License. See the LICENSE file in the root of this repository3# for complete details.4 5from __future__ import annotations6 7import abc8import datetime9import hashlib10import ipaddress11import typing12from collections.abc import Iterable, Iterator13 14from cryptography import utils15from cryptography.hazmat.bindings._rust import asn116from cryptography.hazmat.bindings._rust import x509 as rust_x50917from cryptography.hazmat.primitives import constant_time, serialization18from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey19from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey20from cryptography.hazmat.primitives.asymmetric.types import (21 CertificateIssuerPublicKeyTypes,22 CertificatePublicKeyTypes,23)24from cryptography.x509.certificate_transparency import (25 SignedCertificateTimestamp,26)27from cryptography.x509.general_name import (28 DirectoryName,29 DNSName,30 GeneralName,31 IPAddress,32 OtherName,33 RegisteredID,34 RFC822Name,35 UniformResourceIdentifier,36 _IPAddressTypes,37)38from cryptography.x509.name import Name, RelativeDistinguishedName39from cryptography.x509.oid import (40 CRLEntryExtensionOID,41 ExtensionOID,42 ObjectIdentifier,43 OCSPExtensionOID,44)45 46ExtensionTypeVar = typing.TypeVar(47 "ExtensionTypeVar", bound="ExtensionType", covariant=True48)49 50 51def _key_identifier_from_public_key(52 public_key: CertificatePublicKeyTypes,53) -> bytes:54 if isinstance(public_key, RSAPublicKey):55 data = public_key.public_bytes(56 serialization.Encoding.DER,57 serialization.PublicFormat.PKCS1,58 )59 elif isinstance(public_key, EllipticCurvePublicKey):60 data = public_key.public_bytes(61 serialization.Encoding.X962,62 serialization.PublicFormat.UncompressedPoint,63 )64 else:65 # This is a very slow way to do this.66 serialized = public_key.public_bytes(67 serialization.Encoding.DER,68 serialization.PublicFormat.SubjectPublicKeyInfo,69 )70 data = asn1.parse_spki_for_data(serialized)71 72 return hashlib.sha1(data).digest()73 74 75def _make_sequence_methods(field_name: str):76 def len_method(self) -> int:77 return len(getattr(self, field_name))78 79 def iter_method(self):80 return iter(getattr(self, field_name))81 82 def getitem_method(self, idx):83 return getattr(self, field_name)[idx]84 85 return len_method, iter_method, getitem_method86 87 88class DuplicateExtension(Exception):89 def __init__(self, msg: str, oid: ObjectIdentifier) -> None:90 super().__init__(msg)91 self.oid = oid92 93 94class ExtensionNotFound(Exception):95 def __init__(self, msg: str, oid: ObjectIdentifier) -> None:96 super().__init__(msg)97 self.oid = oid98 99 100class ExtensionType(metaclass=abc.ABCMeta):101 oid: typing.ClassVar[ObjectIdentifier]102 103 def public_bytes(self) -> bytes:104 """105 Serializes the extension type to DER.106 """107 raise NotImplementedError(108 f"public_bytes is not implemented for extension type {self!r}"109 )110 111 112class Extensions:113 def __init__(self, extensions: Iterable[Extension[ExtensionType]]) -> None:114 self._extensions = list(extensions)115 116 def get_extension_for_oid(117 self, oid: ObjectIdentifier118 ) -> Extension[ExtensionType]:119 for ext in self:120 if ext.oid == oid:121 return ext122 123 raise ExtensionNotFound(f"No {oid} extension was found", oid)124 125 def get_extension_for_class(126 self, extclass: type[ExtensionTypeVar]127 ) -> Extension[ExtensionTypeVar]:128 if extclass is UnrecognizedExtension:129 raise TypeError(130 "UnrecognizedExtension can't be used with "131 "get_extension_for_class because more than one instance of the"132 " class may be present."133 )134 135 for ext in self:136 if isinstance(ext.value, extclass):137 return ext138 139 raise ExtensionNotFound(140 f"No {extclass} extension was found", extclass.oid141 )142 143 __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions")144 145 def __repr__(self) -> str:146 return f"<Extensions({self._extensions})>"147 148 149class CRLNumber(ExtensionType):150 oid = ExtensionOID.CRL_NUMBER151 152 def __init__(self, crl_number: int) -> None:153 if not isinstance(crl_number, int):154 raise TypeError("crl_number must be an integer")155 156 self._crl_number = crl_number157 158 def __eq__(self, other: object) -> bool:159 if not isinstance(other, CRLNumber):160 return NotImplemented161 162 return self.crl_number == other.crl_number163 164 def __hash__(self) -> int:165 return hash(self.crl_number)166 167 def __repr__(self) -> str:168 return f"<CRLNumber({self.crl_number})>"169 170 @property171 def crl_number(self) -> int:172 return self._crl_number173 174 def public_bytes(self) -> bytes:175 return rust_x509.encode_extension_value(self)176 177 178class AuthorityKeyIdentifier(ExtensionType):179 oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER180 181 def __init__(182 self,183 key_identifier: bytes | None,184 authority_cert_issuer: Iterable[GeneralName] | None,185 authority_cert_serial_number: int | None,186 ) -> None:187 if (authority_cert_issuer is None) != (188 authority_cert_serial_number is None189 ):190 raise ValueError(191 "authority_cert_issuer and authority_cert_serial_number "192 "must both be present or both None"193 )194 195 if authority_cert_issuer is not None:196 authority_cert_issuer = list(authority_cert_issuer)197 if not all(198 isinstance(x, GeneralName) for x in authority_cert_issuer199 ):200 raise TypeError(201 "authority_cert_issuer must be a list of GeneralName "202 "objects"203 )204 205 if authority_cert_serial_number is not None and not isinstance(206 authority_cert_serial_number, int207 ):208 raise TypeError("authority_cert_serial_number must be an integer")209 210 self._key_identifier = key_identifier211 self._authority_cert_issuer = authority_cert_issuer212 self._authority_cert_serial_number = authority_cert_serial_number213 214 # This takes a subset of CertificatePublicKeyTypes because an issuer215 # cannot have an X25519/X448 key. This introduces some unfortunate216 # asymmetry that requires typing users to explicitly217 # narrow their type, but we should make this accurate and not just218 # convenient.219 @classmethod220 def from_issuer_public_key(221 cls, public_key: CertificateIssuerPublicKeyTypes222 ) -> AuthorityKeyIdentifier:223 digest = _key_identifier_from_public_key(public_key)224 return cls(225 key_identifier=digest,226 authority_cert_issuer=None,227 authority_cert_serial_number=None,228 )229 230 @classmethod231 def from_issuer_subject_key_identifier(232 cls, ski: SubjectKeyIdentifier233 ) -> AuthorityKeyIdentifier:234 return cls(235 key_identifier=ski.digest,236 authority_cert_issuer=None,237 authority_cert_serial_number=None,238 )239 240 def __repr__(self) -> str:241 return (242 f"<AuthorityKeyIdentifier(key_identifier={self.key_identifier!r}, "243 f"authority_cert_issuer={self.authority_cert_issuer}, "244 f"authority_cert_serial_number={self.authority_cert_serial_number}"245 ")>"246 )247 248 def __eq__(self, other: object) -> bool:249 if not isinstance(other, AuthorityKeyIdentifier):250 return NotImplemented251 252 return (253 self.key_identifier == other.key_identifier254 and self.authority_cert_issuer == other.authority_cert_issuer255 and self.authority_cert_serial_number256 == other.authority_cert_serial_number257 )258 259 def __hash__(self) -> int:260 if self.authority_cert_issuer is None:261 aci = None262 else:263 aci = tuple(self.authority_cert_issuer)264 return hash(265 (self.key_identifier, aci, self.authority_cert_serial_number)266 )267 268 @property269 def key_identifier(self) -> bytes | None:270 return self._key_identifier271 272 @property273 def authority_cert_issuer(274 self,275 ) -> list[GeneralName] | None:276 return self._authority_cert_issuer277 278 @property279 def authority_cert_serial_number(self) -> int | None:280 return self._authority_cert_serial_number281 282 def public_bytes(self) -> bytes:283 return rust_x509.encode_extension_value(self)284 285 286class SubjectKeyIdentifier(ExtensionType):287 oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER288 289 def __init__(self, digest: bytes) -> None:290 self._digest = digest291 292 @classmethod293 def from_public_key(294 cls, public_key: CertificatePublicKeyTypes295 ) -> SubjectKeyIdentifier:296 return cls(_key_identifier_from_public_key(public_key))297 298 @property299 def digest(self) -> bytes:300 return self._digest301 302 @property303 def key_identifier(self) -> bytes:304 return self._digest305 306 def __repr__(self) -> str:307 return f"<SubjectKeyIdentifier(digest={self.digest!r})>"308 309 def __eq__(self, other: object) -> bool:310 if not isinstance(other, SubjectKeyIdentifier):311 return NotImplemented312 313 return constant_time.bytes_eq(self.digest, other.digest)314 315 def __hash__(self) -> int:316 return hash(self.digest)317 318 def public_bytes(self) -> bytes:319 return rust_x509.encode_extension_value(self)320 321 322class AuthorityInformationAccess(ExtensionType):323 oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS324 325 def __init__(self, descriptions: Iterable[AccessDescription]) -> None:326 descriptions = list(descriptions)327 if not all(isinstance(x, AccessDescription) for x in descriptions):328 raise TypeError(329 "Every item in the descriptions list must be an "330 "AccessDescription"331 )332 333 self._descriptions = descriptions334 335 __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions")336 337 def __repr__(self) -> str:338 return f"<AuthorityInformationAccess({self._descriptions})>"339 340 def __eq__(self, other: object) -> bool:341 if not isinstance(other, AuthorityInformationAccess):342 return NotImplemented343 344 return self._descriptions == other._descriptions345 346 def __hash__(self) -> int:347 return hash(tuple(self._descriptions))348 349 def public_bytes(self) -> bytes:350 return rust_x509.encode_extension_value(self)351 352 353class SubjectInformationAccess(ExtensionType):354 oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS355 356 def __init__(self, descriptions: Iterable[AccessDescription]) -> None:357 descriptions = list(descriptions)358 if not all(isinstance(x, AccessDescription) for x in descriptions):359 raise TypeError(360 "Every item in the descriptions list must be an "361 "AccessDescription"362 )363 364 self._descriptions = descriptions365 366 __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions")367 368 def __repr__(self) -> str:369 return f"<SubjectInformationAccess({self._descriptions})>"370 371 def __eq__(self, other: object) -> bool:372 if not isinstance(other, SubjectInformationAccess):373 return NotImplemented374 375 return self._descriptions == other._descriptions376 377 def __hash__(self) -> int:378 return hash(tuple(self._descriptions))379 380 def public_bytes(self) -> bytes:381 return rust_x509.encode_extension_value(self)382 383 384class AccessDescription:385 def __init__(386 self, access_method: ObjectIdentifier, access_location: GeneralName387 ) -> None:388 if not isinstance(access_method, ObjectIdentifier):389 raise TypeError("access_method must be an ObjectIdentifier")390 391 if not isinstance(access_location, GeneralName):392 raise TypeError("access_location must be a GeneralName")393 394 self._access_method = access_method395 self._access_location = access_location396 397 def __repr__(self) -> str:398 return (399 f"<AccessDescription(access_method={self.access_method}, "400 f"access_location={self.access_location})>"401 )402 403 def __eq__(self, other: object) -> bool:404 if not isinstance(other, AccessDescription):405 return NotImplemented406 407 return (408 self.access_method == other.access_method409 and self.access_location == other.access_location410 )411 412 def __hash__(self) -> int:413 return hash((self.access_method, self.access_location))414 415 @property416 def access_method(self) -> ObjectIdentifier:417 return self._access_method418 419 @property420 def access_location(self) -> GeneralName:421 return self._access_location422 423 424class BasicConstraints(ExtensionType):425 oid = ExtensionOID.BASIC_CONSTRAINTS426 427 def __init__(self, ca: bool, path_length: int | None) -> None:428 if not isinstance(ca, bool):429 raise TypeError("ca must be a boolean value")430 431 if path_length is not None and not ca:432 raise ValueError("path_length must be None when ca is False")433 434 if path_length is not None and (435 not isinstance(path_length, int) or path_length < 0436 ):437 raise TypeError(438 "path_length must be a non-negative integer or None"439 )440 441 self._ca = ca442 self._path_length = path_length443 444 @property445 def ca(self) -> bool:446 return self._ca447 448 @property449 def path_length(self) -> int | None:450 return self._path_length451 452 def __repr__(self) -> str:453 return (454 f"<BasicConstraints(ca={self.ca}, path_length={self.path_length})>"455 )456 457 def __eq__(self, other: object) -> bool:458 if not isinstance(other, BasicConstraints):459 return NotImplemented460 461 return self.ca == other.ca and self.path_length == other.path_length462 463 def __hash__(self) -> int:464 return hash((self.ca, self.path_length))465 466 def public_bytes(self) -> bytes:467 return rust_x509.encode_extension_value(self)468 469 470class DeltaCRLIndicator(ExtensionType):471 oid = ExtensionOID.DELTA_CRL_INDICATOR472 473 def __init__(self, crl_number: int) -> None:474 if not isinstance(crl_number, int):475 raise TypeError("crl_number must be an integer")476 477 self._crl_number = crl_number478 479 @property480 def crl_number(self) -> int:481 return self._crl_number482 483 def __eq__(self, other: object) -> bool:484 if not isinstance(other, DeltaCRLIndicator):485 return NotImplemented486 487 return self.crl_number == other.crl_number488 489 def __hash__(self) -> int:490 return hash(self.crl_number)491 492 def __repr__(self) -> str:493 return f"<DeltaCRLIndicator(crl_number={self.crl_number})>"494 495 def public_bytes(self) -> bytes:496 return rust_x509.encode_extension_value(self)497 498 499class CRLDistributionPoints(ExtensionType):500 oid = ExtensionOID.CRL_DISTRIBUTION_POINTS501 502 def __init__(503 self, distribution_points: Iterable[DistributionPoint]504 ) -> None:505 distribution_points = list(distribution_points)506 if not all(507 isinstance(x, DistributionPoint) for x in distribution_points508 ):509 raise TypeError(510 "distribution_points must be a list of DistributionPoint "511 "objects"512 )513 514 self._distribution_points = distribution_points515 516 __len__, __iter__, __getitem__ = _make_sequence_methods(517 "_distribution_points"518 )519 520 def __repr__(self) -> str:521 return f"<CRLDistributionPoints({self._distribution_points})>"522 523 def __eq__(self, other: object) -> bool:524 if not isinstance(other, CRLDistributionPoints):525 return NotImplemented526 527 return self._distribution_points == other._distribution_points528 529 def __hash__(self) -> int:530 return hash(tuple(self._distribution_points))531 532 def public_bytes(self) -> bytes:533 return rust_x509.encode_extension_value(self)534 535 536class FreshestCRL(ExtensionType):537 oid = ExtensionOID.FRESHEST_CRL538 539 def __init__(540 self, distribution_points: Iterable[DistributionPoint]541 ) -> None:542 distribution_points = list(distribution_points)543 if not all(544 isinstance(x, DistributionPoint) for x in distribution_points545 ):546 raise TypeError(547 "distribution_points must be a list of DistributionPoint "548 "objects"549 )550 551 self._distribution_points = distribution_points552 553 __len__, __iter__, __getitem__ = _make_sequence_methods(554 "_distribution_points"555 )556 557 def __repr__(self) -> str:558 return f"<FreshestCRL({self._distribution_points})>"559 560 def __eq__(self, other: object) -> bool:561 if not isinstance(other, FreshestCRL):562 return NotImplemented563 564 return self._distribution_points == other._distribution_points565 566 def __hash__(self) -> int:567 return hash(tuple(self._distribution_points))568 569 def public_bytes(self) -> bytes:570 return rust_x509.encode_extension_value(self)571 572 573class DistributionPoint:574 def __init__(575 self,576 full_name: Iterable[GeneralName] | None,577 relative_name: RelativeDistinguishedName | None,578 reasons: frozenset[ReasonFlags] | None,579 crl_issuer: Iterable[GeneralName] | None,580 ) -> None:581 if full_name and relative_name:582 raise ValueError(583 "You cannot provide both full_name and relative_name, at "584 "least one must be None."585 )586 if not full_name and not relative_name and not crl_issuer:587 raise ValueError(588 "Either full_name, relative_name or crl_issuer must be "589 "provided."590 )591 592 if full_name is not None:593 full_name = list(full_name)594 if not all(isinstance(x, GeneralName) for x in full_name):595 raise TypeError(596 "full_name must be a list of GeneralName objects"597 )598 599 if relative_name:600 if not isinstance(relative_name, RelativeDistinguishedName):601 raise TypeError(602 "relative_name must be a RelativeDistinguishedName"603 )604 605 if crl_issuer is not None:606 crl_issuer = list(crl_issuer)607 if not all(isinstance(x, GeneralName) for x in crl_issuer):608 raise TypeError(609 "crl_issuer must be None or a list of general names"610 )611 612 if reasons and (613 not isinstance(reasons, frozenset)614 or not all(isinstance(x, ReasonFlags) for x in reasons)615 ):616 raise TypeError("reasons must be None or frozenset of ReasonFlags")617 618 if reasons and (619 ReasonFlags.unspecified in reasons620 or ReasonFlags.remove_from_crl in reasons621 ):622 raise ValueError(623 "unspecified and remove_from_crl are not valid reasons in a "624 "DistributionPoint"625 )626 627 self._full_name = full_name628 self._relative_name = relative_name629 self._reasons = reasons630 self._crl_issuer = crl_issuer631 632 def __repr__(self) -> str:633 return (634 "<DistributionPoint(full_name={0.full_name}, relative_name={0.rela"635 "tive_name}, reasons={0.reasons}, "636 "crl_issuer={0.crl_issuer})>".format(self)637 )638 639 def __eq__(self, other: object) -> bool:640 if not isinstance(other, DistributionPoint):641 return NotImplemented642 643 return (644 self.full_name == other.full_name645 and self.relative_name == other.relative_name646 and self.reasons == other.reasons647 and self.crl_issuer == other.crl_issuer648 )649 650 def __hash__(self) -> int:651 if self.full_name is not None:652 fn: tuple[GeneralName, ...] | None = tuple(self.full_name)653 else:654 fn = None655 656 if self.crl_issuer is not None:657 crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer)658 else:659 crl_issuer = None660 661 return hash((fn, self.relative_name, self.reasons, crl_issuer))662 663 @property664 def full_name(self) -> list[GeneralName] | None:665 return self._full_name666 667 @property668 def relative_name(self) -> RelativeDistinguishedName | None:669 return self._relative_name670 671 @property672 def reasons(self) -> frozenset[ReasonFlags] | None:673 return self._reasons674 675 @property676 def crl_issuer(self) -> list[GeneralName] | None:677 return self._crl_issuer678 679 680class ReasonFlags(utils.Enum):681 unspecified = "unspecified"682 key_compromise = "keyCompromise"683 ca_compromise = "cACompromise"684 affiliation_changed = "affiliationChanged"685 superseded = "superseded"686 cessation_of_operation = "cessationOfOperation"687 certificate_hold = "certificateHold"688 privilege_withdrawn = "privilegeWithdrawn"689 aa_compromise = "aACompromise"690 remove_from_crl = "removeFromCRL"691 692 693# These are distribution point bit string mappings. Not to be confused with694# CRLReason reason flags bit string mappings.695# ReasonFlags ::= BIT STRING {696# unused (0),697# keyCompromise (1),698# cACompromise (2),699# affiliationChanged (3),700# superseded (4),701# cessationOfOperation (5),702# certificateHold (6),703# privilegeWithdrawn (7),704# aACompromise (8) }705_REASON_BIT_MAPPING = {706 1: ReasonFlags.key_compromise,707 2: ReasonFlags.ca_compromise,708 3: ReasonFlags.affiliation_changed,709 4: ReasonFlags.superseded,710 5: ReasonFlags.cessation_of_operation,711 6: ReasonFlags.certificate_hold,712 7: ReasonFlags.privilege_withdrawn,713 8: ReasonFlags.aa_compromise,714}715 716_CRLREASONFLAGS = {717 ReasonFlags.key_compromise: 1,718 ReasonFlags.ca_compromise: 2,719 ReasonFlags.affiliation_changed: 3,720 ReasonFlags.superseded: 4,721 ReasonFlags.cessation_of_operation: 5,722 ReasonFlags.certificate_hold: 6,723 ReasonFlags.privilege_withdrawn: 7,724 ReasonFlags.aa_compromise: 8,725}726 727# CRLReason ::= ENUMERATED {728# unspecified (0),729# keyCompromise (1),730# cACompromise (2),731# affiliationChanged (3),732# superseded (4),733# cessationOfOperation (5),734# certificateHold (6),735# -- value 7 is not used736# removeFromCRL (8),737# privilegeWithdrawn (9),738# aACompromise (10) }739_CRL_ENTRY_REASON_ENUM_TO_CODE = {740 ReasonFlags.unspecified: 0,741 ReasonFlags.key_compromise: 1,742 ReasonFlags.ca_compromise: 2,743 ReasonFlags.affiliation_changed: 3,744 ReasonFlags.superseded: 4,745 ReasonFlags.cessation_of_operation: 5,746 ReasonFlags.certificate_hold: 6,747 ReasonFlags.remove_from_crl: 8,748 ReasonFlags.privilege_withdrawn: 9,749 ReasonFlags.aa_compromise: 10,750}751 752 753class PolicyConstraints(ExtensionType):754 oid = ExtensionOID.POLICY_CONSTRAINTS755 756 def __init__(757 self,758 require_explicit_policy: int | None,759 inhibit_policy_mapping: int | None,760 ) -> None:761 if require_explicit_policy is not None and not isinstance(762 require_explicit_policy, int763 ):764 raise TypeError(765 "require_explicit_policy must be a non-negative integer or "766 "None"767 )768 769 if inhibit_policy_mapping is not None and not isinstance(770 inhibit_policy_mapping, int771 ):772 raise TypeError(773 "inhibit_policy_mapping must be a non-negative integer or None"774 )775 776 if inhibit_policy_mapping is None and require_explicit_policy is None:777 raise ValueError(778 "At least one of require_explicit_policy and "779 "inhibit_policy_mapping must not be None"780 )781 782 self._require_explicit_policy = require_explicit_policy783 self._inhibit_policy_mapping = inhibit_policy_mapping784 785 def __repr__(self) -> str:786 return (787 "<PolicyConstraints(require_explicit_policy={0.require_explicit"788 "_policy}, inhibit_policy_mapping={0.inhibit_policy_"789 "mapping})>".format(self)790 )791 792 def __eq__(self, other: object) -> bool:793 if not isinstance(other, PolicyConstraints):794 return NotImplemented795 796 return (797 self.require_explicit_policy == other.require_explicit_policy798 and self.inhibit_policy_mapping == other.inhibit_policy_mapping799 )800 801 def __hash__(self) -> int:802 return hash(803 (self.require_explicit_policy, self.inhibit_policy_mapping)804 )805 806 @property807 def require_explicit_policy(self) -> int | None:808 return self._require_explicit_policy809 810 @property811 def inhibit_policy_mapping(self) -> int | None:812 return self._inhibit_policy_mapping813 814 def public_bytes(self) -> bytes:815 return rust_x509.encode_extension_value(self)816 817 818class CertificatePolicies(ExtensionType):819 oid = ExtensionOID.CERTIFICATE_POLICIES820 821 def __init__(self, policies: Iterable[PolicyInformation]) -> None:822 policies = list(policies)823 if not all(isinstance(x, PolicyInformation) for x in policies):824 raise TypeError(825 "Every item in the policies list must be a PolicyInformation"826 )827 828 self._policies = policies829 830 __len__, __iter__, __getitem__ = _make_sequence_methods("_policies")831 832 def __repr__(self) -> str:833 return f"<CertificatePolicies({self._policies})>"834 835 def __eq__(self, other: object) -> bool:836 if not isinstance(other, CertificatePolicies):837 return NotImplemented838 839 return self._policies == other._policies840 841 def __hash__(self) -> int:842 return hash(tuple(self._policies))843 844 def public_bytes(self) -> bytes:845 return rust_x509.encode_extension_value(self)846 847 848class PolicyInformation:849 def __init__(850 self,851 policy_identifier: ObjectIdentifier,852 policy_qualifiers: Iterable[str | UserNotice] | None,853 ) -> None:854 if not isinstance(policy_identifier, ObjectIdentifier):855 raise TypeError("policy_identifier must be an ObjectIdentifier")856 857 self._policy_identifier = policy_identifier858 859 if policy_qualifiers is not None:860 policy_qualifiers = list(policy_qualifiers)861 if not all(862 isinstance(x, (str, UserNotice)) for x in policy_qualifiers863 ):864 raise TypeError(865 "policy_qualifiers must be a list of strings and/or "866 "UserNotice objects or None"867 )868 869 self._policy_qualifiers = policy_qualifiers870 871 def __repr__(self) -> str:872 return (873 f"<PolicyInformation(policy_identifier={self.policy_identifier}, "874 f"policy_qualifiers={self.policy_qualifiers})>"875 )876 877 def __eq__(self, other: object) -> bool:878 if not isinstance(other, PolicyInformation):879 return NotImplemented880 881 return (882 self.policy_identifier == other.policy_identifier883 and self.policy_qualifiers == other.policy_qualifiers884 )885 886 def __hash__(self) -> int:887 if self.policy_qualifiers is not None:888 pq = tuple(self.policy_qualifiers)889 else:890 pq = None891 892 return hash((self.policy_identifier, pq))893 894 @property895 def policy_identifier(self) -> ObjectIdentifier:896 return self._policy_identifier897 898 @property899 def policy_qualifiers(900 self,901 ) -> list[str | UserNotice] | None:902 return self._policy_qualifiers903 904 905class UserNotice:906 def __init__(907 self,908 notice_reference: NoticeReference | None,909 explicit_text: str | None,910 ) -> None:911 if notice_reference and not isinstance(912 notice_reference, NoticeReference913 ):914 raise TypeError(915 "notice_reference must be None or a NoticeReference"916 )917 918 self._notice_reference = notice_reference919 self._explicit_text = explicit_text920 921 def __repr__(self) -> str:922 return (923 f"<UserNotice(notice_reference={self.notice_reference}, "924 f"explicit_text={self.explicit_text!r})>"925 )926 927 def __eq__(self, other: object) -> bool:928 if not isinstance(other, UserNotice):929 return NotImplemented930 931 return (932 self.notice_reference == other.notice_reference933 and self.explicit_text == other.explicit_text934 )935 936 def __hash__(self) -> int:937 return hash((self.notice_reference, self.explicit_text))938 939 @property940 def notice_reference(self) -> NoticeReference | None:941 return self._notice_reference942 943 @property944 def explicit_text(self) -> str | None:945 return self._explicit_text946 947 948class NoticeReference:949 def __init__(950 self,951 organization: str | None,952 notice_numbers: Iterable[int],953 ) -> None:954 self._organization = organization955 notice_numbers = list(notice_numbers)956 if not all(isinstance(x, int) for x in notice_numbers):957 raise TypeError("notice_numbers must be a list of integers")958 959 self._notice_numbers = notice_numbers960 961 def __repr__(self) -> str:962 return (963 f"<NoticeReference(organization={self.organization!r}, "964 f"notice_numbers={self.notice_numbers})>"965 )966 967 def __eq__(self, other: object) -> bool:968 if not isinstance(other, NoticeReference):969 return NotImplemented970 971 return (972 self.organization == other.organization973 and self.notice_numbers == other.notice_numbers974 )975 976 def __hash__(self) -> int:977 return hash((self.organization, tuple(self.notice_numbers)))978 979 @property980 def organization(self) -> str | None:981 return self._organization982 983 @property984 def notice_numbers(self) -> list[int]:985 return self._notice_numbers986 987 988class ExtendedKeyUsage(ExtensionType):989 oid = ExtensionOID.EXTENDED_KEY_USAGE990 991 def __init__(self, usages: Iterable[ObjectIdentifier]) -> None:992 usages = list(usages)993 if not all(isinstance(x, ObjectIdentifier) for x in usages):994 raise TypeError(995 "Every item in the usages list must be an ObjectIdentifier"996 )997 998 self._usages = usages999 1000 __len__, __iter__, __getitem__ = _make_sequence_methods("_usages")1001 1002 def __repr__(self) -> str:1003 return f"<ExtendedKeyUsage({self._usages})>"1004 1005 def __eq__(self, other: object) -> bool:1006 if not isinstance(other, ExtendedKeyUsage):1007 return NotImplemented1008 1009 return self._usages == other._usages1010 1011 def __hash__(self) -> int:1012 return hash(tuple(self._usages))1013 1014 def public_bytes(self) -> bytes:1015 return rust_x509.encode_extension_value(self)1016 1017 1018class OCSPNoCheck(ExtensionType):1019 oid = ExtensionOID.OCSP_NO_CHECK1020 1021 def __eq__(self, other: object) -> bool:1022 if not isinstance(other, OCSPNoCheck):1023 return NotImplemented1024 1025 return True1026 1027 def __hash__(self) -> int:1028 return hash(OCSPNoCheck)1029 1030 def __repr__(self) -> str:1031 return "<OCSPNoCheck()>"1032 1033 def public_bytes(self) -> bytes:1034 return rust_x509.encode_extension_value(self)1035 1036 1037class PrecertPoison(ExtensionType):1038 oid = ExtensionOID.PRECERT_POISON1039 1040 def __eq__(self, other: object) -> bool:1041 if not isinstance(other, PrecertPoison):1042 return NotImplemented1043 1044 return True1045 1046 def __hash__(self) -> int:1047 return hash(PrecertPoison)1048 1049 def __repr__(self) -> str:1050 return "<PrecertPoison()>"1051 1052 def public_bytes(self) -> bytes:1053 return rust_x509.encode_extension_value(self)1054 1055 1056class TLSFeature(ExtensionType):1057 oid = ExtensionOID.TLS_FEATURE1058 1059 def __init__(self, features: Iterable[TLSFeatureType]) -> None:1060 features = list(features)1061 if (1062 not all(isinstance(x, TLSFeatureType) for x in features)1063 or len(features) == 01064 ):1065 raise TypeError(1066 "features must be a list of elements from the TLSFeatureType "1067 "enum"1068 )1069 1070 self._features = features1071 1072 __len__, __iter__, __getitem__ = _make_sequence_methods("_features")1073 1074 def __repr__(self) -> str:1075 return f"<TLSFeature(features={self._features})>"1076 1077 def __eq__(self, other: object) -> bool:1078 if not isinstance(other, TLSFeature):1079 return NotImplemented1080 1081 return self._features == other._features1082 1083 def __hash__(self) -> int:1084 return hash(tuple(self._features))1085 1086 def public_bytes(self) -> bytes:1087 return rust_x509.encode_extension_value(self)1088 1089 1090class TLSFeatureType(utils.Enum):1091 # status_request is defined in RFC 6066 and is used for what is commonly1092 # called OCSP Must-Staple when present in the TLS Feature extension in an1093 # X.509 certificate.1094 status_request = 51095 # status_request_v2 is defined in RFC 6961 and allows multiple OCSP1096 # responses to be provided. It is not currently in use by clients or1097 # servers.1098 status_request_v2 = 171099 1100 1101_TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType}1102 1103 1104class InhibitAnyPolicy(ExtensionType):1105 oid = ExtensionOID.INHIBIT_ANY_POLICY1106 1107 def __init__(self, skip_certs: int) -> None:1108 if not isinstance(skip_certs, int):1109 raise TypeError("skip_certs must be an integer")1110 1111 if skip_certs < 0:1112 raise ValueError("skip_certs must be a non-negative integer")1113 1114 self._skip_certs = skip_certs1115 1116 def __repr__(self) -> str:1117 return f"<InhibitAnyPolicy(skip_certs={self.skip_certs})>"1118 1119 def __eq__(self, other: object) -> bool:1120 if not isinstance(other, InhibitAnyPolicy):1121 return NotImplemented1122 1123 return self.skip_certs == other.skip_certs1124 1125 def __hash__(self) -> int:1126 return hash(self.skip_certs)1127 1128 @property1129 def skip_certs(self) -> int:1130 return self._skip_certs1131 1132 def public_bytes(self) -> bytes:1133 return rust_x509.encode_extension_value(self)1134 1135 1136class KeyUsage(ExtensionType):1137 oid = ExtensionOID.KEY_USAGE1138 1139 def __init__(1140 self,1141 digital_signature: bool,1142 content_commitment: bool,1143 key_encipherment: bool,1144 data_encipherment: bool,1145 key_agreement: bool,1146 key_cert_sign: bool,1147 crl_sign: bool,1148 encipher_only: bool,1149 decipher_only: bool,1150 ) -> None:1151 if not key_agreement and (encipher_only or decipher_only):1152 raise ValueError(1153 "encipher_only and decipher_only can only be true when "1154 "key_agreement is true"1155 )1156 1157 self._digital_signature = digital_signature1158 self._content_commitment = content_commitment1159 self._key_encipherment = key_encipherment1160 self._data_encipherment = data_encipherment1161 self._key_agreement = key_agreement1162 self._key_cert_sign = key_cert_sign1163 self._crl_sign = crl_sign1164 self._encipher_only = encipher_only1165 self._decipher_only = decipher_only1166 1167 @property1168 def digital_signature(self) -> bool:1169 return self._digital_signature1170 1171 @property1172 def content_commitment(self) -> bool:1173 return self._content_commitment1174 1175 @property1176 def key_encipherment(self) -> bool:1177 return self._key_encipherment1178 1179 @property1180 def data_encipherment(self) -> bool:1181 return self._data_encipherment1182 1183 @property1184 def key_agreement(self) -> bool:1185 return self._key_agreement1186 1187 @property1188 def key_cert_sign(self) -> bool:1189 return self._key_cert_sign1190 1191 @property1192 def crl_sign(self) -> bool:1193 return self._crl_sign1194 1195 @property1196 def encipher_only(self) -> bool:1197 if not self.key_agreement:1198 raise ValueError(1199 "encipher_only is undefined unless key_agreement is true"1200 )